Initial version

This commit is contained in:
Ganesh Viswanathan 2018-11-20 03:01:04 -06:00
commit 9787797d15
14 changed files with 826 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
nimcache
*.exe
*.swp
test*
toast

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Ganesh Viswanathan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

73
README.md Normal file
View file

@ -0,0 +1,73 @@
Nimterop is a [Nim](https://nim-lang.org/) package that aims to make C/C++ interop seamless
Nim has one of the best FFI you can find - importing C/C++ is supported out of the box. All you need to provide is type and proc definitions for Nim to interop with C/C++ binaries. Generation of these wrappers is easy for simple libraries but quickly gets out of hand. [c2nim](https://github.com/nim-lang/c2nim) greatly helps here by parsing and converting C/C++ into Nim but is limited due to the complex and constantly evolving C/C++ grammar. [nimgen](https://github.com/genotrance/nimgen) mainly focuses on automating the wrapping process and fills some holes but is again limited to c2nim's capabilities.
The goal of nimterop is to leverage the [tree-sitter](http://tree-sitter.github.io/tree-sitter/) engine to parse C/C++ code and then convert relevant portions of the AST into Nim definitions using compile-time macros. [tree-sitter](https://github.com/tree-sitter) is a Github sponsored project that can parse a variety of languages into an AST which is then leveraged by the [Atom](https://atom.io/) editor for syntax highlighting and code folding. The advantages of this approach are multifold:
- Benefit from the tree-sitter community's investment into language parsing
- Leverage Nim macros which are a user API and relatively stable
- Avoid depending on Nim compiler API which is evolving constantly
The nimterop feature set is still limited when compared with c2nim. Supported language constructs include:
- `#define NAME VALUE` where `VALUE` is a number (int, float, hex)
- `struct X`, `typedef struct`, `enum X`, `typedef enum`
- Functions with primitive types, structs, enums and typedef structs/enums as params and return values
Given the simplicity and success of this approach so far, it seems feasible to continue on for more complex code. The goal is to make interop seamless so nimterop will focus on wrapping headers and not the outright conversion of C/C++ implementation.
C++ constructs are still TBD depending on the results of the C interop.
__Installation__
Nimterop can be installed via [Nimble](https://github.com/nim-lang/nimble):
```
> nimble install http://github.com/genotrance/nimtreesitter?subdir=treesitter
> nimble install http://github.com/genotrance/nimtreesitter?subdir=treesitter_c
> nimble install http://github.com/genotrance/nimtreesitter?subdir=treesitter_cpp
> nimble install http://github.com/genotrance/nimterop
```
This will download and install nimterop in the standard Nimble package location, typically ~/.nimble. Once installed, it can be imported into any Nim program.
__Usage__
```nim
import nimterop/cimport
cDebug()
cDefine("HAS_ABC")
cDefine("HAS_ABC", "DEF")
cIncludeDir("clib/include")
cImport("clib.h")
cCompile("clib/src/*.c")
```
__Documentation__
Detailed documentation is still forthcoming.
`cDebug()` - enable debug messages
`cDefine()` - `#define` an identifer that is forwarded to the compiler using `{.passC: "-DXXX".}` as well as _eventually_ used in processing `#ifdef` statements
`cIncludeDir()` - add an include directory that is forwarded to the compiler using `{.passC: "-IXXX".}` as well as searched for files included using `cImport()` statements and following `cIncludeDir()` statements
`cImport()` - import all supported definitions from specific import header file
__Implementation Details__
In order to use the tree-sitter C library at compile-time, it has to be compiled into a separate binary called `toast` (to AST) since the Nim VM doesn't yet support FFI. `toast` takes a C/C++ file and runs it through the tree-sitter API which returns an AST data structure. This is then printed out to stdout in a Lisp S-Expression format.
The `cImport()` proc runs `toast` on the specified header file and parses the resulting S-Expression back into an AST data structure at compile time. This AST is then processed to generate the relevant Nim definitions to interop with the code accordingly. A few other helper procs are provided to influence this process.
The tree-sitter library is limited as well - it may fail on some advanced language constructs but is designed to handle them gracefully since it is expected to have bad code while actively typing in an editor. When an error is detected, tree-sitter includes an ERROR node at that location in the AST. At this time, `cImport()` will complain and continue if it encounters any errors. Depending on how severe the errors are, compilation may succeed or fail. Glaring issues will be communicated to the tree-sitter team but their goals may not always align with those of this project.
__Credits__
Nimterop depends on [tree-sitter](http://tree-sitter.github.io/tree-sitter/) and all licensing terms of [tree-sitter](https://github.com/tree-sitter/tree-sitter/blob/master/LICENSE) apply to the usage of this package. Interestingly, the tree-sitter functionality is [wrapped](https://github.com/genotrance/nimtreesitter) using c2nim and nimgen at this time. Depending on the success of this project, those could perhaps be bootstrapped using nimterop eventually.
__Feedback__
Nimterop is a work in progress and any feedback or suggestions are welcome. It is hosted on [GitHub](https://github.com/genotrance/nimterop) with an MIT license so issues, forks and PRs are most appreciated.

1
config.nims Normal file
View file

@ -0,0 +1 @@
switch("gcc.linkerexe", "g++")

16
nimterop.nimble Normal file
View file

@ -0,0 +1,16 @@
# Package
version = "0.1.0"
author = "genotrance"
description = "C/C++ interop for Nim"
license = "MIT"
bin = @["toast"]
installDirs = @["nimterop"]
# Dependencies
requires "nim >= 0.19.0", "treesitter >= 0.1.0", "treesitter_c >= 0.1.0", "treesitter_cpp >= 0.1.0", "regex >= 0.10.0"
task test, "Test":
exec "nim c -r tests/tnimterop"

218
nimterop/ast.nim Normal file
View file

@ -0,0 +1,218 @@
import macros, os, strformat
import regex
import getters, globals
proc addReorder*(): NimNode =
result = newNimNode(nnkStmtList)
if not gReorder:
gReorder = true
result.add parseStmt(
"{.experimental: \"codeReordering\".}"
)
proc addHeader*(fullpath: string) =
gCurrentHeader = ("header" & fullpath.splitFile().name.replace(re"[-.]+", ""))
gConstStr &= &" {gCurrentHeader} = \"{fullpath}\" # addHeader()\n"
#
# Preprocessor
#
proc preprocDef(node: Ast) =
if node.children.len() == 2:
let
name = getNodeValIf(node.children[0], "identifier")
val = getNodeValIf(node.children[1], "preproc_arg")
if name.nBl and val.nBl and name notin gConsts:
gConsts.add(name)
if val.getType().nBl:
# #define NAME VALUE
gConstStr &= &" {name.getIdentifier()}* = {val} # preprocDef()\n"
#
# Types
#
proc typeScan(node: Ast, sym, identifier, offset: string): string =
if node.sym != sym or node.children.len() != 2:
return
let
pname = getNodeValIf(node.children[1], identifier)
ptyp = getNodeValIf(node.children[0], "primitive_type")
ttyp = getNodeValIf(node.children[0], "type_identifier")
if pname.len() == 0:
return
elif ptyp.nBl:
result = &"{offset}{pname.getIdentifier()}: {ptyp.getType()}"
elif ttyp.nBl:
result = &"{offset}{pname.getIdentifier()}: {ttyp}"
elif node.children[0].sym in ["struct_specifier", "enum_specifier"] and node.children[0].children.len() == 1:
let styp = getNodeValIf(node.children[0].children[0], "type_identifier")
if styp.nBl:
result = &"{offset}{pname.getIdentifier()}: {styp}"
else:
return
proc structSpecifier(node: Ast, name = "") =
var stmt: string
if node.children.len() == 1 and name notin gTypes:
case node.children[0].sym:
of "type_identifier":
let typ = getNodeValIf(node.children[0], "type_identifier")
if typ.nBl:
# typedef struct X Y
gTypes.add(name)
gTypeStr &= &" {name}* = {typ} #1 structSpecifier()\n"
of "field_declaration_list":
# typedef struct { fields } X
stmt = &" {name}* {{.importc: \"{name}\", header: {gCurrentHeader}, bycopy.}} = object #2 structSpecifier()\n"
for field in node.children[0].children:
let ts = typeScan(field, "field_declaration", "field_identifier", " ")
if ts.len() == 0:
return
stmt &= ts & "\n"
gTypes.add(name)
gTypeStr &= stmt
elif name.len() == 0 and node.children.len() == 2 and node.children[1].sym == "field_declaration_list":
let ename = getNodeValIf(node.children[0], "type_identifier")
if ename.nBl and ename notin gTypes:
# struct X { fields }
stmt &= &" {ename}* {{.importc: \"struct {ename}\", header: {gCurrentHeader}, bycopy.}} = object #3 structSpecifier()\n"
for field in node.children[1].children:
let ts = typeScan(field, "field_declaration", "field_identifier", " ")
if ts.len() == 0:
return
stmt &= ts & "\n"
gTypes.add(name)
gTypeStr &= stmt
proc enumSpecifier(node: Ast, name = "") =
var
ename: string
elid: int
stmt: string
if node.children.len() == 1 and node.children[0].sym == "enumerator_list":
# typedef enum { fields } X
ename = name
elid = 0
stmt = &" {name}* = enum #1 enumSpecifier()\n"
elif name.len() == 0 and node.children.len() == 2 and node.children[1].sym == "enumerator_list":
ename = getNodeValIf(node.children[0], "type_identifier")
elid = 1
if ename.nBl:
# enum X { fields }
stmt = &" {ename}* = enum #2 enumSpecifier()\n"
else:
return
for field in node.children[elid].children:
if field.sym == "enumerator":
let fname = getNodeValIf(field.children[0], "identifier")
if field.children.len() == 1:
stmt &= &" {fname}\n"
elif field.children.len() == 2 and field.children[1].sym == "number_literal":
let num = getNodeValIf(field.children[1], "number_literal")
stmt &= &" {fname} = {num}\n"
else:
return
if ename notin gTypes:
gTypes.add(name)
gTypeStr &= stmt
proc typeDefinition(node: Ast) =
if node.children.len() == 2:
let
name = getNodeValIf(node.children[1], "type_identifier")
ptyp = getNodeValIf(node.children[0], "primitive_type")
ttyp = getNodeValIf(node.children[0], "type_identifier")
if name.nBl and name notin gTypes:
if ptyp.nBl:
# typedef int X
gTypes.add(name)
gTypeStr &= &" {name}* = {ptyp.getType()} #1 typeDefinition()\n"
elif ttyp.nBl:
# typedef X Y
gTypes.add(name)
gTypeStr &= &" {name}* = {ttyp} #2 typeDefinition()\n"
else:
case node.children[0].sym:
of "struct_specifier":
structSpecifier(node.children[0], name)
of "enum_specifier":
enumSpecifier(node.children[0], name)
proc functionDeclarator(node: Ast, typ: string) =
if node.children.len() == 2:
let
name = getNodeValIf(node.children[0], "identifier")
if name.nBl and name notin gProcs and node.children[1].sym == "parameter_list":
# typ function(typ param1, ...)
var stmt = &"# functionDeclarator()\nproc {name}*("
for i in 0 .. node.children[1].children.len()-1:
let ts = typeScan(node.children[1].children[i], "parameter_declaration", "identifier", "")
if ts.len() == 0:
return
stmt &= ts
if i != node.children[1].children.len()-1:
stmt &= ", "
if typ != "void":
stmt &= &"): {typ.getType()} "
else:
stmt &= ") "
stmt &= &"{{.importc: \"{name}\", header: {gCurrentHeader}.}}\n"
gProcs.add(name)
gProcStr &= stmt
proc declaration*(node: Ast) =
if node.children.len() == 2 and node.children[1].sym == "function_declarator":
let
ptyp = getNodeValIf(node.children[0], "primitive_type")
ttyp = getNodeValIf(node.children[0], "type_identifier")
if ptyp.nBl:
functionDeclarator(node.children[1], ptyp.getType())
elif ttyp.nBl:
functionDeclarator(node.children[1], ttyp)
elif node.children[0].sym == "struct_specifier" and node.children[0].children.len() == 1:
let styp = getNodeValIf(node.children[0].children[0], "type_identifier")
if styp.nBl:
functionDeclarator(node.children[1], styp)
proc genNimAst*(node: Ast) =
case node.sym:
of "ERROR":
let (line, col) = getLineCol(node)
echo &"Potentially invalid syntax at line {line} column {col}"
of "preproc_def":
preprocDef(node)
of "type_definition":
typeDefinition(node)
of "declaration":
declaration(node)
of "struct_specifier":
if node.parent.sym notin ["type_definition", "declaration"]:
structSpecifier(node)
of "enum_specifier":
if node.parent.sym notin ["type_definition", "declaration"]:
enumSpecifier(node)
for child in node.children:
genNimAst(child)

149
nimterop/cimport.nim Normal file
View file

@ -0,0 +1,149 @@
import macros, os, strformat, strutils
import ast, getters, globals, lisp
proc search(path: string): string =
result = joinPath(getProjectPath(), path).replace("\\", "/")
if not fileExists(result) and not dirExists(result):
result = path
if not fileExists(result) and not dirExists(result):
var found = false
for inc in gIncludeDirs:
result = inc & "/" & path
if fileExists(result) or dirExists(result):
found = true
break
if not found:
echo "File or directory not found: " & path
quit(1)
macro cDebug*(): untyped =
gDebug = true
macro cDefine*(name: static[string], val: static[string] = ""): untyped =
result = newNimNode(nnkStmtList)
var str = "-D" & name
if val.nBl:
str &= "=\"" & val & "\""
result.add(quote do:
{.passC: `str`.}
)
if gDebug:
echo result.repr
macro cIncludeDir*(dir: static[string]): untyped =
result = newNimNode(nnkStmtList)
let fullpath = search(dir)
gIncludeDirs.add(fullpath)
let str = "-I\"" & fullpath & "\""
result.add(quote do:
{.passC: `str`.}
)
if gDebug:
echo result.repr
macro cIncludeC*(): untyped =
result = newNimNode(nnkStmtList)
var
inc = false
for line in getGccPaths().splitLines():
if "#include <...> search starts here" in line:
inc = true
continue
elif "End of search list" in line:
break
if inc:
if gDebug:
echo "Including " & line.strip()
gIncludeDirs.add(line.strip())
macro cCompile*(path: static[string]): untyped =
result = newNimNode(nnkStmtList)
var
stmt = ""
flags = ""
proc fcompile(file: string): string =
let fn = file.splitFile().name
var
ufn = fn
uniq = 1
while ufn in gCompile:
ufn = fn & $uniq
uniq += 1
gCompile.add(ufn)
if fn == ufn:
return "{.compile: \"$#\".}" % file.replace("\\", "/")
else:
return "{.compile: (\"../$#\", \"$#.o\").}" % [file.replace("\\", "/"), ufn]
proc dcompile(dir: string) =
for f in walkFiles(dir):
stmt &= fcompile(f) & "\n"
if path.contains("*") or path.contains("?"):
dcompile(path)
else:
let fpath = search(path)
if fileExists(fpath):
stmt &= fcompile(fpath) & "\n"
elif dirExists(fpath):
if flags.contains("cpp"):
for i in @["*.C", "*.cpp", "*.c++", "*.cc", "*.cxx"]:
dcompile(fpath / i)
else:
dcompile(fpath / "*.c")
result.add stmt.parseStmt()
if gDebug:
echo result.repr
macro cImport*(filename: static[string]): untyped =
result = newNimNode(nnkStmtList)
result.add addReorder()
let
fullpath = search(filename)
root = parseLisp(fullpath)
echo "Importing " & fullpath
gCode = staticRead(fullpath)
gConstStr = ""
gTypeStr = ""
addHeader(fullpath)
genNimAst(root)
if gConstStr.nBl:
if gDebug:
echo "const\n" & gConstStr
result.add parseStmt(
"const\n" & gConstStr
)
if gTypeStr.nBl:
if gDebug:
echo "type\n" & gTypeStr
result.add parseStmt(
"type\n" & gTypeStr
)
if gProcStr.nBl:
if gDebug:
echo gProcStr
result.add gProcStr.parseStmt()
if gDebug:
echo result.repr

39
nimterop/getters.nim Normal file
View file

@ -0,0 +1,39 @@
import macros, strutils
import regex
import globals
proc getIdentifier*(str: string): string =
result = str.strip(chars={'_'})
proc getType*(str: string): string =
result = str.strip(chars={'_'}).replace(re"([u]?int[\d]+)_t", "$1")
proc getLit*(str: string): string =
if str.contains(re"^[\-]?[\d]+$") or
str.contains(re"^[\-]?[\d]*\.[\d]+$") or
str.contains(re"^0x[\d]+$"):
return str
proc getNodeValIf*(node: Ast, esym: string): string =
if esym != node.sym:
return
return gCode[node.start .. node.stop-1].strip()
proc getGccPaths*(mode = "c"): string =
let
nul = when defined(Windows): "nul" else: "/dev/null"
return staticExec("gcc -Wp,-v -x" & mode & " " & nul)
proc getLineCol*(node: Ast): tuple[line, col: int] =
result.line = 1
result.col = 1
echo gCode[node.start .. node.stop-1]
for i in 0 .. node.start-1:
if gCode[i] == '\n':
result.col = 0
result.line += 1
result.col += 1

28
nimterop/globals.nim Normal file
View file

@ -0,0 +1,28 @@
import macros
type
Ast* = object
sym*: string
start*, stop*: int
parent*: ptr Ast
children*: seq[Ast]
var
gDefines* {.compiletime.}: seq[string]
gCompile* {.compiletime.}: seq[string]
gConsts* {.compiletime.}: seq[string]
gHeaders* {.compiletime.}: seq[string]
gIncludeDirs* {.compiletime.}: seq[string]
gProcs* {.compiletime.}: seq[string]
gTypes* {.compiletime.}: seq[string]
gCode* {.compiletime.}: string
gConstStr* {.compiletime.}: string
gCurrentHeader* {.compiletime.}: string
gDebug* {.compiletime.}: bool
gReorder* {.compiletime.}: bool
gProcStr* {.compiletime.}: string
gTypeStr* {.compiletime.}: string
template nBl*(s: untyped): untyped =
(s.len() != 0)

69
nimterop/lisp.nim Normal file
View file

@ -0,0 +1,69 @@
import strutils
import regex
import globals
var
gTokens {.compiletime.}: seq[string]
idx {.compiletime.} = 0
proc tokenize(fullpath: string) =
var collect = ""
gTokens = @[]
idx = 0
for i in staticExec("toast " & fullpath):
case i:
of ' ', '\n', '\r', ')':
if collect.nBl:
gTokens.add(collect)
collect = ""
if i == ')':
gTokens.add(")")
of '(':
gTokens.add("(")
else:
collect &= $i
proc readFromTokens(): Ast =
if idx == gTokens.len():
echo "Bad AST"
quit(1)
if gTokens[idx] == "(":
if gTokens.len() - idx < 2:
echo "Corrupt AST"
quit(1)
result.sym = gTokens[idx+1]
result.start = gTokens[idx+2].parseInt()
result.stop = gTokens[idx+3].parseInt()
idx += 4
result.children = @[]
while gTokens[idx] != ")":
var res = readFromTokens()
if res.sym.nBl:
res.parent = addr result
result.children.add(res)
idx += 1
return
elif gTokens[idx] == ")":
echo "Poor AST"
quit(1)
idx += 1
proc printAst*(node: Ast, offset=""): string =
result = offset & "(" & node.sym & " " & $node.start & " " & $node.stop
if node.children.len() != 0:
result &= "\n"
for child in node.children:
result &= printAst(child, offset & " ")
result &= offset & ")\n"
else:
result &= ")\n"
proc parseLisp*(fullpath: string): Ast =
tokenize(fullpath)
return readFromTokens()

33
tests/include/test.c Normal file
View file

@ -0,0 +1,33 @@
#include "test.h"
int test_call_int() {
return 5;
}
struct STRUCT1 test_call_int_param(int param1) {
struct STRUCT1 s;
s.field1 = param1;
return s;
}
STRUCT2 test_call_int_param2(int param1, STRUCT2 param2) {
STRUCT2 s;
s.field1 = param1 + param2.field1;
return s;
}
STRUCT2 test_call_int_param3(int param1, struct STRUCT1 param2) {
STRUCT2 s;
s.field1 = param1 + param2.field1;
return s;
}
ENUM2 test_call_int_param4(enum ENUM param1) {
return enum4;
}

36
tests/include/test.h Normal file
View file

@ -0,0 +1,36 @@
#include <stdint.h>
#define TEST_INT 512
#define TEST_FLOAT 5.12
#define TEST_HEX 0x512
typedef uint8_t PRIMTYPE;
typedef PRIMTYPE CUSTTYPE;
struct STRUCT1 {
int field1;
};
typedef struct STRUCT1 STRUCT2;
typedef struct {
int field1;
} STRUCT3;
enum ENUM {
enum1,
enum2,
enum3
};
typedef enum {
enum4 = 3,
enum5,
enum6
} ENUM2;
int test_call_int();
struct STRUCT1 test_call_int_param(int param1);
STRUCT2 test_call_int_param2(int param1, STRUCT2 param2);
STRUCT2 test_call_int_param3(int param1, struct STRUCT1 param2);
ENUM2 test_call_int_param4(enum ENUM param1);

39
tests/tnimterop.nim Normal file
View file

@ -0,0 +1,39 @@
import nimterop/cimport
import macros
cDebug()
cIncludeDir("include")
cCompile("test.c")
cImport("test.h")
assert TEST_INT == 512
assert TEST_FLOAT == 5.12
assert TEST_HEX == 0x512
var
pt: PRIMTYPE
ct: CUSTTYPE
s: STRUCT1
s2: STRUCT2
s3: STRUCT3
e: ENUM
e2: ENUM2 = enum5
pt = 3
ct = 4
s.field1 = 5
s2.field1 = 6
s3.field1 = 7
e = enum1
e2 = enum4
assert test_call_int() == 5
assert test_call_int_param(5).field1 == 5
assert test_call_int_param2(5, s2).field1 == 11
assert test_call_int_param3(5, s).field1 == 10
assert test_call_int_param4(e) == e2

99
toast.nim Normal file
View file

@ -0,0 +1,99 @@
import os, strutils
import regex
import treesitter/[runtime, c, cpp]
const HELP = """
> toast header.h
"""
proc printLisp(root: TSNode, data: var string) =
var
node = root
nextnode: TSNode
depth = 0
while true:
if not node.tsNodeIsNull():
stdout.write spaces(depth) & "(" & $node.tsNodeType() & " " & $node.tsNodeStartByte() & " " & $node.tsNodeEndByte()
if node.tsNodeNamedChildCount() != 0:
echo ""
nextnode = node.tsNodeNamedChild(0)
depth += 1
else:
echo ")"
nextnode = node.tsNodeNextNamedSibling()
if nextnode.tsNodeIsNull():
while true:
node = node.tsNodeParent()
depth -= 1
echo spaces(depth) & ")"
if node == root:
break
if not node.tsNodeNextNamedSibling().tsNodeIsNull():
node = node.tsNodeNextNamedSibling()
break
else:
node = nextnode
if node == root:
break
proc process(path: string, mode="") =
if not existsFile(path):
echo "Invalid path " & path
return
var
parser = tsParserNew()
ext = path.splitFile().ext
pmode = ""
data = readFile(path)
defer:
parser.tsParserDelete()
if mode.len() != 0:
pmode = mode
elif ext in [".h", ".c"]:
pmode = "c"
elif ext in [".hxx", ".hpp", ".hh", ".H", ".h++", ".cpp", ".cxx", ".cc", ".C", ".c++"]:
pmode = "cpp"
if "cplusplus" in data or "extern \"C\"" in data:
pmode = "cpp"
if pmode == "c":
if not parser.tsParserSetLanguage(treeSitterC()):
echo "Failed to load C parser"
quit()
elif pmode == "cpp":
if not parser.tsParserSetLanguage(treeSitterCpp()):
echo "Failed to load C++ parser"
quit()
else:
echo "Invalid parser " & mode
quit()
var
tree = parser.tsParserParseString(nil, data.cstring, data.len().uint32)
root = tree.tsTreeRootNode()
defer:
tree.tsTreeDelete()
printLisp(root, data)
proc parseCli() =
let params = commandLineParams()
for param in params:
if param in ["-h", "--help", "-?", "/?", "/h"]:
echo HELP
quit()
else:
process(param)
parseCli()