nimterop/nimterop/getters.nim
2019-12-21 23:29:57 -06:00

510 lines
14 KiB
Nim

import dynlib, macros, os, sequtils, sets, strformat, strutils, tables, times
import regex
import "."/[build, compat, globals, plugin, treesitter/api]
const gReserved = """
addr and as asm
bind block break
case cast concept const continue converter
defer discard distinct div do
elif else end enum except export
finally for from func
if import in include interface is isnot iterator
let
macro method mixin mod
nil not notin
of or out
proc ptr
raise ref return
shl shr static
template try tuple type
using
var
when while
xor
yield""".split(Whitespace).toSet()
const gTypeMap* = {
# char
"char": "cchar",
"signed char": "cschar",
"unsigned char": "cuchar",
# short
"short": "cshort",
"short int": "cshort",
"signed short": "cshort",
"signed short int": "cshort",
"unsigned short": "cushort",
"unsigned short int": "cushort",
"uShort": "cushort",
"u_short": "cushort",
# int
"int": "cint",
"signed": "cint",
"signed int": "cint",
"ssize_t": "int",
"unsigned": "cuint",
"unsigned int": "cuint",
"uInt": "cuint",
"u_int": "cuint",
"size_t": "uint",
# long
"long": "clong",
"long int": "clong",
"signed long": "clong",
"signed long int": "clong",
"off_t": "clong",
"unsigned long": "culong",
"unsigned long int": "culong",
"uLong": "culong",
"u_long": "culong",
# long long
"long long": "clonglong",
"long long int": "clonglong",
"signed long long": "clonglong",
"signed long long int": "clonglong",
"off64_t": "clonglong",
"unsigned long long": "culonglong",
"unsigned long long int": "culonglong",
# floating point
"float": "cfloat",
"double": "cdouble",
"long double": "clongdouble"
}.toTable()
proc getType*(str: string): string =
if str == "void":
return "object"
result = str.strip(chars={'_'}).
replace(re"\s+", " ").
replace(re"^([u]?int[\d]+)_t$", "$1").
replace(re"^([u]?int)ptr_t$", "ptr $1")
if gTypeMap.hasKey(result):
result = gTypeMap[result]
proc checkIdentifier(name, kind, parent, origName: string) =
let
parentStr = if parent.nBl: parent & ":" else: ""
if name.nBl:
let
origStr = if name != origName: &", originally '{origName}' before 'cPlugin:onSymbol()', still" else: ""
errmsg = &"Identifier '{parentStr}{name}' ({kind}){origStr} contains"
doAssert name[0] != '_' and name[^1] != '_', errmsg & " leading/trailing underscores '_'"
doAssert (not name.contains("__")): errmsg & " consecutive underscores '_'"
if parent.nBl:
doAssert name.nBl, &"Blank identifier, originally '{parentStr}{origName}' ({kind}), cannot be empty"
proc getIdentifier*(nimState: NimState, name: string, kind: NimSymKind, parent=""): string =
doAssert name.nBl, "Blank identifier error"
if name notin nimState.gState.symOverride or parent.nBl:
if nimState.gState.onSymbol != nil:
# Use onSymbol from plugin provided by user
var
sym = Symbol(name: name, parent: parent, kind: kind)
nimState.gState.onSymbol(sym)
result = sym.name
else:
result = name
# Strip out --prefix from CLI if specified
for str in nimState.gState.prefix:
if result.startsWith(str):
result = result[str.len .. ^1]
# Strip out --suffix from CLI if specified
for str in nimState.gState.suffix:
if result.endsWith(str):
result = result[0 .. ^(str.len+1)]
checkIdentifier(result, $kind, parent, name)
if result in gReserved or (result == "object" and kind != nskType):
# Enclose in backticks since Nim reserved word
result = &"`{result}`"
else:
# Skip identifier since in symOverride
result = ""
proc getUniqueIdentifier*(nimState: NimState, prefix = ""): string =
var
name = prefix & "_" & nimState.sourceFile.extractFilename().multiReplace([(".", ""), ("-", "")])
nimName = name[0] & name[1 .. ^1].replace("_", "").toLowerAscii
count = 1
while (nimName & $count) in nimState.identifiers:
count += 1
return name & $count
proc addNewIdentifer*(nimState: NimState, name: string, override = false): bool =
if override or name notin nimState.gState.symOverride:
let
nimName = name[0] & name[1 .. ^1].replace("_", "").toLowerAscii
if nimState.identifiers.hasKey(nimName):
doAssert name == nimState.identifiers[nimName],
&"Identifier '{name}' is a stylistic duplicate of identifier " &
&"'{nimState.identifiers[nimName]}', use 'cPlugin:onSymbol()' to rename"
result = false
else:
nimState.identifiers[nimName] = name
result = true
proc getOverride*(nimState: NimState, name: string, kind: NimSymKind): string =
doAssert name.nBl, "Blank identifier error"
if nimState.gState.onSymbolOverride != nil:
var
nname = nimState.getIdentifier(name, kind, "Override")
sym = Symbol(name: nname, kind: kind)
if nname.nBl:
nimState.gState.onSymbolOverride(sym)
if sym.override.nBl and nimState.addNewIdentifer(nname, override = true):
result = sym.override
if kind != nskProc:
result = result.replace(re"(?m)^(.*?)$", " $1")
proc getOverrideFinal*(nimState: NimState, kind: NimSymKind): string =
let
typ = $kind
if nimState.gState.onSymbolOverrideFinal != nil:
for i in nimState.gState.onSymbolOverrideFinal(typ):
result &= "\n" & nimState.getOverride(i, kind)
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"
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 getNodeVal*(nimState: NimState, node: TSNode): string =
return nimState.gState.code[node.tsNodeStartByte() .. node.tsNodeEndByte()-1].strip()
proc getLineCol*(gState: State, node: TSNode): tuple[line, col: int] =
result.line = 1
result.col = 1
for i in 0 .. node.tsNodeStartByte().int-1:
if gState.code[i] == '\n':
result.col = 0
result.line += 1
result.col += 1
proc getCurrentHeader*(fullpath: string): string =
("header" & fullpath.splitFile().name.multiReplace([(".", ""), ("-", "")]))
proc removeStatic(content: string): string =
## Replace static function bodies with a semicolon and commented
## out body
return content.replace(
re"(?msU)static inline ([^)]+\))([^}]+\})",
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*(gState: State, fullpath: string, mode = "cpp"): string =
var
mmode = if mode == "cpp": "c++" else: mode
cmts = if gState.nocomments: "" else: "-CC"
cmd = &"""{getCompiler()} -E {cmts} -dD -x{mmode} -w """
rdata: seq[string] = @[]
start = false
sfile = fullpath.sanitizePath(noQuote = true)
for inc in gState.includeDirs:
cmd &= &"-I{inc.sanitizePath} "
for def in gState.defines:
cmd &= &"-D{def} "
cmd &= &"{fullpath.sanitizePath}"
# Include content only from file
for line in execAction(cmd).output.splitLines():
if line.strip() != "":
if line.len > 1 and line[0 .. 1] == "# ":
start = false
let
saniLine = line.sanitizePath(noQuote = true)
if sfile in saniLine:
start = true
elif not ("\\" in line) and not ("/" in line) and extractFilename(sfile) in line:
start = true
elif gState.recurse:
let
pDir = sfile.expandFilename().parentDir().sanitizePath(noQuote = true)
if pDir.Bl or pDir in saniLine:
start = true
else:
for inc in gState.includeDirs:
if inc.absolutePath().sanitizePath(noQuote = true) in saniLine:
start = true
break
else:
if start:
if "#undef" in line:
continue
rdata.add line
return rdata.join("\n").
replace("__restrict", "").
replace(re"__attribute__[ ]*\(\(.*?\)\)([ ,;])", "$1").
removeStatic()
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 getTSNodeNamedChildCountSansComments*(node: TSNode): int =
if node.tsNodeNamedChildCount() != 0:
for i in 0 .. node.tsNodeNamedChildCount()-1:
if $node.tsNodeType() != "comment":
result += 1
proc getTSNodeNamedChildNames*(node: TSNode): seq[string] =
if node.tsNodeNamedChildCount() != 0:
for i in 0 .. node.tsNodeNamedChildCount()-1:
let
name = $node.tsNodeNamedChild(i).tsNodeType()
if name != "comment":
result.add(name)
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 getPxName*(node: TSNode, offset: int): string =
var
np = node
count = 0
while not np.tsNodeIsNull() and count < offset:
np = np.tsNodeParent()
count += 1
if count == offset and not np.tsNodeIsNull():
return $np.tsNodeType()
proc getNimExpression*(nimState: NimState, expr: string): string =
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:
ident = nimState.getIdentifier(ident, nskConst)
result &= ident
ident = ""
result &= gen
gen = ""
# Convert shift ops to Nim
result = result.multiReplace([
("<<", " shl "), (">>", " shr ")
])
proc getSplitComma*(joined: seq[string]): seq[string] =
for i in joined:
result = result.concat(i.split(","))
proc getHeader*(nimState: NimState): string =
result =
if nimState.gState.dynlib.Bl:
&", header: {nimState.currentHeader}"
else:
""
proc getDynlib*(nimState: NimState): string =
result =
if nimState.gState.dynlib.nBl:
&", dynlib: {nimState.gState.dynlib}"
else:
""
proc getImportC*(nimState: NimState, origName, nimName: string): string =
if nimName != origName:
result = &"importc: \"{origName}\"{nimState.getHeader()}"
else:
result = nimState.impShort
proc getPragma*(nimState: NimState, 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(nimState.impShort & ", cdecl", nimState.impShort & "C")
let
dy = nimState.getDynlib()
if ", cdecl" in result and dy.nBl:
result = result.replace(".}", dy & ".}")
proc getComments*(nimState: NimState, strip = false): string =
if not nimState.gState.nocomments and nimState.commentStr.nBl:
result = "\n" & nimState.commentStr
if strip:
result = result.replace("\n ", "\n")
nimState.commentStr = ""
proc dll*(path: string): string =
let
(dir, name, _) = path.splitFile()
result = dir / (DynlibFormat % name)
proc loadPlugin*(gState: State, sourcePath: string) =
doAssert fileExists(sourcePath), "Plugin file does not exist: " & sourcePath
let
pdll = sourcePath.dll
if not fileExists(pdll) or
sourcePath.getLastModificationTime() > pdll.getLastModificationTime():
discard execAction(&"{gState.nim.sanitizePath} c --app:lib {sourcePath.sanitizePath}")
doAssert fileExists(pdll), "No plugin binary generated for " & sourcePath
let lib = loadLib(pdll)
doAssert lib != nil, "Plugin $1 compiled to $2 failed to load" % [sourcePath, pdll]
gState.onSymbol = cast[OnSymbol](lib.symAddr("onSymbol"))
gState.onSymbolOverride = cast[OnSymbol](lib.symAddr("onSymbolOverride"))
gState.onSymbolOverrideFinal = cast[OnSymbolOverrideFinal](lib.symAddr("onSymbolOverrideFinal"))
proc expandSymlinkAbs*(path: string): string =
try:
result = path.expandSymlink().absolutePath(path.parentDir()).myNormalizedPath()
except:
result = path