nimterop/nimterop/cimport.nim
Joey Yakimowich-Payne 1e105d34a4 Add docgen runs in tests
Fix paths import

Skip docgen for rsa for now
2020-05-04 16:43:07 -05:00

732 lines
25 KiB
Nim

##[
This is the main nimterop import file to help with wrapping C/C++ source code.
Check out `template.nim <https://github.com/nimterop/nimterop/blob/master/nimterop/template.nim>`_
as a starting point for wrapping a new library. The template can be copied and
trimmed down and modified as required. `templite.nim <https://github.com/nimterop/nimterop/blob/master/nimterop/templite.nim>`_ is a shorter
version for more experienced users.
All `{.compileTime.}` procs must be used in a compile time context, e.g. using:
.. code-block:: c
static:
cAddStdDir()
]##
import hashes, macros, os, strformat, strutils
import regex
import "."/[build, globals, paths, types]
export types
proc interpPath(dir: string): string=
# TODO: more robust: needs a DirSep after "$projpath"
# disabling this interpolation as this is error prone, but other less
# interpolations can be added, eg see https://github.com/nim-lang/Nim/pull/10530
# result = dir.replace("$projpath", getProjectPath())
result = dir
proc joinPathIfRel(path1: string, path2: string): string =
if path2.isAbsolute:
result = path2
else:
result = joinPath(path1, path2)
proc findPath(path: string, fail = true): string =
# Relative to project path
result = joinPathIfRel(getProjectPath(), path).replace("\\", "/")
if not fileExists(result) and not dirExists(result):
doAssert (not fail), "File or directory not found: " & path
result = ""
proc walkDirImpl(indir, inext: string, file=true): seq[string] =
let
dir = joinPathIfRel(getProjectPath(), indir)
ext =
if inext.nBl:
when not defined(Windows):
"-name " & inext
else:
"\\" & inext
else:
""
let
cmd =
when defined(Windows):
if file:
"cmd /c dir /s/b/a-d " & dir.replace("/", "\\") & ext
else:
"cmd /c dir /s/b/ad " & dir.replace("/", "\\")
else:
if file:
"find $1 -type f $2" % [dir, ext]
else:
"find $1 -type d" % dir
(output, ret) = execAction(cmd, die = false)
if ret == 0:
result = output.splitLines()
proc getFileDate(fullpath: string): string =
var
ret = 0
cmd =
when defined(Windows):
&"cmd /c for %a in ({fullpath.sanitizePath}) do echo %~ta"
elif defined(Linux):
&"stat -c %y {fullpath.sanitizePath}"
elif defined(OSX) or defined(FreeBSD):
&"stat -f %m {fullpath.sanitizePath}"
(result, ret) = execAction(cmd)
proc getCacheValue(fullpath: string): string =
if not gStateCT.nocache:
result = fullpath.getFileDate()
proc getCacheValue(fullpaths: seq[string]): string =
if not gStateCT.nocache:
for fullpath in fullpaths:
result &= getCacheValue(fullpath)
proc getToastError(output: string): string =
# Filter out preprocessor errors
for line in output.splitLines():
if "fatal error:" in line.toLowerAscii:
if result.len == 0:
result = "\n\nFailed in preprocessing, check if `cIncludeDir()` is needed or compiler `mode` is correct (c/cpp)"
result &= "\n\nERROR:$1\n" % line.split("fatal error:")[1]
# Toast error
if result.Bl:
result = "\n\n" & output
proc getNimCheckError(output: string): tuple[tmpFile, errors: string] =
let
hash = output.hash().abs()
result.tmpFile = getProjectCacheDir("failed", forceClean = false) / "nimterop_" & $hash & ".nim"
if not fileExists(result.tmpFile) or gStateCT.nocache or compileOption("forceBuild"):
mkDir(result.tmpFile.parentDir())
writeFile(result.tmpFile, output)
doAssert fileExists(result.tmpFile), "Failed to write to cache dir: " & result.tmpFile
let
(check, _) = execAction(
&"{getCurrentNimCompiler()} check {result.tmpFile.sanitizePath}",
die = false
)
result.errors = "\n\n" & check
proc getToast(fullpaths: seq[string], recurse: bool = false, dynlib: string = "",
mode = "c", flags = "", noNimout = false): string =
var
ret = 0
cmd = when defined(Windows): "cmd /c " else: ""
let toastExe = toastExePath()
doAssert fileExists(toastExe), "toast not compiled: " & toastExe.sanitizePath &
" make sure 'nimble build' or 'nimble install' built it"
cmd &= &"{toastExe} --preprocess -m:{mode}"
if recurse:
cmd.add " --recurse"
if flags.nBl:
cmd.add " " & flags
for i in gStateCT.defines:
cmd.add &" --defines+={i.quoteShell}"
for i in gStateCT.includeDirs:
cmd.add &" --includeDirs+={i.sanitizePath}"
if not noNimout:
cmd.add &" --pnim"
if dynlib.nBl:
cmd.add &" --dynlib={dynlib}"
if gStateCT.symOverride.nBl:
cmd.add &" --symOverride={gStateCT.symOverride.join(\",\")}"
cmd.add &" --nim:{getCurrentNimCompiler().sanitizePath}"
if gStateCT.pluginSourcePath.nBl:
cmd.add &" --pluginSourcePath={gStateCT.pluginSourcePath.sanitizePath}"
for fullpath in fullpaths:
cmd.add &" {fullpath.sanitizePath}"
# see https://github.com/nimterop/nimterop/issues/69
(result, ret) = execAction(cmd, die = false, cache = (not gStateCT.nocache),
cacheKey = getCacheValue(fullpaths))
doAssert ret == 0, getToastError(result)
macro cOverride*(body): untyped =
## When the wrapper code generated by nimterop is missing certain symbols or not
## accurate, it may be required to hand wrap them. Define them in a
## `cOverride() <cimport.html#cOverride.m>`_ macro block so that Nimterop uses
## these definitions instead.
##
## For example:
##
## .. code-block:: c
##
## int svGetCallerInfo(const char** fileName, int *lineNumber);
##
## This might map to:
##
## .. code-block:: nim
##
## proc svGetCallerInfo(fileName: ptr cstring; lineNumber: var cint)
##
## Whereas it might mean:
##
## .. code-block:: nim
##
## cOverride:
## proc svGetCallerInfo(fileName: var cstring; lineNumber: var cint)
##
## Using the `cOverride() <cimport.html#cOverride.m>`_ block, nimterop
## can be instructed to use this definition of `svGetCallerInfo()` instead.
## This works for procs, consts and types.
##
## `cOverride()` only affects the next `cImport()` call. This is because any
## recognized symbols get overridden in place and any remaining symbols get
## added to the top. If reused, the next `cImport()` would add those symbols
## again leading to redefinition errors.
iterator findOverrides(node: NimNode): tuple[name, override: string, kind: NimNodeKind] =
for child in node:
case child.kind
of nnkTypeSection, nnkConstSection:
# Types, const
for inst in child:
let name =
if inst[0].kind == nnkPragmaExpr:
$inst[0][0]
else:
$inst[0]
yield (name.strip(chars={'*'}), inst.repr, child.kind)
of nnkProcDef:
let
name = $child[0]
yield (name.strip(chars={'*'}), child.repr, child.kind)
else:
discard
if gStateCT.overrides.Bl:
gStateCT.overrides = """
import sets, tables
proc onSymbolOverride*(sym: var Symbol) {.exportc, dynlib.} =
"""
# If cPlugin called before cOverride
if gStateCT.pluginSourcePath.nBl:
gStateCT.pluginSourcePath = ""
var
names: seq[string]
for name, override, kind in body.findOverrides():
let
typ =
case kind
of nnkTypeSection: "nskType"
of nnkConstSection: "nskConst"
of nnkProcDef: "nskProc"
else: ""
gStateCT.overrides &= &"""
if sym.name == "{name}" and sym.kind == {typ} and "{name}" in cOverrides["{typ}"]:
sym.override = """ & "\"\"\"" & override & "\"\"\"\n"
gStateCT.overrides &= &" cOverrides[\"{typ}\"].excl \"{name}\"\n"
gStateCT.overrides = gStateCT.overrides.replace("proc onSymbolOverride",
&"cOverrides[\"{typ}\"].incl \"{name}\"\nproc onSymbolOverride")
names.add name
gStateCT.symOverride.add name
if gStateCT.debug and names.nBl:
echo "# Overriding " & names.join(" ")
proc cSkipSymbol*(skips: seq[string]) {.compileTime.} =
## Similar to `cOverride() <cimport.html#cOverride.m>`_, this macro allows
## filtering out symbols not of interest from the generated output.
##
## `cSkipSymbol() <cimport.html#cSkipSymbol%2Cseq[T][string]>`_ only affects calls to
## `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_ that follow it.
runnableExamples:
static: cSkipSymbol @["proc1", "Type2"]
gStateCT.symOverride.add skips
proc cPluginHelper(body: string, imports = "import macros, nimterop/plugin\n\n") =
gStateCT.pluginSource = body
if gStateCT.pluginSource.nBl or gStateCT.overrides.nBl:
let
data = imports & body & "\n\n" & gStateCT.overrides
hash = data.hash().abs()
path = getProjectCacheDir("cPlugins", forceClean = false) / "nimterop_" & $hash & ".nim"
if not fileExists(path) or gStateCT.nocache or compileOption("forceBuild"):
mkDir(path.parentDir())
writeFile(path, data)
writeNimConfig(path & ".cfg")
doAssert fileExists(path), "Unable to write plugin file: " & path
gStateCT.pluginSourcePath = path
macro cPlugin*(body): untyped =
## When `cOverride() <cimport.html#cOverride.m>`_ and
## `cSkipSymbol() <cimport.html#cSkipSymbol%2Cseq[T][string]>`_
## are not adequate, the `cPlugin() <cimport.html#cPlugin.m>`_ macro can be used
## to customize the generated Nim output. The following callbacks are available at
## this time.
##
## .. code-block:: nim
##
## proc onSymbol(sym: var Symbol) {.exportc, dynlib.}
##
## `onSymbol()` can be used to handle symbol name modifications required due
## to invalid characters in identifiers or to rename symbols that would clash
## due to Nim's style insensitivity. The symbol name and type is provided to
## the callback and the name can be modified.
##
## While `cPlugin` can easily remove leading/trailing `_` or prefixes and
## suffixes like `SDL_`, passing `--prefix` or `--suffix` flags to `cImport`
## in the `flags` parameter is much easier. However, these flags will only be
## considered when no `cPlugin` is specified.
##
## Returning a blank name will result in the symbol being skipped. This will
## fail for `nskParam` and `nskField` since the generated Nim code will be wrong.
##
## Symbol types can be any of the following:
## - `nskConst` for constants
## - `nskType` for type identifiers, including primitive
## - `nskParam` for param names
## - `nskField` for struct field names
## - `nskEnumField` for enum (field) names, though they are in the global namespace as `nskConst`
## - `nskProc` - for proc names
##
## `macros` and `nimterop/plugins` are implicitly imported to provide access to standard
## plugin facilities.
##
## `cPlugin() <cimport.html#cPlugin.m>`_ only affects calls to
## `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_ that
## follow it.
runnableExamples:
cPlugin:
import strutils
# Strip leading and trailing underscores
proc onSymbol*(sym: var Symbol) {.exportc, dynlib.} =
sym.name = sym.name.strip(chars={'_'})
runnableExamples:
cPlugin:
import strutils
# Strip prefix from procs
proc onSymbol*(sym: var Symbol) {.exportc, dynlib.} =
if sym.kind == nskProc and sym.name.contains("SDL_"):
sym.name = sym.name.replace("SDL_", "")
cPluginHelper(body.repr)
macro cPluginPath*(path: static[string]): untyped =
## Rather than embedding the `cPlugin()` code within the wrapper, it might be
## preferable to have it stored in a separate source file. This allows for reuse
## across multiple wrappers when applicable.
##
## The `cPluginPath()` macro enables this functionality - specify the path to the
## plugin file and it will be consumed in the same way as `cPlugin()`.
##
## `path` is relative to the current dir and not necessarily relative to the
## location of the wrapper file. Use `currentSourcePath` to specify a path relative
## to the wrapper file.
##
## Unlike `cPlugin()`, this macro also does not implicitly import any other modules
## since the standalone plugin file will need explicit imports for `nim check` and
## suggestions to work. `import nimterop/plugin` is required for all plugins.
doAssert fileExists(path), "Plugin file not found: " & path
cPluginHelper(readFile(path), imports = "")
proc cSearchPath*(path: string): string {.compileTime.}=
## Get full path to file or directory `path` in search path configured
## using `cAddSearchDir() <cimport.html#cAddSearchDir%2Cstring>`_ and
## `cAddStdDir() <cimport.html#cAddStdDir,string>`_.
##
## This can be used to locate files or directories that can be passed onto
## `cCompile() <cimport.html#cCompile.m%2C%2Cstring%2Cstring>`_,
## `cIncludeDir() <cimport.html#cIncludeDir.m>`_ and
## `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_.
result = findPath(path, fail = false)
if result.Bl:
var found = false
for inc in gStateCT.searchDirs:
result = findPath(inc / path, fail = false)
if result.nBl:
found = true
break
doAssert found, "File or directory not found: " & path &
" gStateCT.searchDirs: " & $gStateCT.searchDirs
proc cDebug*() {.compileTime.} =
## Enable debug messages and display the generated Nim code
gStateCT.debug = true
build.gDebugCT = true
proc cDisableCaching*() {.compileTime.} =
## Disable caching of generated Nim code - useful during wrapper development
##
## If files included by header being processed by
## `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## change and affect the generated content, they will be ignored and the cached
## value will continue to be used . Use `cDisableCaching() <cimport.html#cDisableCaching>`_
## to avoid this scenario during development.
##
## `nim -f` was broken prior to 0.19.4 but can also be used to flush the cached content.
gStateCT.nocache = true
macro cDefine*(name: static string, val: static string = ""): untyped =
## `#define` an identifer that is forwarded to the C/C++ preprocessor if
## called within `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## or `c2nImport() <cimport.html#c2nImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## as well as to the C/C++ compiler during Nim compilation using `{.passC: "-DXXX".}`
result = newNimNode(nnkStmtList)
var str = name
# todo: see https://github.com/nimterop/nimterop/issues/100 for
# edge case of empty strings
if val.nBl:
str &= &"={val.quoteShell}"
if str notin gStateCT.defines:
gStateCT.defines.add(str)
str = "-D" & str
result.add quote do:
{.passC: `str`.}
if gStateCT.debug:
echo result.repr & "\n"
proc cAddSearchDir*(dir: string) {.compileTime.} =
## Add directory `dir` to the search path used in calls to
## `cSearchPath() <cimport.html#cSearchPath,string>`_.
runnableExamples:
import nimterop/paths, os
static:
cAddSearchDir testsIncludeDir()
doAssert cSearchPath("test.h").existsFile
var dir = interpPath(dir)
if dir notin gStateCT.searchDirs:
gStateCT.searchDirs.add(dir)
macro cIncludeDir*(dir: static string): untyped =
## Add an include directory that is forwarded to the C/C++ preprocessor if
## called within `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## or `c2nImport() <cimport.html#c2nImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## as well as to the C/C++ compiler during Nim compilation using `{.passC: "-IXXX".}`.
var dir = interpPath(dir)
result = newNimNode(nnkStmtList)
let fullpath = findPath(dir)
if fullpath notin gStateCT.includeDirs:
gStateCT.includeDirs.add(fullpath)
let str = &"-I{fullpath.quoteShell}"
result.add quote do:
{.passC: `str`.}
if gStateCT.debug:
echo result.repr
proc cAddStdDir*(mode = "c") {.compileTime.} =
## Add the standard `c` [default] or `cpp` include paths to search
## path used in calls to `cSearchPath() <cimport.html#cSearchPath,string>`_
runnableExamples:
static: cAddStdDir()
import os
doAssert cSearchPath("math.h").existsFile
for inc in getGccPaths(mode):
cAddSearchDir inc
macro cCompile*(path: static string, mode = "c", exclude = ""): untyped =
## Compile and link C/C++ implementation into resulting binary using `{.compile.}`
##
## `path` can be a specific file or contain wildcards:
##
## .. code-block:: nim
##
## cCompile("file.c")
## cCompile("path/to/*.c")
##
## `mode` recursively searches for code files in `path`.
##
## `c` searches for `*.c` whereas `cpp` searches for `*.C *.cpp *.c++ *.cc *.cxx`
##
## .. code-block:: nim
##
## cCompile("path/to/dir", "cpp")
##
## `exclude` can be used to exclude files by partial string match. Comma separated to
## specify multiple exclude strings
##
## .. code-block:: nim
##
## cCompile("path/to/dir", exclude="test2.c")
result = newNimNode(nnkStmtList)
var
stmt = ""
proc fcompile(file: string): string =
let
(_, fn, ext) = file.splitFile()
var
ufn = fn
uniq = 1
while ufn in gStateCT.compile:
ufn = fn & $uniq
uniq += 1
# - https://github.com/nim-lang/Nim/issues/10299
# - https://github.com/nim-lang/Nim/issues/10486
gStateCT.compile.add(ufn)
if fn == ufn:
return "{.compile: \"$#\".}\n" % file.replace("\\", "/")
else:
# - https://github.com/nim-lang/Nim/issues/9370
let
hash = file.hash().abs()
tmpFile = file.parentDir() / &"_nimterop_{$hash}_{ufn}{ext}"
if not tmpFile.fileExists() or file.getFileDate() > tmpFile.getFileDate():
cpFile(file, tmpFile)
return "{.compile: \"$#\".}\n" % tmpFile.replace("\\", "/")
# Due to https://github.com/nim-lang/Nim/issues/9863
# cannot use seq[string] for excludes
proc notExcluded(file, exclude: string): bool =
result = true
if "_nimterop_" in file:
result = false
elif exclude.nBl:
for excl in exclude.split(","):
if excl in file:
result = false
proc dcompile(dir, exclude: string, ext=""): string =
let
files = walkDirImpl(dir, ext)
for f in files:
if f.nBl and f.notExcluded(exclude):
result &= fcompile(f)
if path.contains("*") or path.contains("?"):
stmt &= dcompile(path, exclude.strVal())
else:
let fpath = findPath(path)
if fileExists(fpath) and fpath.notExcluded(exclude.strVal()):
stmt &= fcompile(fpath)
elif dirExists(fpath):
if mode.strVal().contains("cpp"):
for i in @["*.cpp", "*.c++", "*.cc", "*.cxx"]:
stmt &= dcompile(fpath, exclude.strVal(), i)
when not defined(Windows):
stmt &= dcompile(fpath, exclude.strVal(), "*.C")
else:
stmt &= dcompile(fpath, exclude.strVal(), "*.c")
result.add stmt.parseStmt()
if gStateCT.debug:
echo result.repr
macro cImport*(filenames: static seq[string], recurse: static bool = false, dynlib: static string = "",
mode: static string = "c", flags: static string = ""): untyped =
## Import multiple headers in one shot
##
## This macro is preferable over multiple individual `cImport()` calls, especially
## when the headers might `#include` the same headers and result in duplicate symbols.
result = newNimNode(nnkStmtList)
var
fullpaths: seq[string]
for filename in filenames:
fullpaths.add findPath(filename)
# In case cOverride called after cPlugin
if gStateCT.pluginSourcePath.Bl:
cPluginHelper(gStateCT.pluginSource)
echo "# Importing " & fullpaths.join(", ").sanitizePath
let
output = getToast(fullpaths, recurse, dynlib, mode, flags)
# Reset plugin and overrides for next cImport
if gStateCT.overrides.nBl:
gStateCT.pluginSourcePath = ""
gStateCT.overrides = ""
if gStateCT.debug:
echo output
try:
let body = parseStmt(output)
result.add body
except:
let
(tmpFile, errors) = getNimCheckError(output)
doAssert false, errors & "\n\nNimterop codegen limitation or error - review 'nim check' output above generated for " & tmpFile
macro cImport*(filename: static string, recurse: static bool = false, dynlib: static string = "",
mode: static string = "c", flags: static string = ""): untyped =
## Import all supported definitions from specified header file. Generated
## content is cached in `nimcache` until `filename` changes unless
## `cDisableCaching() <cimport.html#cDisableCaching>`_ is set. `nim -f`
## can also be used after Nim v0.19.4 to flush the cache.
##
## `recurse` can be used to generate Nim wrappers from `#include` files
## referenced in `filename`. This is only done for files in the same
## directory as `filename` or in a directory added using
## `cIncludeDir() <cimport.html#cIncludeDir.m>`_
##
## `dynlib` can be used to specify the Nim string to use to specify the dynamic
## library to load the imported symbols from. For example:
##
## .. code-block:: nim
##
## const
## dynpcre =
## when defined(Windows):
## when defined(cpu64):
## "pcre64.dll"
## else:
## "pcre32.dll"
## elif hostOS == "macosx":
## "libpcre(.3|.1|).dylib"
## else:
## "libpcre.so(.3|.1|)"
##
## cImport("pcre.h", dynlib="dynpcre")
##
## If `dynlib` is not specified, the C/C++ implementation files can be compiled in
## with `cCompile() <cimport.html#cCompile.m%2C%2Cstring%2Cstring>`_, or the
## `{.passL.}` pragma can be used to specify the static lib to link.
##
## `mode` selects the preprocessor and tree-sitter parser to be used to process
## the header.
##
## `flags` can be used to pass any other command line arguments to `toast`. A
## good example would be `--prefix` and `--suffix` which strip leading and
## trailing strings from identifiers, `_` being quite common.
##
## `cImport()` consumes and resets preceding `cOverride()` calls. `cPlugin()`
## is retained for the next `cImport()` call unless a new `cPlugin()` call is
## defined.
return quote do:
cImport(@[`filename`], bool(`recurse`), `dynlib`, `mode`, `flags`)
macro c2nImport*(filename: static string, recurse: static bool = false, dynlib: static string = "",
mode: static string = "c", flags: static string = ""): untyped =
## Import all supported definitions from specified header file using `c2nim`
##
## Similar to `cImport() <cimport.html#cImport.m%2C%2Cstring%2Cstring%2Cstring>`_
## but uses `c2nim` to generate the Nim wrapper instead of `toast`. Note that neither
## `cOverride() <cimport.html#cOverride.m>`_, `cSkipSymbol() <cimport.html#cSkipSymbol%2Cseq[T][string]>`_
## nor `cPlugin() <cimport.html#cPlugin.m>`_ have any impact on `c2nim`.
##
## `toast` is only used to preprocess the header file and recurse
## if specified.
##
## `mode` should be set to `cpp` for c2nim to wrap C++ headers.
##
## `flags` can be used to pass other command line arguments to `c2nim`.
##
## `nimterop` does not depend on `c2nim` as a `nimble` dependency so it
## does not get installed automatically. Any wrapper or library that requires this proc
## needs to install `c2nim` with `nimble install c2nim` or add it as a dependency in
## its own `.nimble` file.
result = newNimNode(nnkStmtList)
let
fullpath = findPath(filename)
echo "# Importing " & fullpath & " with c2nim"
let
output = getToast(@[fullpath], recurse, dynlib, mode, noNimout = true)
hash = output.hash().abs()
hpath = getProjectCacheDir("c2nimCache", forceClean = false) / "nimterop_" & $hash & ".h"
npath = hpath[0 .. hpath.rfind('.')] & "nim"
header = ("header" & fullpath.splitFile().name.replace(re"[-.]+", ""))
if not fileExists(hpath) or gStateCT.nocache or compileOption("forceBuild"):
mkDir(hpath.parentDir())
writeFile(hpath, output)
doAssert fileExists(hpath), "Unable to write temporary header file: " & hpath
var
cmd = when defined(Windows): "cmd /c " else: ""
cmd &= &"c2nim {hpath} --header:{header}"
if dynlib.nBl:
cmd.add &" --dynlib:{dynlib}"
if mode.contains("cpp"):
cmd.add " --cpp"
if flags.nBl:
cmd.add &" {flags}"
for i in gStateCT.defines:
cmd.add &" --assumedef:{i.quoteShell}"
let
(c2nimout, ret) = execAction(cmd, cache = not gStateCT.nocache,
cacheKey = getCacheValue(hpath))
doAssert ret == 0, "\n\nc2nim codegen limitation or error - " & c2nimout
var
nimout = &"const {header} = \"{fullpath}\"\n\n" & readFile(npath)
nimout = nimout.
replace(re"([u]?int[\d]+)_t", "$1").
replace(re"([u]?int)ptr_t", "ptr $1")
if gStateCT.debug:
echo nimout
try:
let body = parseStmt(nimout)
result.add body
except:
let
(tmpFile, errors) = getNimCheckError(nimout)
doAssert false, errors & "\n\nc2nim codegen limitation or error - review 'nim check' output above generated for " & tmpFile