Remove legacy backend

This commit is contained in:
Ganesh Viswanathan 2020-06-24 10:37:38 -05:00
commit d126e9944f
17 changed files with 33 additions and 1295 deletions

View file

@ -19,6 +19,8 @@ https://github.com/nimterop/nimterop/compare/v0.5.9...v0.6.0
### Breaking changes
- The legacy algorithm has been removed as promised. `ast2` is now the default and wrappers no longer need to explicitly specify `-f:ast2` in order to use it.
- All shared libraries installed by `getHeader()` will now get copied into the `libdir` parameter specified. If left blank, `libdir` will default to the directory where the executable binary gets created (outdir). While this is not really a breaking change, it is a change in behavior compared to older versions of nimterop. Note that `Std` libraries are not copied over. [#154](i154)
- `git.nim` has been removed. This module was an artifact from the early days and was renamed to `build.nim` back in v0.2.0.

View file

@ -177,13 +177,13 @@ Now that this is understood, a user might want any combination of the above in t
- By default, generated wrappers will include the `{.header, importc.}` pragmas for types and procs. This can be disabled with the `--noHeader | -H` flag to `toast` or `flags = "-H"` param to `cImport()` which will remove `{.header}` for both and `{.importc.}` for types only.
- By default, generated wrappers will assume that the user will link the library implementation themselves. The `--dynlib | -l` flag to `toast` or `dynlib = "headerLPath"` param to `cImport()` will configure the wrapper to generate `{.dynlib.}` pragmas for procs.
This results in four cases:
This results in four supported cases:
1. Default: `{.header, importc.}` for both types and procs
2. With `--noHeader`, types will be pure Nim and procs will be just `{.importc.}`
3. With `--dynlib`, types will still be `{.header, importc.}` but procs will be `{.dynlib, importc.}`
4. With `--dynlib` and `--noHeader`, types will be pure Nim, procs will be `{.dynlib, importc.}`
While `ast2` supports all these modes, the legacy backend does not support the third mixed case and will infer `--noHeader` when `--dynlib` is specified (case 4). Creation of a standalone wrapper (case 4) which does not require the header or library at compile time will require an explicit `--noHeader` and `--dynlib` for `ast2`.
Creation of a standalone wrapper (case 4) which does not require the header or library at compile time will require an explicit `--noHeader` and `--dynlib`.
More documentation on on these pragmas can be found in the Nim manual:
- [{.importc.}](https://nim-lang.org/docs/manual.html#foreign-function-interface-importc-pragma)
@ -218,7 +218,7 @@ Options:
-d, --debug bool false enable debug output
-D=, --defines= strings {} definitions to pass to preprocessor
-l=, --dynlib= string "" {.dynlib.} pragma to import symbols - Nim const string or file path
-f=, --feature= Features ast1 flags to enable experimental features
-f=, --feature= Features {} flags to enable experimental features
-I=, --includeDirs= strings {} include directory to pass to preprocessor
-m=, --mode= string "" language parser: c or cpp
--nim= string "nim" use a particular Nim executable
@ -226,12 +226,11 @@ Options:
-H, --noHeader bool false skip {.header.} pragma in wrapper
-o=, --output= string "" file to output content - default: stdout
-a, --past bool false print AST output
-g, --pgrammar bool false print grammar
--pluginSourcePath= string "" nim file to build and load as a plugin
-n, --pnim bool false print Nim output
-E=, --prefix= strings {} strip prefix from identifiers
-p, --preprocess bool false run preprocessor on header
-r, --recurse bool false process #include files, implies --preprocess
-r, --recurse bool false process #include files - implies --preprocess
-G=, --replace= strings {} replace X with Y in identifiers, X1=Y1,X2=Y2, @X for regex
-s, --stub bool false stub out undefined type references as objects
-F=, --suffix= strings {} strip suffix from identifiers

View file

@ -1,6 +1,6 @@
# Package
version = "0.5.9"
version = "0.6.0"
author = "genotrance"
description = "C/C++ interop for Nim"
license = "MIT"
@ -55,15 +55,12 @@ task test, "Test":
execTest "tests/tast2.nim", "-d:NOHEADER"
execTest "tests/tnimterop_c.nim"
execTest "tests/tnimterop_c.nim", "-d:FLAGS=\"-f:ast2\""
execTest "tests/tnimterop_c.nim", "-d:FLAGS=\"-f:ast2 -H\""
execTest "tests/tnimterop_c.nim", "-d:FLAGS=\"-H\""
execCmd "nim cpp --hints:off -f -r tests/tnimterop_cpp.nim"
execCmd "./nimterop/toast tests/toast.cfg tests/include/toast.h"
execCmd "./nimterop/toast tests/toast.cfg -f:ast2 tests/include/toast.h"
execTest "tests/tpcre.nim"
execTest "tests/tpcre.nim", "-d:FLAGS=\"-f:ast2\""
when defined(Linux):
execTest "tests/rsa.nim"
@ -72,12 +69,10 @@ task test, "Test":
# Platform specific tests
when defined(Windows):
execTest "tests/tmath.nim"
execTest "tests/tmath.nim", "-d:FLAGS=\"-f:ast2\""
execTest "tests/tmath.nim", "-d:FLAGS=\"-f:ast2 -H\""
execTest "tests/tmath.nim", "-d:FLAGS=\"-H\""
if defined(OSX) or defined(Windows) or not existsEnv("TRAVIS"):
execTest "tests/tsoloud.nim"
execTest "tests/tsoloud.nim", "-d:FLAGS=\"-f:ast2\""
execTest "tests/tsoloud.nim", "-d:FLAGS=\"-f:ast2 -H\""
execTest "tests/tsoloud.nim", "-d:FLAGS=\"-H\""
# getHeader tests
withDir("tests"):

View file

@ -14,12 +14,6 @@ var
gNimExe* = ""
# Misc helpers
proc echoDebug(str: string) =
let str = "\n# " & str.strip().replace("\n", "\n# ")
when nimvm:
if gDebugCT: echo str
else:
if gDebug: echo str
proc sanitizePath*(path: string, noQuote = false, sep = $DirSep): string =
result = path.multiReplace([("\\\\", sep), ("\\", sep), ("/", sep)])
@ -34,7 +28,7 @@ proc getCurrentNimCompiler*(): string =
else:
result = gNimExe
template fixOutDir() {.dirty.} =
template fixOutDir() {.dirty, used.} =
let
outdir = if outdir.isAbsolute(): outdir else: getProjectDir() / outdir

View file

@ -178,7 +178,6 @@ proc getLocalPath(header, outdir: string): string =
proc buildLibrary(lname, outdir, conFlags, cmakeFlags, makeFlags: string, buildTypes: openArray[BuildType]): string =
var
lpath = findFile(lname, outdir, regex = true)
makeFlagsProc = &"-j {getNumProcs()} {makeFlags}"
makePath = outdir
if lpath.len != 0:
@ -200,7 +199,7 @@ proc buildLibrary(lname, outdir, conFlags, cmakeFlags, makeFlags: string, buildT
let libraryExists = findFile(lname, buildStatus.buildPath, regex = true).len > 0
if not libraryExists and fileExists(buildStatus.buildPath / "Makefile"):
make(buildStatus.buildPath, lname, makeFlagsProc, regex = true)
make(buildStatus.buildPath, lname, makeFlags, regex = true)
buildStatus.built = true
let error = if buildStatus.error.len > 0: buildStatus.error else: "No build files found in " & outdir

View file

@ -455,7 +455,8 @@ proc linkLibs*(names: openArray[string], staticLink = true): string =
for res in resSet:
result &= " " & res
proc getNumProcs(): string =
proc getNumProcs*(): string =
## Get number of processors
when defined(Windows):
getEnv("NUMBER_OF_PROCESSORS").strip()
elif defined(linux):

View file

@ -9,6 +9,13 @@ type
buildPath: string
error: string
proc echoDebug(str: string) =
let str = "\n# " & str.strip().replace("\n", "\n# ")
when nimvm:
if gDebugCT: echo str
else:
if gDebug: echo str
proc configure*(path, check: string, flags = "") =
## Run the GNU `configure` command to generate all Makefiles or other
## build scripts in the specified path
@ -193,7 +200,7 @@ proc make*(path, check: string, flags = "", regex = false) =
cpFile(cmd, cmd.replace("mingw32-make", "make"))
doAssert cmd.len != 0, "Make not found"
cmd = &"cd {path.sanitizePath} && make"
cmd = &"cd {path.sanitizePath} && make -j {getNumProcs()}"
if flags.len != 0:
cmd &= &" {flags}"

View file

@ -3,8 +3,6 @@ import tables
when defined(TOAST):
import sets, sequtils, strutils
import regex
import "."/plugin
import compiler/[ast, idents, modulegraphs, options]
@ -13,7 +11,7 @@ when defined(TOAST):
type
Feature* = enum
ast1, ast2
ast2
State* = ref object
# Command line arguments to toast - some forwarded from cimport.nim
@ -72,12 +70,6 @@ type
# Controls whether or not the current expression
# should validate idents against currently defined idents
skipIdentValidation*: bool
# Legacy AST fields, remove when ast2 becomes default
constStr*, enumStr*, procStr*, typeStr*: string
commentStr*, debugStr*, skipStr*: string
data*: seq[tuple[name, val: string]]
nodeBranch*: seq[string]
else:
# cimport.nim specific
compile*: seq[string] # `cCompile()` list of files already processed
@ -114,23 +106,6 @@ when defined(TOAST):
].concat(toSeq(gExpressions.items))
type
Kind* = enum
exactlyOne
oneOrMore # +
zeroOrMore # *
zeroOrOne # ?
orWithNext # !
Ast* = object
name*: string
kind*: Kind
recursive*: bool
children*: seq[ref Ast]
tonim*: proc (ast: ref Ast, node: TSNode, gState: State)
regex*: Regex
AstTable* {.used.} = TableRef[string, seq[ref Ast]]
Status* = enum
success, unknown, error

View file

@ -4,9 +4,9 @@ import "."/treesitter/[api, c, cpp]
import "."/[build, globals]
import "."/toastlib/[ast, ast2, getters, grammar, tshelp]
import "."/toastlib/[ast2, getters, tshelp]
proc process(gState: State, path: string, astTable: AstTable) =
proc process(gState: State, path: string) =
doAssert existsFile(path), &"Invalid path {path}"
if gState.mode.Bl:
@ -21,10 +21,7 @@ proc process(gState: State, path: string, astTable: AstTable) =
if gState.past:
gecho gState.printLisp(root)
elif gState.pnim:
if Feature.ast2 in gState.feature:
ast2.parseNim(gState, path, root)
elif Feature.ast1 in gState.feature:
ast.parseNim(gState, path, root, astTable)
parseNim(gState, path, root)
elif gState.preprocess:
gecho gState.code
@ -43,7 +40,6 @@ proc main(
noHeader = false,
output = "",
past = false,
pgrammar = false,
pluginSourcePath: string = "",
pnim = false,
prefix: seq[string] = @[],
@ -84,10 +80,6 @@ proc main(
build.gDebug = gState.debug
build.gNimExe = gState.nim
# Default `ast` mode
if gState.feature.Bl:
gState.feature.add Feature.ast1
# Split some arguments with ,
gState.symOverride = gState.symOverride.getSplitComma()
gState.prefix = gState.prefix.getSplitComma()
@ -137,31 +129,14 @@ proc main(
if gState.debug:
echo &"# Writing output to {outputFile}\n"
# Process grammar into AST
let
astTable =
if Feature.ast1 in gState.feature:
parseGrammar()
else:
nil
if pgrammar:
if Feature.ast1 in gState.feature:
# Print AST of grammar
gState.printGrammar(astTable)
elif source.nBl:
if source.nBl:
# Print source after preprocess or Nim output
if gState.pnim:
gState.initNim()
for src in source:
gState.process(src.expandSymlinkAbs(), astTable)
gState.process(src.expandSymlinkAbs())
if gState.pnim:
if Feature.ast2 in gState.feature:
ast2.printNim(gState)
elif Feature.ast1 in gState.feature:
ast.printNim(gState)
gecho """{.hint: "The legacy wrapper generation algorithm is deprecated and will be removed in the next release of Nimterop.".}"""
gecho """{.hint: "Refer to CHANGES.md for details on migrating to the new backend.".}"""
printNim(gState)
# Close outputFile
if outputFile.len != 0:
@ -249,7 +224,6 @@ when isMainModule:
"noHeader": "skip {.header.} pragma in wrapper",
"output": "file to output content - default: stdout",
"past": "print AST output",
"pgrammar": "print grammar",
"pluginSourcePath": "nim file to build and load as a plugin",
"pnim": "print Nim output",
"prefix": "strip prefix from identifiers",
@ -273,7 +247,6 @@ when isMainModule:
"noHeader": 'H',
"output": 'o',
"past": 'a',
"pgrammar": 'g',
"pnim": 'n',
"prefix": 'E',
"preprocess": 'p',

View file

@ -1,253 +0,0 @@
import hashes, macros, os, sets, strformat, strutils, tables
import regex
import ".."/[globals, treesitter/api]
import "."/[getters, tshelp]
proc getHeaderPragma*(gState: State): string =
result =
if not gState.noHeader and gState.dynlib.Bl:
&", header: {gState.currentHeader}"
else:
""
proc getDynlib*(gState: State): string =
result =
if gState.dynlib.nBl:
&", dynlib: {gState.dynlib}"
else:
""
proc getImportC*(gState: State, origName, nimName: string): string =
if nimName != origName:
result = &"importc: \"{origName}\"{gState.getHeaderPragma()}"
else:
result = gState.impShort
proc getPragma*(gState: State, pragmas: varargs[string]): string =
result = ""
for pragma in pragmas.items():
if pragma.nBl:
result &= pragma & ", "
if result.nBl:
result = " {." & result[0 .. ^3] & ".}"
result = result.replace(gState.impShort & ", cdecl", gState.impShort & "C")
let
dy = gState.getDynlib()
if ", cdecl" in result and dy.nBl:
result = result.replace(".}", dy & ".}")
proc saveNodeData(node: TSNode, gState: State): bool =
let name = $node.tsNodeType()
# Atoms are nodes whose values are to be saved
if name in gAtoms:
let
pname = node.getPxName(1)
ppname = node.getPxName(2)
pppname = node.getPxName(3)
ppppname = node.getPxName(4)
var
val = gState.getNodeVal(node)
# Skip since value already obtained from parent atom
if name == "primitive_type" and pname == "sized_type_specifier":
return true
# Skip since value already obtained from parent expression
if name in ["number_literal", "identifier"] and pname in gExpressions:
return true
# Add reference point in saved data for bitfield_clause
if name in ["number_literal"] and pname == "bitfield_clause":
gState.data.add(("bitfield_clause", val))
return true
# Process value as a type
if name in ["primitive_type", "sized_type_specifier"]:
val = val.getType()
if node.tsNodePrevNamedSibling().tsNodeIsNull():
if pname == "pointer_declarator":
if ppname notin ["function_declarator", "array_declarator"]:
gState.data.add(("pointer_declarator", ""))
elif ppname == "array_declarator":
gState.data.add(("array_pointer_declarator", ""))
# Double pointer
if ppname == "pointer_declarator":
gState.data.add(("pointer_declarator", ""))
elif pname in ["function_declarator", "array_declarator"]:
if ppname == "pointer_declarator":
gState.data.add(("pointer_declarator", ""))
if pppname == "pointer_declarator":
gState.data.add(("pointer_declarator", ""))
gState.data.add((name, val))
if pname == "pointer_declarator" and
ppname == "function_declarator":
if name == "field_identifier":
if pppname == "pointer_declarator":
gState.data.insert(("pointer_declarator", ""), gState.data.len-1)
if ppppname == "pointer_declarator":
gState.data.insert(("pointer_declarator", ""), gState.data.len-1)
gState.data.add(("function_declarator", ""))
elif name == "identifier":
gState.data.add(("pointer_declarator", ""))
# Save node value for a top-level expression
elif name in gExpressions and name != "escape_sequence":
if $node.tsNodeParent.tsNodeType() notin gExpressions:
gState.data.add((name, gState.getNodeVal(node)))
elif name in ["abstract_pointer_declarator", "enumerator", "field_declaration", "function_declarator"]:
gState.data.add((name.replace("abstract_", ""), ""))
return true
proc searchAstForNode(ast: ref Ast, node: TSNode, gState: State): bool =
let
childNames = node.getTSNodeNamedChildNames().join()
if ast.isNil:
return
if gState.debug:
gState.nodeBranch.add $node.tsNodeType()
gecho "#" & spaces(gState.nodeBranch.len * 2) & gState.nodeBranch[^1]
if ast.children.nBl:
if childNames.contains(ast.regex) or
(childNames.Bl and ast.recursive):
if node.getTSNodeNamedChildCountSansComments() != 0:
var flag = true
for i in 0 .. node.tsNodeNamedChildCount()-1:
if $node.tsNodeNamedChild(i).tsNodeType() != "comment":
let
nodeChild = node.tsNodeNamedChild(i)
astChild =
if not ast.recursive:
ast.getAstChildByName($nodeChild.tsNodeType())
else:
ast
if not searchAstForNode(astChild, nodeChild, gState):
flag = false
break
if flag:
result = node.saveNodeData(gState)
else:
result = node.saveNodeData(gState)
else:
if gState.debug:
gecho "#" & spaces(gState.nodeBranch.len * 2) & &" {ast.getRegexForAstChildren()} !=~ {childNames}"
elif node.getTSNodeNamedChildCountSansComments() == 0:
result = node.saveNodeData(gState)
if gState.debug:
discard gState.nodeBranch.pop()
if gState.nodeBranch.Bl:
gecho ""
proc searchAst(root: TSNode, astTable: AstTable, gState: State) =
var
node = root
nextnode: TSNode
depth = 0
while true:
if not node.tsNodeIsNull() and depth > -1:
let
name = $node.tsNodeType()
if name in astTable:
for ast in astTable[name]:
if gState.debug:
gecho "\n# " & gState.getNodeVal(node).replace("\n", "\n# ") & "\n"
if searchAstForNode(ast, node, gState):
ast.tonim(ast, node, gState)
if gState.debug:
gState.debugStr &= "\n# " & gState.data.join("\n# ") & "\n"
break
gState.data = @[]
else:
break
if $node.tsNodeType() notin astTable and node.tsNodeNamedChildCount() != 0:
nextnode = node.tsNodeNamedChild(0)
depth += 1
else:
nextnode = node.tsNodeNextNamedSibling()
if nextnode.tsNodeIsNull():
while true:
node = node.tsNodeParent()
depth -= 1
if depth == -1:
break
if node == root:
break
if not node.tsNodeNextNamedSibling().tsNodeIsNull():
node = node.tsNodeNextNamedSibling()
break
else:
node = nextnode
if node == root:
break
proc parseNim*(gState: State, fullpath: string, root: TSNode, astTable: AstTable) =
# Generate Nim from tree-sitter AST root node
var
fp = fullpath.replace("\\", "/")
gState.currentHeader = getCurrentHeader(fullpath)
gState.impShort = gState.currentHeader.replace("header", "imp")
gState.sourceFile = fullpath
if not gState.noHeader and gState.dynlib.Bl:
gState.constStr &= &"\n {gState.currentHeader} {{.used.}} = \"{fp}\""
root.searchAst(astTable, gState)
proc printNim*(gState: State) =
# Print Nim generated by parseNim()
if gState.enumStr.nBl:
gecho &"{gState.enumStr}\n"
gState.constStr = gState.getOverrideFinal(nskConst) & gState.constStr
if gState.constStr.nBl:
gecho &"const{gState.constStr}\n"
gecho &"""
{{.pragma: {gState.impShort}, importc{gState.getHeaderPragma()}.}}
{{.pragma: {gState.impShort}C, {gState.impShort}, cdecl{gState.getDynlib()}.}}
"""
gState.typeStr = gState.getOverrideFinal(nskType) & gState.typeStr
if gState.typeStr.nBl:
gecho &"type{gState.typeStr}\n"
gState.procStr = gState.getOverrideFinal(nskProc) & gState.procStr
if gState.procStr.nBl:
gecho &"{gState.procStr}\n"
gecho "{.pop.}"
if gState.debug:
if gState.debugStr.nBl:
gecho gState.debugStr
if gState.skipStr.nBl:
let
hash = gState.skipStr.hash().abs()
sname = getTempDir() / &"nimterop_{$hash}.h"
gecho &"# Writing skipped definitions to {sname}\n"
writeFile(sname, gState.skipStr)

View file

@ -304,131 +304,6 @@ proc getPreprocessor*(gState: State, fullpath: string) =
rdata.add line
gState.code = rdata.join("\n")
converter toString*(kind: Kind): string =
return case kind:
of exactlyOne:
""
of oneOrMore:
"+"
of zeroOrMore:
"*"
of zeroOrOne:
"?"
of orWithNext:
"!"
converter toKind*(kind: string): Kind =
return case kind:
of "+":
oneOrMore
of "*":
zeroOrMore
of "?":
zeroOrOne
of "!":
orWithNext
else:
exactlyOne
proc getNameKind*(name: string): tuple[name: string, kind: Kind, recursive: bool] =
if name[0] == '^':
result.recursive = true
result.name = name[1 .. ^1]
else:
result.name = name
result.kind = $name[^1]
if result.kind != exactlyOne:
result.name = result.name[0 .. ^2]
proc getRegexForAstChildren*(ast: ref Ast): string =
result = "^"
for i in 0 .. ast.children.len-1:
let
kind: string = ast.children[i].kind
begin = if result[^1] == '|': "" else: "(?:"
case kind:
of "!":
result &= &"{begin}{ast.children[i].name}|"
else:
result &= &"{begin}{ast.children[i].name}){kind}"
result &= "$"
proc getAstChildByName*(ast: ref Ast, name: string): ref Ast =
for i in 0 .. ast.children.len-1:
if name in ast.children[i].name.split("|"):
return ast.children[i]
if ast.children.len == 1 and ast.children[0].name == ".":
return ast.children[0]
proc getNimExpression*(gState: State, expr: string, name = ""): string =
# Convert C/C++ expression into Nim - cast identifiers to `name` if specified
var
clean = expr.multiReplace([("\n", " "), ("\r", "")])
ident = ""
gen = ""
hex = false
for i in 0 .. clean.len:
if i != clean.len:
if clean[i] in IdentChars:
if clean[i] in Digits and ident.Bl:
# Identifiers cannot start with digits
gen = $clean[i]
elif clean[i] in HexDigits and hex == true:
# Part of a hex number
gen = $clean[i]
elif i > 0 and i < clean.len-1 and clean[i] in ['x', 'X'] and
clean[i-1] == '0' and clean[i+1] in HexDigits:
# Found a hex number
gen = $clean[i]
hex = true
else:
# Part of an identifier
ident &= clean[i]
hex = false
else:
gen = (block:
if (i == 0 or clean[i-1] != '\'') or
(i == clean.len - 1 or clean[i+1] != '\''):
# If unquoted, convert logical ops to Nim
case clean[i]
of '^': " xor "
of '&': " and "
of '|': " or "
of '~': " not "
else: $clean[i]
else:
$clean[i]
)
hex = false
if i == clean.len or gen.nBl:
# Process identifier
if ident.nBl:
# Issue #178
if ident != "_":
ident = gState.getIdentifier(ident, nskConst, name)
if name.nBl and ident in gState.constIdentifiers:
ident = ident & "." & name
result &= ident
ident = ""
result &= gen
gen = ""
# Convert shift ops to Nim
result = result.multiReplace([
("<<", " shl "), (">>", " shr ")
])
proc getComments*(gState: State, strip = false): string =
if not gState.noComments and gState.commentStr.nBl:
result = "\n" & gState.commentStr
if strip:
result = result.replace("\n ", "\n")
gState.commentStr = ""
# Plugin related
proc dll*(path: string): string =

View file

@ -1,768 +0,0 @@
import macros, strformat, strutils, tables
import regex
import ".."/[globals, treesitter/api]
import "."/[ast, getters, lisp, tshelp]
type
Grammar = seq[tuple[grammar: string, call: proc(ast: ref Ast, node: TSNode, gState: State) {.nimcall.}]]
proc getPtrType(str: string): string =
result = case str:
of "ptr cchar":
"cstring"
of "ptr ptr cchar":
"ptr cstring"
of "ptr object":
"pointer"
of "ptr ptr object":
"ptr pointer"
of "ptr FILE":
"File"
else:
str
proc getLit(str: string): string =
# Used to convert #define literals into const
let
str = str.replace(re"/[/*].*?(?:\*/)?$", "").strip()
if str.contains(re"^[\-]?[\d]*[.]?[\d]+$") or # decimal
str.contains(re"^0x[\da-fA-F]+$") or # hexadecimal
str.contains(re"^'[[:ascii:]]'$") or # char
str.contains(re"""^"[[:ascii:]]+"$"""): # char *
return str
proc initGrammar(): Grammar =
# #define X Y
result.add(("""
(preproc_def
(identifier)
(preproc_arg)
)
""",
proc (ast: ref Ast, node: TSNode, gState: State) =
if gState.debug:
gState.debugStr &= "\n# define X Y"
let
name = gState.data[0].val
nname = gState.getIdentifier(name, nskConst)
val = gState.data[1].val.getLit()
if not nname.nBl:
let
override = gState.getOverride(name, nskConst)
if override.nBl:
gState.constStr &= &"{gState.getComments()}\n{override}"
else:
gState.constStr &= &"{gState.getComments()}\n # Const '{name}' skipped"
if gState.debug:
gState.skipStr &= &"\n{gState.getNodeVal(node)}"
elif val.nBl and gState.addNewIdentifer(nname):
gState.constStr &= &"{gState.getComments()}\n {nname}* = {val}"
))
let
typeGrammar = """
(type_qualifier?)
(primitive_type|type_identifier?)
(type_qualifier?)
(sized_type_specifier?
(primitive_type?)
)
(struct_specifier|union_specifier|enum_specifier?
(type_identifier)
)
"""
arrGrammar = &"""
(array_declarator!
(pointer_declarator!
(pointer_declarator!
(type_identifier)
)
(type_identifier)
)
(type_identifier|identifier)
(identifier|number_literal)
)
"""
paramListGrammar = &"""
(parameter_list
(parameter_declaration*
{typeGrammar}
(identifier|type_identifier?)
(pointer_declarator?
(type_qualifier?)
(pointer_declarator!
(type_qualifier?)
{arrGrammar}
(identifier|type_identifier)
)
{arrGrammar}
(identifier|type_identifier)
)
{arrGrammar}
(abstract_pointer_declarator?
(abstract_pointer_declarator?)
)
)
)
"""
funcGrammar = &"""
(function_declarator*
(identifier|type_identifier!)
(pointer_declarator
(pointer_declarator!
(type_identifier)
)
(type_identifier|identifier)
)
{paramListGrammar}
(noexcept|throw_specifier?)
)
"""
template funcParamCommon(fname, pname, ptyp, pptr, pout, count, i, flen: untyped): untyped =
ptyp = gState.getIdentifier(gState.data[i].val, nskType, fname).getType()
pptr = ""
while i+1 < gState.data.len and gState.data[i+1].name == "pointer_declarator":
pptr &= "ptr "
i += 1
if i+1 < gState.data.len and gState.data[i+1].name == "identifier":
pname = gState.getIdentifier(gState.data[i+1].val, nskParam, fname)
i += 2
else:
pname = "a" & $count
count += 1
i += 1
if i < gState.data.len and gState.data[i].name in ["identifier", "number_literal"]:
flen = gState.data[i].val
if gState.data[i].name == "identifier":
flen = gState.getIdentifier(flen, nskConst, fname)
pout &= &"{pname}: array[{flen}, {getPtrType(pptr&ptyp)}], "
i += 1
elif pptr.nBl or ptyp != "object":
pout &= &"{pname}: {getPtrType(pptr&ptyp)}, "
# typedef int X
# typedef X Y
# typedef struct X Y
# typedef ?* Y
result.add((&"""
(type_definition
{typeGrammar}
(type_identifier!)
{arrGrammar}
(pointer_declarator!
(pointer_declarator!
(type_identifier!)
{arrGrammar}
{funcGrammar}
)
(type_identifier!)
{arrGrammar}
{funcGrammar}
)
{funcGrammar}
)
""",
proc (ast: ref Ast, node: TSNode, gState: State) =
if gState.debug:
gState.debugStr &= "\n# typedef X Y"
var
i = 0
typ = gState.getIdentifier(gState.data[i].val, nskType, "IgnoreSkipSymbol").getType()
name = ""
nname = ""
tptr = ""
aptr = ""
pragmas: seq[string] = @[]
i += 1
while i < gState.data.len and "pointer" in gState.data[i].name:
case gState.data[i].name:
of "pointer_declarator":
tptr &= "ptr "
i += 1
of "array_pointer_declarator":
aptr &= "ptr "
i += 1
if i < gState.data.len:
name = gState.data[i].val
nname = gState.getIdentifier(name, nskType)
i += 1
if not gState.noHeader and gState.dynlib.Bl:
pragmas.add gState.getImportC(name, nname)
let
pragma = gState.getPragma(pragmas)
if not nname.nBl:
let
override = gState.getOverride(name, nskType)
if override.nBl:
gState.typeStr &= &"{gState.getComments()}\n{override}"
elif nname notin gTypeMap and typ.nBl and gState.addNewIdentifer(nname):
if i < gState.data.len and gState.data[^1].name == "function_declarator":
var
fname = nname
pout, pname, ptyp, pptr = ""
count = 1
flen = ""
while i < gState.data.len:
if gState.data[i].name == "function_declarator":
break
funcParamCommon(fname, pname, ptyp, pptr, pout, count, i, flen)
if pout.nBl and pout[^2 .. ^1] == ", ":
pout = pout[0 .. ^3]
if tptr.nBl or typ != "object":
gState.typeStr &= &"{gState.getComments()}\n {nname}*{pragma} = proc({pout}): {getPtrType(tptr&typ)} {{.cdecl.}}"
else:
gState.typeStr &= &"{gState.getComments()}\n {nname}*{pragma} = proc({pout}) {{.cdecl.}}"
else:
if i < gState.data.len and gState.data[i].name in ["identifier", "number_literal"]:
var
flen = gState.data[i].val
if gState.data[i].name == "identifier":
flen = gState.getIdentifier(flen, nskConst, nname)
gState.typeStr &= &"{gState.getComments()}\n {nname}*{pragma} = {aptr}array[{flen}, {getPtrType(tptr&typ)}]"
else:
if nname == typ:
pragmas.add "incompleteStruct"
let
pragma = gState.getPragma(pragmas)
gState.typeStr &= &"{gState.getComments()}\n {nname}*{pragma} = object"
else:
gState.typeStr &= &"{gState.getComments()}\n {nname}*{pragma} = {getPtrType(tptr&typ)}"
))
proc pDupTypeCommon(nname: string, fend: int, gState: State, isEnum=false) =
if gState.debug:
gState.debugStr &= "\n# pDupTypeCommon()"
var
dname = gState.data[^1].val
ndname = gState.getIdentifier(dname, nskType)
dptr =
if fend == 2:
"ptr "
else:
""
if ndname.nBl and ndname != nname:
if isEnum:
if gState.addNewIdentifer(ndname):
gState.enumStr &= &"{gState.getComments(true)}\ntype {ndname}* = {dptr}{nname}"
else:
if gState.addNewIdentifer(ndname):
let
pragma = gState.getPragma(gState.getImportc(dname, ndname), "bycopy")
gState.typeStr &=
&"{gState.getComments()}\n {ndname}*{pragma} = {dptr}{nname}"
proc pStructCommon(ast: ref Ast, node: TSNode, name: string, fstart, fend: int, gState: State) =
if gState.debug:
gState.debugStr &= "\n# pStructCommon"
var
nname = gState.getIdentifier(name, nskType)
prefix = ""
union = ""
case $node.tsNodeType():
of "struct_specifier":
prefix = "struct "
of "union_specifier":
prefix = "union "
union = ", union"
of "type_definition":
if node.getTSNodeNamedChildCountSansComments() != 0:
for i in 0 .. node.tsNodeNamedChildCount()-1:
let
nchild = $node.tsNodeNamedChild(i).tsNodeType()
if nchild != "comment":
case nchild:
of "struct_specifier":
if fstart == 1:
prefix = "struct "
of "union_specifier":
if fstart == 1:
prefix = "union "
union = ", union"
break
if not nname.nBl:
let
override = gState.getOverride(name, nskType)
if override.nBl:
gState.typeStr &= &"{gState.getComments()}\n{override}"
elif gState.addNewIdentifer(nname):
if gState.data.len == 1:
gState.typeStr &= &"{gState.getComments()}\n {nname}* {{.bycopy{union}.}} = object"
else:
var
pragmas: seq[string] = @[]
if not gState.noHeader and gState.dynlib.Bl:
pragmas.add gState.getImportC(prefix & name, nname)
pragmas.add "bycopy"
if union.nBl:
pragmas.add "union"
let
pragma = gState.getPragma(pragmas)
gState.typeStr &= &"{gState.getComments()}\n {nname}*{pragma} = object"
var
i = fstart
ftyp, fname: string
fptr = ""
aptr = ""
flen = ""
while i < gState.data.len-fend:
fptr = ""
aptr = ""
if gState.data[i].name == "field_declaration":
i += 1
continue
if gState.data[i].name notin ["field_identifier", "pointer_declarator", "array_pointer_declarator"]:
ftyp = gState.getIdentifier(gState.data[i].val, nskType, nname).getType()
i += 1
while i < gState.data.len-fend and "pointer" in gState.data[i].name:
case gState.data[i].name:
of "pointer_declarator":
fptr &= "ptr "
i += 1
of "array_pointer_declarator":
aptr &= "ptr "
i += 1
fname = gState.getIdentifier(gState.data[i].val, nskField, nname)
if i+1 < gState.data.len-fend and gState.data[i+1].name in gEnumVals:
# Struct field is an array where size is an expression
var
flen = gState.getNimExpression(gState.data[i+1].val)
if "/" in flen:
flen = &"({flen}).int"
gState.typeStr &= &"{gState.getComments()}\n {fname}*: {aptr}array[{flen}, {getPtrType(fptr&ftyp)}]"
i += 2
elif i+1 < gState.data.len-fend and gState.data[i+1].name == "bitfield_clause":
let
size = gState.data[i+1].val
gState.typeStr &= &"{gState.getComments()}\n {fname}* {{.bitsize: {size}.}} : {getPtrType(fptr&ftyp)} "
i += 2
elif i+1 < gState.data.len-fend and gState.data[i+1].name == "function_declarator":
var
pout, pname, ptyp, pptr = ""
count = 1
i += 2
while i < gState.data.len-fend:
if gState.data[i].name == "function_declarator":
i += 1
continue
if gState.data[i].name == "field_declaration":
break
funcParamCommon(fname, pname, ptyp, pptr, pout, count, i, flen)
if pout.nBl and pout[^2 .. ^1] == ", ":
pout = pout[0 .. ^3]
if fptr.nBl or ftyp != "object":
gState.typeStr &= &"{gState.getComments()}\n {fname}*: proc({pout}): {getPtrType(fptr&ftyp)} {{.cdecl.}}"
else:
gState.typeStr &= &"{gState.getComments()}\n {fname}*: proc({pout}) {{.cdecl.}}"
i += 1
else:
if ftyp == "object":
gState.typeStr &= &"{gState.getComments()}\n {fname}*: pointer"
else:
gState.typeStr &= &"{gState.getComments()}\n {fname}*: {getPtrType(fptr&ftyp)}"
i += 1
if node.tsNodeType() == "type_definition" and
gState.data[^1].name == "type_identifier" and gState.data[^1].val.nBl:
pDupTypeCommon(nname, fend, gState, false)
let
fieldGrammar = &"""
(field_identifier!)
(bitfield_clause!
(number_literal)
)
(array_declarator!
(field_identifier!)
(pointer_declarator
(pointer_declarator!
(field_identifier)
)
(field_identifier)
)
(^$1+)
)
(function_declarator+
(pointer_declarator
(pointer_declarator!
(field_identifier)
)
(field_identifier)
)
{paramListGrammar}
)
""" % gEnumVals.join("|")
fieldListGrammar = &"""
(field_declaration_list?
(field_declaration+
{typeGrammar}
(pointer_declarator!
(pointer_declarator!
{fieldGrammar}
)
{fieldGrammar}
)
{fieldGrammar}
)
)
"""
# struct X {}
result.add((&"""
(struct_specifier|union_specifier
(type_identifier)
{fieldListGrammar}
)
""",
proc (ast: ref Ast, node: TSNode, gState: State) =
if gState.debug:
gState.debugStr &= "\n# struct X {}"
pStructCommon(ast, node, gState.data[0].val, 1, 1, gState)
))
# typedef struct X {}
result.add((&"""
(type_definition
(struct_specifier|union_specifier
(type_identifier?)
{fieldListGrammar}
)
(type_identifier!)
(pointer_declarator
(pointer_declarator!
(type_identifier)
)
(type_identifier)
)
)
""",
proc (ast: ref Ast, node: TSNode, gState: State) =
if gState.debug:
gState.debugStr &= "\n# typedef struct X {}"
var
fstart = 0
fend = 1
if gState.data[^2].name == "pointer_declarator":
fend = 2
if gState.data.len > 1 and
gState.data[0].name == "type_identifier" and
gState.data[1].name notin ["field_identifier", "pointer_declarator"]:
fstart = 1
pStructCommon(ast, node, gState.data[0].val, fstart, fend, gState)
else:
pStructCommon(ast, node, gState.data[^1].val, fstart, fend, gState)
))
proc pEnumCommon(ast: ref Ast, node: TSNode, name: string, fstart, fend: int, gState: State) =
if gState.debug:
gState.debugStr &= "\n# pEnumCommon()"
let nname =
if name.Bl:
getUniqueIdentifier(gState, "Enum")
else:
gState.getIdentifier(name, nskType)
if nname.nBl and gState.addNewIdentifer(nname):
gState.enumStr &= &"{gState.getComments(true)}\ndefineEnum({nname})"
var
i = fstart
count = 0
while i < gState.data.len-fend:
if gState.data[i].name == "enumerator":
i += 1
continue
let
fname = gState.getIdentifier(gState.data[i].val, nskEnumField)
if i+1 < gState.data.len-fend and
gState.data[i+1].name in gEnumVals:
if fname.nBl and gState.addNewIdentifer(fname):
gState.constStr &= &"{gState.getComments()}\n {fname}* = ({gState.getNimExpression(gState.data[i+1].val)}).{nname}"
try:
count = gState.data[i+1].val.parseInt() + 1
except:
count += 1
i += 2
else:
if fname.nBl and gState.addNewIdentifer(fname):
gState.constStr &= &"{gState.getComments()}\n {fname}* = {count}.{nname}"
i += 1
count += 1
if node.tsNodeType() == "type_definition" and
gState.data[^1].name == "type_identifier" and gState.data[^1].val.nBl:
pDupTypeCommon(nname, fend, gState, true)
# enum X {}
result.add(("""
(enum_specifier
(type_identifier?)
(enumerator_list
(enumerator+
(identifier?)
(^$1+)
)
)
)
""" % gEnumVals.join("|"),
proc (ast: ref Ast, node: TSNode, gState: State) =
if gState.debug:
gState.debugStr &= "\n# enum X {}"
var
name = ""
offset = 0
if gState.data[0].name == "type_identifier":
name = gState.data[0].val
offset = 1
pEnumCommon(ast, node, name, offset, 0, gState)
))
# typedef enum {} X
result.add((&"""
(type_definition
{result[^1].grammar}
(type_identifier!)
(pointer_declarator
(pointer_declarator!
(type_identifier)
)
(type_identifier)
)
)
""",
proc (ast: ref Ast, node: TSNode, gState: State) =
if gState.debug:
gState.debugStr &= "\n# typedef enum {}"
var
fstart = 0
fend = 1
if gState.data[^2].name == "pointer_declarator":
fend = 2
if gState.data[0].name == "type_identifier":
fstart = 1
pEnumCommon(ast, node, gState.data[0].val, fstart, fend, gState)
else:
pEnumCommon(ast, node, gState.data[^1].val, fstart, fend, gState)
))
# typ function(typ param1, ...)
result.add((&"""
(declaration
(storage_class_specifier?)
{typeGrammar}
(pointer_declarator!
(pointer_declarator!
{funcGrammar}
)
{funcGrammar}
)
{funcGrammar}
)
""",
proc (ast: ref Ast, node: TSNode, gState: State) =
if gState.debug:
gState.debugStr &= "\n# typ function"
var
fptr = ""
i = 1
while i < gState.data.len:
if gState.data[i].name == "function_declarator":
i += 1
continue
fptr = ""
while i < gState.data.len and gState.data[i].name == "pointer_declarator":
fptr &= "ptr "
i += 1
var
fname = gState.data[i].val
fnname = gState.getIdentifier(fname, nskProc)
pout, pname, ptyp, pptr = ""
count = 1
flen = ""
fVar = false
i += 1
if i < gState.data.len and gState.data[i].name == "pointer_declarator":
fVar = true
i += 1
while i < gState.data.len:
if gState.data[i].name == "function_declarator":
break
funcParamCommon(fnname, pname, ptyp, pptr, pout, count, i, flen)
if pout.nBl and pout[^2 .. ^1] == ", ":
pout = pout[0 .. ^3]
if not fnname.nBl:
let
override = gState.getOverride(fname, nskProc)
if override.nBl:
gState.typeStr &= &"{gState.getComments()}\n{override}"
elif gState.addNewIdentifer(fnname):
let
ftyp = gState.getIdentifier(gState.data[0].val, nskType, fnname).getType()
pragma = gState.getPragma(gState.getImportC(fname, fnname), "cdecl")
if fptr.nBl or ftyp != "object":
if fVar:
gState.procStr &= &"{gState.getComments(true)}\nvar {fnname}*: proc ({pout}): {getPtrType(fptr&ftyp)}{{.cdecl.}}"
else:
gState.procStr &= &"{gState.getComments(true)}\nproc {fnname}*({pout}): {getPtrType(fptr&ftyp)}{pragma}"
else:
if fVar:
gState.procStr &= &"{gState.getComments(true)}\nvar {fnname}*: proc ({pout}){{.cdecl.}}"
else:
gState.procStr &= &"{gState.getComments(true)}\nproc {fnname}*({pout}){pragma}"
))
# // comment
result.add((&"""
(comment
)
""",
proc (ast: ref Ast, node: TSNode, gState: State) =
let
cmt = $gState.getNodeVal(node)
for line in cmt.splitLines():
let
line = line.multiReplace([("//", ""), ("/*", ""), ("*/", "")])
gState.commentStr &= &"\n # {line.strip(leading=false)}"
))
# // unknown
result.add((&"""
(type_definition|struct_specifier|union_specifier|enum_specifier|declaration
(^.*)
)
""",
proc (ast: ref Ast, node: TSNode, gState: State) =
var
done = false
for i in gState.data:
case $node.tsNodeType()
of "declaration":
if i.name == "identifier":
let
override = gState.getOverride(i.val, nskProc)
if override.nBl:
gState.procStr &= &"{gState.getComments(true)}\n{override}"
done = true
break
else:
gState.procStr &= &"{gState.getComments(true)}\n# Declaration '{i.val}' skipped"
else:
if i.name == "type_identifier":
let
override = gState.getOverride(i.val, nskType)
if override.nBl:
gState.typeStr &= &"{gState.getComments()}\n{override}"
done = true
break
else:
gState.typeStr &= &"{gState.getComments()}\n # Type '{i.val}' skipped"
if gState.debug and not done:
gState.skipStr &= &"\n{gState.getNodeVal(node)}"
))
proc initRegex(ast: ref Ast) =
if ast.children.nBl:
if not ast.recursive:
for child in ast.children:
child.initRegex()
var
reg: string
try:
reg = ast.getRegexForAstChildren()
ast.regex = reg.re()
except:
echo reg
raise newException(Exception, getCurrentExceptionMsg())
proc parseGrammar*(): AstTable =
const grammars = initGrammar()
result = newTable[string, seq[ref Ast]]()
for i in 0 .. grammars.len-1:
var
ast = grammars[i].grammar.parseLisp()
ast.tonim = grammars[i].call
ast.initRegex()
for n in ast.name.split("|"):
if n notin result:
result[n] = @[ast]
else:
result[n].add(ast)
proc printGrammar*(gState: State, astTable: AstTable) =
for name in astTable.keys():
for ast in astTable[name]:
gecho ast.printAst()

View file

@ -1,60 +0,0 @@
import ".."/globals
import "."/getters
var
gTokens: seq[string]
idx = 0
proc tokenize(tree: string) =
var collect = ""
gTokens = @[]
idx = 0
for i in tree:
case i:
of ' ', '\n', '\r', '(', ')':
if collect.nBl:
gTokens.add(collect)
collect = ""
if i in ['(', ')']:
gTokens.add($i)
else:
collect &= $i
proc readFromTokens(): ref Ast =
if idx == gTokens.len:
doAssert false, "Bad AST " & $(idx: idx)
if gTokens[idx] == "(":
if gTokens.len - idx < 2:
doAssert false, "Corrupt AST " & $(gTokensLen: gTokens.len, idx: idx)
result = new(Ast)
(result.name, result.kind, result.recursive) = gTokens[idx+1].getNameKind()
result.children = @[]
if result.recursive:
result.children.add(result)
idx += 2
while gTokens[idx] != ")":
var res = readFromTokens()
if not res.isNil:
result.children.add(res)
elif gTokens[idx] == ")":
doAssert false, "Poor AST " & $(idx: idx)
idx += 1
proc printAst*(node: ref Ast, offset=""): string =
result = offset & "(" & (if node.recursive: "^" else: "") & node.name & node.kind.toString()
if node.children.nBl and not node.recursive:
result &= "\n"
for child in node.children:
result &= printAst(child, offset & " ")
result &= offset & ")\n"
else:
result &= ")\n"
proc parseLisp*(tree: string): ref Ast =
tokenize(tree)
return readFromTokens()

View file

@ -2,7 +2,6 @@ import sets, strformat, strutils
import ".."/treesitter/[api, c, cpp]
import ".."/globals
import "."/getters
template withCodeAst*(code: string, mode: string, body: untyped): untyped =
## A simple template to inject the TSNode into a body of code

View file

@ -14,7 +14,7 @@ proc testCall(cmd, output: string, exitCode: int, delete = true) =
doAssert outp.contains(output), outp
var
cmd = "nim c -f --hints:off -d:FLAGS=\"-f:ast2\" -d:checkAbi"
cmd = "nim c -f --hints:off -d:checkAbi"
lrcmd = " -r lzma.nim"
zrcmd = " -r zlib.nim"
sshcmd = " -r libssh2.nim"

View file

@ -17,12 +17,12 @@ cOverride:
SOCKET = object
when not libssh2Static:
cImport(libssh2Path, recurse = true, dynlib = "libssh2LPath", flags = "-f:ast2 -c -E_ -F_")
cImport(libssh2Path, recurse = true, dynlib = "libssh2LPath", flags = "-c -E_ -F_")
when not defined(Windows) and not isDefined(libssh2JBB):
proc zlibVersion(): cstring {.importc, dynlib: libssh2LPath.}
else:
cImport(libssh2Path, recurse = true, flags = "-f:ast2 -c -E_ -F_")
cImport(libssh2Path, recurse = true, flags = "-c -E_ -F_")
when not defined(Windows) and not isDefined(libssh2JBB):
proc zlibVersion(): cstring {.importc.}

View file

@ -39,7 +39,7 @@ cOverride:
cImport(@[
basePath / "rsa.h",
basePath / "err.h",
], recurse = true, flags = "-f:ast2 -s -c " & FLAGS)
], recurse = true, flags = "-s -c " & FLAGS)
{.passL: cryptoLPath.}