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