aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGanesh Viswanathan <dev@genotrance.com>2019-01-11 12:22:49 -0600
committerGanesh Viswanathan <dev@genotrance.com>2019-01-11 12:22:49 -0600
commit88005fa9841ca74c25cc54699ee7b0968dd495ef (patch)
treea1989bada7f825fe7a37dd6d93f550a744c7f1bc
parentc65c566e09faf11f374429e3dbe573cc09f5aba9 (diff)
downloadnimterop-88005fa9841ca74c25cc54699ee7b0968dd495ef.tar.gz
nimterop-88005fa9841ca74c25cc54699ee7b0968dd495ef.zip
#include file, static line, nostderr, improve preprocessing
-rw-r--r--README.md14
-rw-r--r--nimterop/cimport.nim9
-rw-r--r--nimterop/getters.nim42
-rw-r--r--nimterop/git.nim9
-rw-r--r--nimterop/globals.nim2
-rw-r--r--toast.nim4
6 files changed, 59 insertions, 21 deletions
diff --git a/README.md b/README.md
index 5bb725f..cc51f4e 100644
--- a/README.md
+++ b/README.md
@@ -12,16 +12,10 @@ The goal of nimterop is to leverage the [tree-sitter](http://tree-sitter.github.
Most of the functionality is contained within the `toast` binary that is built when nimterop is installed and can be used standalone similar to how c2nim can be used today. In addition, nimterop also offers an API to pull in the generated Nim content directly into an application.
-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`, `union X`, `typedef union`
-- Functions with primitive types, structs, enums, unions and typedef structs/enums/unions as params and return values
-- Pointers to data types
+The nimterop feature set is still limited to C but is expanding rapidly. C++ support will be added once most popular C libraries can be wrapped seamlessly.
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 such as classes and templats are still TBD depending on the results of the C interop.
-
__Installation__
Nimterop can be installed via [Nimble](https://github.com/nim-lang/nimble):
@@ -71,6 +65,8 @@ Detailed documentation is still forthcoming.
`cImport("header.h")` - import all supported definitions from header file
+`cImport("header.h", recurse=true)` - import all supported definitions from header file and #includes
+
`cAddSearchDir("XXX")` - add directory XXX to search path in calls to `cSearchPath()`
`cAddStdDir("XXX")` - add standard "c" [default] or "cpp" include paths to search path
@@ -81,10 +77,12 @@ Detailed documentation is still forthcoming.
__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 can then be printed out to stdout in a Lisp S-Expression format or the relevant Nim wrapper output. This content can be saved to a `.nim` file and imported if so desired.
+In order to use the tree-sitter C library, 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 can then be printed out to stdout in a Lisp S-Expression format or the relevant Nim wrapper output. This content can be saved to a `.nim` file and imported if so desired.
Alternatively, the `cImport()` macro allows easier creation of wrappers in code. It runs `toast` on the specified header file and injects the generated wrapper content into the application at compile time. A few other helper procs are provided to influence this process.
+`toast` can also be used to run the header through the preprocessor which cleans up the code considerably. Along with the recursion capability which runs through all #include files, one large simpler header file can be created which can then be processed with c2nim if so desired.
+
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__
diff --git a/nimterop/cimport.nim b/nimterop/cimport.nim
index 2d29e6c..5b8935b 100644
--- a/nimterop/cimport.nim
+++ b/nimterop/cimport.nim
@@ -21,12 +21,15 @@ proc findPath(path: string, fail = true): string =
else:
return ""
-proc getToast(fullpath: string): string =
+proc getToast(fullpath: string, recurse: bool = false): string =
var
cmd = when defined(Windows): "cmd /c " else: ""
cmd &= "toast --pnim --preprocess "
+ if recurse:
+ cmd.add "--recurse "
+
for i in gStateCT.defines:
cmd.add &"--defines+={i.quoteShell} "
@@ -156,7 +159,7 @@ macro cCompile*(path: static string): untyped =
if gStateCT.debug:
echo result.repr
-macro cImport*(filename: static string): untyped =
+macro cImport*(filename: static string, recurse: static bool = false): untyped =
result = newNimNode(nnkStmtList)
let
@@ -165,7 +168,7 @@ macro cImport*(filename: static string): untyped =
echo "Importing " & fullpath
let
- output = getToast(fullpath)
+ output = getToast(fullpath, recurse)
try:
result.add parseStmt(output)
diff --git a/nimterop/getters.nim b/nimterop/getters.nim
index 4d217a7..d0fa49c 100644
--- a/nimterop/getters.nim
+++ b/nimterop/getters.nim
@@ -112,6 +112,20 @@ proc getGccPaths*(mode = "c"): string =
(result, ret) = gorgeEx("gcc -Wp,-v -x" & mmode & " " & nul)
+proc removeStatic*(content: string): string =
+ ## Replace static function bodies with a semicolon and commented
+ ## out body
+ return content.replace(
+ re"(?ms)static inline(.*?\))(\s*\{(\s*?.*?$)*?[\n\r]\})",
+ proc (m: RegexMatch, s: string): string =
+ let funcDecl = s[m.group(0)[0]]
+ let body = s[m.group(1)[0]].strip()
+ result = ""
+
+ result.add("$#;" % [funcDecl])
+ result.add(body.replace(re"(?m)^(.*\n?)", "//$1"))
+ )
+
proc getPreprocessor*(fullpath: string, mode = "cpp"): string =
var
mmode = if mode == "cpp": "c++" else: mode
@@ -130,20 +144,36 @@ proc getPreprocessor*(fullpath: string, mode = "cpp"): string =
cmd &= &"\"{fullpath}\""
# Include content only from file
- for line in execAction(cmd).splitLines():
+ for line in execAction(cmd, true).splitLines():
if line.strip() != "":
- if line[0] == '#' and not line.contains("#pragma") and not line.contains("define"):
+ if line.len > 1 and line[0 .. 1] == "# ":
start = false
- if sfile in line.sanitizePath:
+ let
+ saniLine = line.sanitizePath
+ if sfile in saniLine:
start = true
- if not ("\\" in line) and not ("/" in line) and extractFilename(sfile) in line:
+ elif not ("\\" in line) and not ("/" in line) and extractFilename(sfile) in line:
start = true
+ elif gStateRT.recurse:
+ if sfile.parentDir() in saniLine:
+ start = true
+ else:
+ for inc in gStateRT.includeDirs:
+ if inc.absolutePath().sanitizePath in saniLine:
+ start = true
+ break
+ if start:
+ rdata.add(&"// {line}")
else:
if start:
+ if "#undef" in line:
+ continue
rdata.add(
- line.replace(re"__attribute__[ ]*\(\(.*?\)\) ", "")
+ line.
+ replace("__restrict", "").
+ replace(re"__attribute__[ ]*\(\(.*?\)\)([ ,;])", "$1")
)
- return rdata.join("\n")
+ return rdata.join("\n").removeStatic()
converter toString*(kind: Kind): string =
return case kind:
diff --git a/nimterop/git.nim b/nimterop/git.nim
index 51ea756..f4412ce 100644
--- a/nimterop/git.nim
+++ b/nimterop/git.nim
@@ -2,7 +2,7 @@ import macros, os, osproc, regex, strformat, strutils
import globals
-proc execAction*(cmd: string): string =
+proc execAction*(cmd: string, nostderr=false): string =
var
ccmd = ""
ret = 0
@@ -14,7 +14,10 @@ proc execAction*(cmd: string): string =
when nimvm:
(result, ret) = gorgeEx(ccmd)
else:
- (result, ret) = execCmdEx(ccmd)
+ if nostderr:
+ (result, ret) = execCmdEx(ccmd, {poUsePath})
+ else:
+ (result, ret) = execCmdEx(ccmd)
if ret != 0:
echo "Command failed: " & $ret
echo ccmd
@@ -66,7 +69,7 @@ macro gitCheckout*(file, outdir: static string): untyped =
macro gitPull*(url: static string, outdirN = "", plistN = "", checkoutN = ""): untyped =
let
- outdir = getProjectPath()/outdirN.strVal()
+ outdir = if outdirN.strVal().isAbsolute(): outdirN.strVal() else: getProjectPath()/outdirN.strVal()
plist = plistN.strVal()
checkout = checkoutN.strVal()
diff --git a/nimterop/globals.nim b/nimterop/globals.nim
index 771987e..7b2375c 100644
--- a/nimterop/globals.nim
+++ b/nimterop/globals.nim
@@ -21,7 +21,7 @@ type
State* = object
compile*, defines*, headers*, includeDirs*, searchDirs*: seq[string]
- debug*, past*, preprocess*, pnim*, pretty*: bool
+ debug*, past*, preprocess*, pnim*, pretty*, recurse*: bool
consts*, enums*, procs*, types*: seq[string]
diff --git a/toast.nim b/toast.nim
index f9edd89..d4a40c9 100644
--- a/toast.nim
+++ b/toast.nim
@@ -113,6 +113,7 @@ proc main(
pretty = true,
preprocess = false,
pgrammar = false,
+ recurse = false,
defines: seq[string] = @[],
includeDirs: seq[string] = @[],
source: seq[string],
@@ -124,6 +125,7 @@ proc main(
pnim: pnim,
pretty: pretty,
preprocess: preprocess,
+ recurse: recurse,
defines: defines,
includeDirs: includeDirs,
)
@@ -144,6 +146,7 @@ when isMainModule:
"includeDirs": "include directory to pass to preprocessor",
"preprocess": "run preprocessor on header",
"pgrammar": "print grammar",
+ "recurse": "process #include files",
"source" : "C/C++ source/header",
}, short = {
"past": 'a',
@@ -151,5 +154,6 @@ when isMainModule:
"defines": 'D',
"includeDirs": 'I',
"preprocess": 'p',
+ "recurse": 'r',
"pgrammar": 'g'
})