nimterop/nimterop/getters.nim
Ganesh Viswanathan 2bad84c4c1 Fix #123
2019-04-06 18:33:40 -05:00

367 lines
9.7 KiB
Nim

import dynlib, macros, os, sequtils, sets, strformat, strutils, tables, times
import regex
import "."/[git, 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",
# int
"int": "cint",
"signed": "cint",
"signed int": "cint",
"ssize_t": "cint",
"unsigned": "cuint",
"unsigned int": "cuint",
"size_t": "cuint",
# long
"long": "clong",
"long int": "clong",
"signed long": "clong",
"signed long int": "clong",
"off_t": "clong",
"unsigned long": "culong",
"unsigned long int": "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 sanitizePath*(path: string): string =
path.multiReplace([("\\\\", $DirSep), ("\\", $DirSep), ("/", $DirSep)])
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.len != 0:
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(re"_[_]+")): errmsg & " more than one consecutive underscore '_'"
if parent.nBl:
doAssert name.nBl, &"Blank identifier, originally '{parentStr}{name}' ({kind}), cannot be empty"
proc getIdentifier*(name: string, kind: NimSymKind, parent=""): string =
doAssert name.len != 0, "Blank identifier error"
if name notin gStateRT.symOverride or parent.nBl:
if gStateRT.onSymbol != nil:
var
sym = Symbol(name: name, parent: parent, kind: kind)
gStateRT.onSymbol(sym)
result = sym.name
else:
result = name
checkIdentifier(result, $kind, parent, name)
if result in gReserved or (result == "object" and kind != nskType):
result = &"`{result}`"
else:
result = ""
proc getUniqueIdentifier*(existing: TableRef[string, string], prefix = ""): string =
var
name = prefix & "_" & gStateRT.sourceFile.extractFilename().multiReplace([(".", ""), ("-", "")])
nimName = name[0] & name[1 .. ^1].replace("_", "").toLowerAscii
count = 1
while (nimName & $count) in existing:
count += 1
return name & $count
proc addNewIdentifer*(existing: var TableRef[string, string], name: string): bool =
if name notin gStateRT.symOverride:
let
nimName = name[0] & name[1 .. ^1].replace("_", "").toLowerAscii
if existing.hasKey(nimName):
doAssert name == existing[nimName], &"Identifier '{name}' is a stylistic duplicate of identifier '{existing[nimName]}', use 'cPlugin:onSymbol()' to rename"
result = false
else:
existing[nimName] = name
result = true
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 =
if str.contains(re"^[\-]?[\d]+$") or
str.contains(re"^[\-]?[\d]*\.[\d]+$") or
str.contains(re"^0x[\d]+$"):
return str
proc getNodeVal*(node: TSNode): string =
return gStateRT.code[node.tsNodeStartByte() .. node.tsNodeEndByte()-1].strip()
proc getNodeValIf*(node: TSNode, esym: string): string =
if esym != $node.tsNodeType():
return
return node.getNodeVal()
proc getLineCol*(node: TSNode): tuple[line, col: int] =
result.line = 1
result.col = 1
for i in 0 .. node.tsNodeStartByte().int-1:
if gStateRT.code[i] == '\n':
result.col = 0
result.line += 1
result.col += 1
proc getCurrentHeader*(fullpath: string): string =
("header" & fullpath.splitFile().name.replace(re"[-.]+", ""))
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
cmd = &"gcc -E -dD -x{mmode} -w "
rdata: seq[string] = @[]
start = false
sfile = fullpath.sanitizePath
for inc in gStateRT.includeDirs:
cmd &= &"-I{inc.quoteShell} "
for def in gStateRT.defines:
cmd &= &"-D{def} "
cmd &= &"{fullpath.quoteShell}"
# Include content only from file
for line in execAction(cmd).splitLines():
if line.strip() != "":
if line.len > 1 and line[0 .. 1] == "# ":
start = false
let
saniLine = line.sanitizePath
if sfile in saniLine:
start = true
elif not ("\\" in line) and not ("/" in line) and extractFilename(sfile) in line:
start = true
elif gStateRT.recurse:
let
pDir = sfile.expandFilename().parentDir().sanitizePath()
if pDir.len == 0 or pDir in saniLine:
start = true
else:
for inc in gStateRT.includeDirs:
if inc.absolutePath().sanitizePath in saniLine:
start = true
break
else:
if start:
if "#undef" in line:
continue
rdata.add(
line.
replace("__restrict", "").
replace(re"__attribute__[ ]*\(\(.*?\)\)([ ,;])", "$1")
)
return rdata.join("\n").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]
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*(expr: string): string =
return expr.multiReplace([
(" ", ""),
("<<", " shl "), (">>", " shr "),
("^", " xor "), ("&", " and "), ("|", " or "),
("~", " not ")
])
proc getSplitComma*(joined: seq[string]): seq[string] =
for i in joined:
result = result.concat(i.split(","))
proc dll*(path: string): string =
let
(dir, name, _) = path.splitFile()
result = dir / (DynlibFormat % name)
proc loadPlugin*(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("nim c --app:lib " & sourcePath.quoteShell)
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]
gStateRT.onSymbol = cast[OnSymbol](lib.symAddr("onSymbol"))
doAssert gStateRT.onSymbol != nil, "onSymbol() load failed from " & pdll