├── .gitignore ├── examples └── macrorepl │ ├── config.nims │ ├── macrorepl.nim │ ├── macrosports.nim │ └── test.nims ├── license ├── nimscripter.nimble ├── readme.md ├── src ├── nimscripter.nim └── nimscripter │ ├── expose.nim │ ├── variables.nim │ ├── vmaddins.nim │ ├── vmconversion.nim │ └── vmops.nim └── tests ├── config.nims ├── example ├── first.nims └── objects.nim ├── taddinvariable.nim ├── texamples.nim ├── tgeneral.nim ├── tpegs.nim └── tvmops.nim /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.* 3 | !*/ 4 | *.dll 5 | *.exe 6 | *.kate-swap 7 | nimcache/ 8 | nimblecache/ 9 | htmldocs/ 10 | -------------------------------------------------------------------------------- /examples/macrorepl/config.nims: -------------------------------------------------------------------------------- 1 | --path:"$nim" -------------------------------------------------------------------------------- /examples/macrorepl/macrorepl.nim: -------------------------------------------------------------------------------- 1 | import nimscripter 2 | import std/[strscans, strutils, parseutils, os, times, strformat, linenoise, terminal] 3 | import packages/docutils/highlite 4 | 5 | 6 | type 7 | RenderMode = enum 8 | astRender, codeRender 9 | 10 | RenderSetting = enum 11 | showIndex 12 | useColor 13 | 14 | ColoredToken = enum 15 | operator, keyword, ident, strlit, numlit 16 | 17 | ColoredCode = distinct string 18 | 19 | RenderSettings = set[RenderSetting] 20 | 21 | AstNode = ref object 22 | name: string 23 | value: string 24 | children: seq[AstNode] 25 | parent: AstNode 26 | 27 | var 28 | renderMode = astRender 29 | renderSettings: RenderSettings 30 | indexColor = fgGreen 31 | nameColors = (fgWhite, fgWhite) 32 | tokenColors = [ 33 | operator: fgYellow, 34 | keyword: fgRed, 35 | ident: fgBlue, 36 | strLit: fgGreen, 37 | numLit: fgCyan 38 | ] 39 | 40 | 41 | proc addName(s: var string, name: string, depth: int) = 42 | if useColor in renderSettings: 43 | s.add: 44 | ansiForegroundColorCode: 45 | if depth mod 2 == 0: 46 | nameColors[0] 47 | else: 48 | nameColors[1] 49 | s.add name 50 | if useColor in renderSettings: 51 | s.add ansiResetCode 52 | 53 | proc addIndex(s: var string, index: int) = 54 | if useColor in renderSettings: 55 | s.add ansiForegroundColorCode(indexColor) 56 | s.add fmt"[{index}]" 57 | 58 | if useColor in renderSettings: 59 | s.add ansiResetCode 60 | 61 | proc toString(ast: AstNode, depth, index: int): string = 62 | result = repeat(" ", depth.Natural) 63 | result.addName(ast.name, depth) 64 | if ast.value.len > 0: 65 | result.add &", {ast.value}" 66 | 67 | if showIndex in renderSettings and depth > 0: 68 | result.addIndex(index) 69 | 70 | result.add "\n" 71 | for i, x in ast.children: 72 | result.add x.toString(depth + 1, i) 73 | 74 | proc `$`(ast: AstNode): string = 75 | for i, x in ast.children: 76 | result.add x.toString(0, i) 77 | 78 | proc renderAst(s: string): AstNode = 79 | new result 80 | var 81 | indent = 0 82 | presentNode = result 83 | for line in s.splitLines: 84 | let 85 | start = skipWhitespace(line) 86 | newIndent = start div 2 87 | line = line[start..^1] 88 | 89 | var name, value: string 90 | 91 | if newIndent < indent: 92 | for _ in 0.. 0: 137 | echo s[offset .. ^1] 138 | else: 139 | echo s 140 | 141 | 142 | proc recieveData(codeRepr, astRepr: string) = 143 | clearScreen() 144 | setCursorPos(0, 0) 145 | case renderMode: 146 | of astRender: 147 | echo renderAst(astRepr) 148 | of codeRender: 149 | if useColor in renderSettings: 150 | renderCode(ColoredCode(codeRepr)) 151 | else: 152 | renderCode(codeRepr) 153 | 154 | proc setIndexColor(col: ForegroundColor) = indexColor = col 155 | proc setColors(col: (ForegroundColor, ForegroundColor)) = nameColors = col 156 | 157 | proc setRenderSettings(settings: RenderSettings) = 158 | renderSettings = settings 159 | 160 | proc setRenderMode(mode: RenderMode) = renderMode = mode 161 | 162 | exportTo(macrosport, 163 | recieveData, 164 | RenderSetting, 165 | RenderSettings, 166 | setRenderSettings, 167 | ForegroundColor, 168 | setIndexColor, 169 | setColors, 170 | RenderMode, 171 | setRenderMode 172 | ) 173 | 174 | const additions = implNimscriptModule(macrosport) 175 | let filePath = paramStr(1) 176 | try: 177 | var intr: Option[Interpreter] 178 | var lastMod: Time 179 | while true: 180 | let currMod = getLastModificationTime(filePath) 181 | if lastMod < currMod: 182 | lastMod = currMod 183 | intr.safeloadScriptWithState(NimScriptPath(filePath), additions, modules = ["macrosports"]) 184 | 185 | 186 | sleep(10) # TODO Use selectors 187 | 188 | except Exception as e: 189 | echo e.msg 190 | -------------------------------------------------------------------------------- /examples/macrorepl/macrosports.nim: -------------------------------------------------------------------------------- 1 | import std/macros 2 | macro untypedRepl*(body: untyped) = 3 | newCall("recieveData", newLit(body.repr), newLit(body.treeRepr)) 4 | 5 | macro typedRepl*(body: typed) = 6 | newCall("recieveData", newLit(body.repr), newLit(body.treeRepr)) -------------------------------------------------------------------------------- /examples/macrorepl/test.nims: -------------------------------------------------------------------------------- 1 | import std/macros 2 | setRenderSettings({showIndex, useColor}) 3 | setColors (fgRed, fgGreen) 4 | setIndexColor(fgCyan) 5 | setRenderMode(codeRender) 6 | 7 | macro doStuff(a: int): untyped = 8 | result = newStmtList() 9 | for x in 0..a.intVal: 10 | result.add newCall("echo", newLit(x)) 11 | 12 | template doStufff() = 13 | echo "hello world" 14 | 15 | 16 | typedRepl(doStuff(3)) -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jason Beetham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /nimscripter.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "1.1.6" 4 | author = "Jason Beetham" 5 | description = "A easy to use Nimscript interop package" 6 | license = "MIT" 7 | srcDir = "src" 8 | 9 | 10 | # Dependencies 11 | requires "nim >= 1.6.0" # need some bug fixes 12 | requires "https://github.com/disruptek/assume >= 0.7.1" 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Nimscripter 2 | Nimscripter is enables easy interop between Nim and Nimscript for realtime scriptable applications. 3 | 4 | ## How to use 5 | Install Nimscripter(`nimble install nimscripter`) with Nimble then create a .nim file with the following. 6 | 7 | ```nim 8 | import nimscripter 9 | proc doThing(): int = 42 10 | exportTo(myImpl, doThing) # The name of our "nimscript module" is `myImpl` 11 | const 12 | scriptProcs = implNimScriptModule(myImpl) # This emits our exported code 13 | ourScript = NimScriptFile("assert doThing() == 42") # Convert to `NimScriptFile` for loading from strings 14 | let intr = loadScript(ourScript, scriptProcs) # Load our script with our code and using our system `stdlib`(not portable) 15 | ``` 16 | 17 | Note that `exportTo` can take in multiple procedures, types, or global variables at once. 18 | 19 | ```nim 20 | proc doThing(): int = 42 21 | var myGlobal = 30 22 | type MyType = enum 23 | a, b, c 24 | exportTo(myImpl, 25 | doThing 26 | myGlobal, 27 | myType) 28 | ``` 29 | 30 | ### Calling code from Nim 31 | Any exported non overloaded and non generic procedures can be called from Nim 32 | ```nim 33 | const script = NimScriptFile"proc fancyStuff*(a: int) = assert a in [10, 300]" # Notice `fancyStuff` is exported 34 | let intr = loadScript(script) # We are not exposing any procedures hence single parameter 35 | intr.invoke(fancyStuff, 10) # Calls `fancyStuff(10)` in vm 36 | intr.invoke(fancyStuff, 300) # Calls `fancyStuff(300)` in vm 37 | ``` 38 | 39 | The above works but does not impose any safety on the VM code, to do that the following can be done 40 | ```nim 41 | addCallable(test3): 42 | proc fancyStuff(a: int) # Has checks for the nimscript to ensure it's definition doesnt change to something unexpected. 43 | const 44 | addins = implNimscriptModule(test3) 45 | script = NimScriptFile"proc fancyStuff*(a: int) = assert a in [10, 300]" # Notice `fancyStuff` is exported 46 | let intr = loadScript(script, addins) # This adds in out checks for the proc 47 | intr.invoke(fancyStuff, 10) # Calls `fancyStuff(10)` in vm 48 | intr.invoke(fancyStuff, 300) # Calls `fancyStuff(300)` in vm 49 | ``` 50 | 51 | ### Getting global variables from nimscript 52 | 53 | One may extract global variables from a nimscript file using a convenience macro. 54 | 55 | ```nim 56 | import nimscripter, nimscripter/variables 57 | 58 | let script = NimScriptFile""" 59 | let required* = "main" 60 | let defaultValueExists* = "foo" 61 | """ 62 | let intr = loadScript script 63 | 64 | getGlobalNimsVars intr: 65 | required: string # required variable 66 | optional: Option[string] # optional variable 67 | defaultValue: int = 1 # optional variable with default value 68 | defaultValueExists = "bar" # You may omit the type if there is a default value 69 | 70 | check required == "main" 71 | check optional.isNone 72 | check defaultValue == 1 73 | check defaultValueExists == "foo" 74 | 75 | ``` 76 | Basic types are supported, such as string, int, bool, etc.. 77 | 78 | 79 | ### Exporting code verbatim 80 | `nimscriptr/expose` has `exportCode` and `exportCodeAndKeep` they both work the same, except the latter keeps the code so it can be used inside Nim. 81 | ```nim 82 | exportCode(nimScripter): 83 | proc doThing(a, b: int) = echo a, " ", b # This runs on nimscript if called there 84 | ``` 85 | 86 | ### Keeping state inbetween loads 87 | 88 | `loadScriptWithState` will load a script, if it loads a valid script it will reset any global exported variables in the script with their preload values. 89 | 90 | `safeloadScriptWithState` will attempt to load a script keeping global state, if it fails it does not change the interpeter, else it'll load the script and set it's state to the interpreters. 91 | 92 | `saveState`/`loadState` can be used to manually manage the state inbetween loaded scripts. 93 | 94 | ### VmOps 95 | 96 | A subset of the nimscript interopped procedures are available inside `nimscripter/vmops`. 97 | If you feel a new op should be added feel free to PR it. 98 | ```nim 99 | import nimscripter 100 | import nimscripter/vmops 101 | 102 | const script = """ 103 | proc build*(): bool = 104 | echo "building nim... " 105 | echo getCurrentDir() 106 | echo "done" 107 | true 108 | 109 | when isMainModule: 110 | discard build() 111 | """ 112 | addVmops(buildpackModule) 113 | addCallable(buildpackModule): 114 | proc build(): bool 115 | const addins = implNimscriptModule(buildpackModule) 116 | discard loadScript(NimScriptFile(script), addins) 117 | ``` 118 | 119 | ### Using a custom/shipped stdlib 120 | 121 | Make a folder entitled `stdlib` and copy all Nim files you wish to ship as a stdlib from Nim's stdlib and any of your own files. 122 | `system.nim` and the `system` folder are required. 123 | `You can copy any other pure libraries and ship them, though they're only usable if they support Nimscript. 124 | `If you use choosenim you can find the the Nim stdlib to copy from inside `~/.choosenim/toolchains/nim-version/lib`. 125 | When using a custom search paths add the root file only, if you provide more than that it will break modules. 126 | 127 | ### Overriding the error hook 128 | 129 | The error hook can be overridden for more behaviour like showing the error in the program, 130 | the builtin error hook is as follows: 131 | ```nim 132 | proc errorHook(config: ConfigRef; info: TLineInfo; msg: string; severity: Severity) {.gcsafe.} = 133 | if severity == Error and config.error_counter >= config.error_max: 134 | var fileName: string 135 | for k, v in config.m.filenameToIndexTbl.pairs: 136 | if v == info.fileIndex: 137 | fileName = k 138 | echo "Script Error: $1:$2:$3 $4." % [fileName, $info.line, $(info.col + 1), msg] 139 | raise (ref VMQuit)(info: info, msg: msg) 140 | ``` 141 | -------------------------------------------------------------------------------- /src/nimscripter.nim: -------------------------------------------------------------------------------- 1 | import "$nim"/compiler / [nimeval, renderer, ast, llstream, lineinfos, idents, types] 2 | import "$nim"/compiler / options as copts 3 | import std/[os, json, options, strutils, macros, tables] 4 | import nimscripter/[expose, vmaddins, vmconversion] 5 | from "$nim"/compiler/vmdef import TSandboxFlag 6 | export options, Interpreter, ast, lineinfos, idents, nimEval, expose, VMParseError 7 | 8 | const defaultDefines = @{"nimscript": "true", "nimconfig": "true"} 9 | 10 | type 11 | VMQuit* = object of CatchableError 12 | info*: TLineInfo 13 | VMErrorHook* = proc (config: ConfigRef; info: TLineInfo; msg: string; 14 | severity: Severity) {.gcsafe.} 15 | VmProcNotFound* = object of CatchableError 16 | VmSymNotFound* = object of CatchableError 17 | NimScriptFile* = distinct string ## Distinct to load from string 18 | NimScriptPath* = distinct string ## Distinct to load from path 19 | SavedVar = object 20 | name: string 21 | typ: PType 22 | val: Pnode 23 | SaveState* = seq[SavedVar] 24 | 25 | proc getSearchPath(path: string): seq[string] = 26 | result.add path 27 | for dir in walkDirRec(path, {pcDir}): 28 | result.add dir 29 | 30 | proc errorHook(config: ConfigRef; info: TLineInfo; msg: string; severity: Severity) {.gcsafe.} = 31 | if severity == Error and config.error_counter >= config.error_max: 32 | var fileName: string 33 | for k, v in config.m.filenameToIndexTbl.pairs: 34 | if v == info.fileIndex: 35 | fileName = k 36 | echo "Script Error: $1:$2:$3 $4." % [fileName, $info.line, $(info.col + 1), msg] 37 | raise (ref VMQuit)(info: info, msg: msg) 38 | 39 | when declared(nimeval.setGlobalValue): 40 | proc saveState*(intr: Interpreter): SaveState = 41 | for x in intr.exportedSymbols(): 42 | if x.kind in {skVar, skLet}: 43 | let 44 | val = intr.getGlobalValue(x) 45 | typ = x.typ 46 | name = x.name.s 47 | result.add SavedVar(name: name, typ: typ, val: val) 48 | 49 | proc loadState*(intr: Interpreter; state: SaveState) = 50 | for x in state: 51 | let sym = intr.selectUniqueSymbol(x.name, {skLet, skVar}) 52 | if sym != nil and sameType(sym.typ, x.typ): 53 | intr.setGlobalValue(sym, x.val) 54 | 55 | proc loadScript*( 56 | script: NimScriptFile or NimScriptPath; 57 | addins: VMAddins = VMaddins(); 58 | modules: varargs[string]; 59 | vmErrorHook = errorHook; 60 | stdPath = findNimStdlibCompileTime(); 61 | searchPaths: sink seq[string] = @[]; 62 | defines = defaultDefines): Option[Interpreter] = 63 | ## Loads an interpreter from a file or from string, with given addtions and userprocs. 64 | ## To load from the filesystem use `NimScriptPath(yourPath)`. 65 | ## To load from a string use `NimScriptFile(yourFile)`. 66 | ## `addins` is the overrided procs/addons from `impleNimScriptModule 67 | ## `modules` implict imports to add to the module. 68 | ## `stdPath` to use shipped path instead of finding it at compile time. 69 | ## `vmErrorHook` a callback which should raise `VmQuit`, refer to `errorHook` for reference. 70 | ## `searchPaths` optional paths one can use to supply libraries or packages for the 71 | const isFile = script is NimScriptPath 72 | var searchPaths = getSearchPath(stdPath) & searchPaths 73 | let 74 | scriptName = when isFile: script.string.splitFile.name else: "script" 75 | scriptDir = getTempDir() / scriptName 76 | scriptNimble = scriptDir / scriptName.changeFileExt(".nimble") 77 | scriptPath = scriptDir / scriptName.changeFileExt(".nim") 78 | 79 | discard existsOrCreateDir(scriptDir) 80 | writeFile(scriptNimble, "") 81 | 82 | let scriptFile = open(scriptPath, fmReadWrite) 83 | 84 | searchPaths.add scriptDir 85 | 86 | 87 | for `mod` in modules: # Add modules 88 | scriptFile.write("import " & `mod` & "\n") 89 | 90 | scriptFile.write addins.additions 91 | 92 | for uProc in addins.procs: 93 | scriptFile.write uProc.vmRunImpl 94 | 95 | when script is NimScriptFile: 96 | scriptFile.write string script 97 | else: 98 | scriptFile.write readFile(string script) 99 | searchPaths.add script.string.parentDir 100 | 101 | scriptFile.write addins.postCodeAdditions 102 | 103 | let 104 | intr = createInterpreter(scriptPath, searchPaths, flags = {allowInfiniteLoops}, 105 | defines = defines 106 | ) 107 | 108 | for uProc in addins.procs: 109 | intr.implementRoutine(scriptName, scriptName, uProc.name, uProc.vmProc) 110 | 111 | intr.registerErrorHook(vmErrorHook) 112 | try: 113 | scriptFile.setFilePos(0) 114 | intr.evalScript(llStreamOpen(scriptFile)) 115 | result = option(intr) 116 | except VMQuit: discard 117 | 118 | proc loadScriptWithState*( 119 | intr: var Option[Interpreter]; 120 | script: NimScriptFile or NimScriptPath; 121 | addins: VMAddins = VMaddins(); 122 | modules: varargs[string]; 123 | vmErrorHook = errorHook; 124 | stdPath = findNimStdlibCompileTime(); 125 | searchPaths: sink seq[string] = @[]; 126 | defines = defaultDefines) = 127 | ## Same as loadScript, but saves state, then loads the intepreter into `intr`. 128 | ## This does not keep a working intepreter if there is a script error. 129 | let state = 130 | if intr.isSome: 131 | intr.get.saveState() 132 | else: 133 | @[] 134 | intr = loadScript(script, addins, modules, vmErrorHook, stdPath, searchPaths, defines) 135 | if intr.isSome: 136 | intr.get.loadState(state) 137 | 138 | proc safeloadScriptWithState*( 139 | intr: var Option[Interpreter]; 140 | script: NimScriptFile or NimScriptPath; 141 | addins: VMAddins = VMaddins(); 142 | modules: varargs[string]; 143 | vmErrorHook = errorHook; 144 | stdPath = findNimStdlibCompileTime(); 145 | searchPaths: sink seq[string] = @[]; 146 | defines = defaultDefines) = 147 | ## Same as loadScriptWithState but saves state then loads the intepreter into `intr` if there were no script errors. 148 | ## Tries to keep the interpreter running. 149 | let state = 150 | if intr.isSome: 151 | intr.get.saveState() 152 | else: 153 | @[] 154 | let tempIntr = loadScript(script, addins, modules, vmErrorHook, stdPath, searchPaths, defines) 155 | if tempIntr.isSome: 156 | intr = tempIntr 157 | intr.get.loadState(state) 158 | 159 | proc getGlobalVariable*[T](intr: Option[Interpreter] or Interpreter; name: string): T = 160 | ## Easy access of a global nimscript variable 161 | when intr is Option[Interpreter]: 162 | assert intr.isSome 163 | let intr = intr.get 164 | let sym = intr.selectUniqueSymbol(name) 165 | if sym != nil: 166 | fromVm(T, intr.getGlobalValue(sym)) 167 | else: 168 | raise newException(VmSymNotFound, name & " is not a global symbol in the script.") 169 | 170 | macro invokeDynamic*(intr: Interpreter; pName: string; args: varargs[typed]; 171 | returnType: typedesc = void): untyped = 172 | ## Calls a nimscript function named `pName`, passing the `args` 173 | ## Converts the returned value to `returnType` 174 | let 175 | convs = newStmtList() 176 | argSym = genSym(nskVar, "args") 177 | retName = genSym(nskLet, "ret") 178 | retNode = newStmtList() 179 | resultIdnt = ident"res" 180 | fromVm = bindSym"fromVm" 181 | cachedIntrName = genSym(nskLet, "intr") 182 | if not returnType.eqIdent("void"): 183 | retNode.add nnkAsgn.newTree(resultIdnt, newCall(fromVm, returnType, retName)) 184 | retNode.add resultIdnt 185 | 186 | let nsProc = genSym(nskLet, "nsProc") 187 | var nsCall: NimNode 188 | if args.len > 0: 189 | let count = newLit(args.len) 190 | convs.add quote do: 191 | var `argSym`: array[`count`, PNode] 192 | nsCall = quote do: 193 | `cachedIntrName`.callRoutine(`nsProc`, `argSym`) 194 | else: 195 | nsCall = quote do: 196 | `cachedIntrName`.callRoutine(`nsProc`, []) 197 | 198 | for i, arg in args: 199 | convs.add quote do: 200 | `argSym`[`i`] = toVm(`arg`) 201 | 202 | result = quote do: 203 | block: 204 | let `cachedIntrName` = `intr` 205 | 206 | when `returnType` isnot void: 207 | var `resultIdnt`: `returnType` 208 | let `nsProc` = `cachedIntrName`.selectRoutine(`pName`) 209 | if `nsProc` != nil: 210 | `convs` 211 | when `returnType` isnot void: 212 | let `retName` = `nsCall` 213 | `retNode` 214 | else: 215 | discard `nsCall` 216 | else: 217 | raise newException(VmProcNotFound, "'$#' was not found in the script." % `pName`) 218 | 219 | macro invoke*(intr: Option[Interpreter]; pName: untyped; 220 | args: varargs[typed]; returnType: typedesc = void): untyped = 221 | ## Invoke but takes an option and unpacks it, if `intr.`isNone, assertion is raised 222 | result = newCall("invokeDynamic", newCall("get", intr), pName.toStrLit) 223 | for x in args: 224 | result.add x 225 | result.add nnkExprEqExpr.newTree(ident"returnType", returnType) 226 | 227 | macro invoke*(intr: Interpreter; pName: untyped; 228 | args: varargs[typed]; returnType: typedesc = void): untyped = 229 | result = newCall("invokeDynamic", intr, pName.toStrLit) 230 | for x in args: 231 | result.add x 232 | result.add nnkExprEqExpr.newTree(ident"returnType", returnType) 233 | -------------------------------------------------------------------------------- /src/nimscripter/expose.nim: -------------------------------------------------------------------------------- 1 | import std/[macros, macrocache, typetraits] 2 | import "$nim"/compiler/[vmdef, vm] 3 | import vmconversion 4 | 5 | import vmaddins 6 | export VMAddins 7 | 8 | var genSymOffset {.compileTime.} = 321321 9 | 10 | proc genSym(name: string): NimNode = 11 | result = ident(name & $genSymOffset) 12 | inc genSymOffset 13 | 14 | func deSym(n: NimNode): NimNode = 15 | ## Remove all symbols 16 | result = n 17 | for x in 0 .. result.len - 1: 18 | if result[x].kind == nnkSym: 19 | result[x] = ident($result[x]) 20 | else: 21 | result[x] = result[x].deSym 22 | 23 | func getMangledName*(pDef: NimNode): string = 24 | ## Generates a close to type safe name for backers 25 | result = $pdef[0] 26 | for def in pDef[3][1..^1]: 27 | for idnt in def[0..^3]: 28 | result.add $idnt 29 | if def[^2].kind in {nnkSym, nnkIdent}: 30 | result.add $def[^2] 31 | result.add "Comp" 32 | 33 | func getVmRuntimeImpl*(pDef: NimNode): string = 34 | ## Takes a proc definition and emits a proc with discard for the Nimscript side. 35 | let deSymd = deSym(pDef.copyNimTree()) 36 | deSymd[^1] = nnkDiscardStmt.newTree(newEmptyNode()) 37 | deSymd[^2] = nnkDiscardStmt.newTree(newEmptyNode()) 38 | deSymd[2] = newEmptyNode() 39 | for i, x in deSymd.params[1..^1]: 40 | if x[^2].typeKind == ntyOr: 41 | deSymd.params[i + 1][^2] = x[^2][1] 42 | deSymd.repr 43 | 44 | proc getReg(vmargs: Vmargs, pos: int): TFullReg = vmargs.slots[pos + vmargs.rb + 1] 45 | 46 | proc getLambda*(pDef: NimNode, realProcName: Nimnode = nil): NimNode = 47 | ## Generates the lambda for the vm backed logic. 48 | ## This is what the vm calls internally when talking to Nim 49 | let 50 | vmArgs = ident"vmArgs" 51 | tmp = quote do: 52 | proc n(`vmArgs`: VmArgs) {.gcsafe.} = discard 53 | 54 | tmp[^1] = newStmtList() 55 | 56 | tmp[0] = newEmptyNode() 57 | result = nnkLambda.newNimNode() 58 | tmp.copyChildrenTo(result) 59 | 60 | var procArgs: seq[NimNode] 61 | for i, def in pDef.params[1..^1]: 62 | var typ = def[^2] 63 | case typ.typeKind 64 | of ntyOr: 65 | typ = typ[1] 66 | of ntyBuiltinTypeClass, ntyCompositeTypeClass: 67 | error("Cannot use type classes with nimscripter, make an alias.", pdef) 68 | elif typ.kind == nnkCommand and typ[0].eqIdent"sink": 69 | typ = typ[1] 70 | elif typ.kind == nnkEmpty: 71 | typ = newCall("typeof", def[^1]) 72 | else: discard 73 | for idnt in def[0..^3]: # Get data from buffer in the vm proc 74 | let 75 | idnt = ident($idnt) 76 | argNum = newLit(procArgs.len) 77 | procArgs.add idnt 78 | result[^1].add quote do: 79 | let reg {.used.} = getReg(`vmArgs`, `argNum`) 80 | var `idnt`: `typ` 81 | when `typ` is (SomeOrdinal or enum) or `typ`.distinctBase(true) is (SomeOrdinal or enum): 82 | case reg.kind: 83 | of rkInt: 84 | `idnt` = `typ`(reg.intVal) 85 | of rkNode: 86 | `idnt` = fromVm(typeof(`typ`), reg.node) 87 | else: discard 88 | elif `typ` is SomeFloat or `typ`.distinctBase(true) is (SomeFloat): 89 | case reg.kind: 90 | of rkFloat: 91 | `idnt` = `typ`(reg.floatVal) 92 | of rkNode: 93 | `idnt` = fromVm(typeof(`typ`), reg.node) 94 | else: discard 95 | else: 96 | `idnt` = fromVm(typeof(`typ`), getNode(`vmArgs`, `argNum`)) 97 | 98 | let procName = 99 | if realProcName != nil: 100 | realProcName 101 | else: 102 | pDef[0] 103 | 104 | if pdef.params.len > 1: 105 | result[^1].add newCall(procName, procArgs) 106 | else: 107 | result[^1].add newCall(procName) 108 | let body = result[^1] 109 | result[^1] = quote do: 110 | {.cast(gcsafe).}: 111 | `body` 112 | 113 | if pdef.params[0].kind != nnkEmpty: 114 | let 115 | retT = pDef.params[0] 116 | call = result[^1][^1] 117 | result[^1][^1] = quote do: 118 | when `retT` is (SomeOrdinal or enum): 119 | `vmArgs`.setResult(BiggestInt(`call`)) 120 | elif `retT` is SomeFloat: 121 | `vmArgs`.setResult(BiggestFloat(`call`)) 122 | elif `retT` is string: 123 | `vmArgs`.setResult(`call`) 124 | else: 125 | `vmArgs`.setResult(toVm(`call`)) 126 | 127 | const 128 | procedureCache = CacheTable"NimscriptProcedures" 129 | addonsCache = CacheTable"NimscriptAddons" 130 | checksCache = CacheTable"NimscriptProcChecks" 131 | 132 | proc addToProcCache(n: NimNode, moduleName: string) = 133 | var impl: NimNode 134 | if n.kind == nnkProcDef: 135 | impl = n 136 | elif n.kind == nnkSym and n.symKind in {nskProc, nskFunc}: 137 | impl = n.getImpl 138 | elif n.kind == nnkSym and n.symKind in {nskVar, nskLet, nskConst}: 139 | impl = n 140 | else: 141 | impl = n 142 | for name, _ in procedureCache: 143 | if name == moduleName: 144 | procedureCache[name].add n 145 | return 146 | procedureCache[moduleName] = nnkStmtList.newTree(n) 147 | 148 | proc addToAddonCache(n: NimNode, moduleName: string) = 149 | var impl = 150 | if n.kind == nnkSym: 151 | n.getImpl() 152 | else: 153 | n 154 | if impl.kind == nnktypeDef: 155 | impl = nnkTypeSection.newTree(impl) 156 | for name, _ in addonsCache: 157 | if name == moduleName: 158 | addonsCache[name].add impl 159 | return 160 | addonsCache[moduleName] = nnkStmtList.newTree(impl) 161 | 162 | macro addToCache*(sym: typed, moduleName: static string) = 163 | ## Can be used to manually add a symbol to a cache. 164 | ## Otherwise used internally to add symbols to cache 165 | case sym.kind 166 | of nnkSym: 167 | if sym.symKind in {nskType, nskConverter, nskIterator, nskMacro, nskTemplate}: 168 | addToAddonCache(sym, moduleName) 169 | else: 170 | addToProcCache(sym, moduleName) 171 | of nnkClosedSymChoice: 172 | for x in sym: 173 | if x.symKind in {nskType, nskConverter, nskIterator, nskMacro, nskTemplate}: 174 | addToAddonCache(x, moduleName) 175 | else: 176 | addToProcCache(x, moduleName) 177 | else: error("Invalid code passed must be a symbol") 178 | 179 | macro exportTo*(moduleName: untyped, procDefs: varargs[untyped]): untyped = 180 | ## Takes a module name and symbols, adding them to the proper table internally. 181 | result = newStmtList() 182 | var moduleName = $moduleName 183 | for pDef in procDefs: 184 | result.add newCall("addToCache", pdef, newLit(modulename)) 185 | 186 | macro addCallable*(moduleName: untyped, body: typed) = 187 | block searchAdd: 188 | for k, v in checksCache: 189 | if k.eqIdent(moduleName): 190 | v.add: 191 | if body.kind == nnkProcDef: 192 | body 193 | else: 194 | body[0] 195 | break searchAdd 196 | checksCache[$moduleName] = newStmtList(moduleName): 197 | if body.kind == nnkProcDef: 198 | body 199 | else: 200 | body[0] 201 | 202 | iterator generateParamHeaders(paramList: NimNode, types: seq[(int, NimNode)], indicies: var seq[int]): NimNode = 203 | ## Generates permutations of all proc headers with the given types 204 | var params = copyNimTree(paramList) 205 | while indicies[^1] < (types[^1][1].len - 1): 206 | for x in 0..types.high: 207 | let 208 | (ind, typ) = types[x] 209 | params[ind][^2] = typ[indicies[x] + 1] 210 | yield params 211 | inc indicies[0] 212 | for i, x in indicies: 213 | if indicies[i] >= types[i][1].len - 1: 214 | if i + 1 < indicies.len: 215 | inc indicies[i + 1] 216 | indicies[i] = 0 217 | 218 | proc makeVMProcSignature(n: NimNode, genSym = false): NimNode = 219 | if not genSym: 220 | n[4] = newEmptyNode() # remove pragmas 221 | n[^2] = newStmtList() # remove bodies 222 | n[^1] = newStmtList() # remove bodies 223 | let 224 | runImpl = getVmRuntimeImpl(n) 225 | lambda = getLambda(n) 226 | realName = $n[0] 227 | result = quote do: 228 | VmProcSignature( 229 | name: `realName`, 230 | vmRunImpl: `runImpl`, 231 | vmProc: `lambda` 232 | ) 233 | else: 234 | # This is gensym'd so there is a proc calling a hidden proc, 235 | # since the VM doesnt support overload implementations 236 | let 237 | newDef = copyNimTree(n) 238 | newName = genSym($n[0]) 239 | strName = $newName 240 | newDef[0] = newName 241 | newDef[4] = newEmptyNode() # Remove pragmas 242 | newDef[^1] = newStmtList() # Removes body 243 | newDef[^2] = newStmtList() # Removes body 244 | 245 | var runImpl = getVmRuntimeImpl(newDef) 246 | let lambda = getLambda(n) 247 | newDef[0] = n[0] 248 | newDef[^1] = newCall(newName) 249 | newDef[^2] = newCall(newName) 250 | 251 | for i, def in n.params: 252 | if i > 0: 253 | # Body can be in one of two places 254 | newDef[^1].add def[0..^3] 255 | newDef[^2].add def[0..^3] 256 | runImpl.add newDef.repr 257 | result = quote do: 258 | VmProcSignature( 259 | name: `strName`, 260 | vmRunImpl: `runImpl`, 261 | vmProc: `lambda` 262 | ) 263 | 264 | proc generateTypeclassProcSignatures(pDef: Nimnode): NimNode = 265 | result = newStmtList() 266 | var 267 | types: seq[(int, NimNode)] 268 | indicies: seq[int] 269 | for i in 1.. = 56 | let 57 | name = def[0] 58 | defaultValue = def[1] 59 | 60 | result.add quote do: 61 | getWithDefault(`intr`, `name`, `defaultValue`) 62 | of nnkCall: 63 | # : 64 | let 65 | name = def[0] 66 | nameStr = $name 67 | rhs = def[1][0] # Whatever comes after ':'. Could include assignment 68 | 69 | case rhs.kind: 70 | of nnkAsgn: 71 | # : = 72 | let defaultValue = rhs[1] 73 | result.add quote do: 74 | getWithDefault(`intr`, `name`, `defaultValue`) 75 | else: 76 | # : 77 | let ttype = rhs 78 | result.add quote do: 79 | let `name` = tryGetGlobalVariable[`ttype`](`intr`, `nameStr`) 80 | else: continue 81 | -------------------------------------------------------------------------------- /src/nimscripter/vmaddins.nim: -------------------------------------------------------------------------------- 1 | import "$nim"/compiler/[renderer, vmdef] 2 | 3 | type 4 | VmProcSignature* = object 5 | name*: string 6 | vmRunImpl*: string 7 | vmProc*: proc(args: VmArgs){.closure, gcsafe.} 8 | VMAddins* = object 9 | procs*: seq[VmProcSignature] 10 | additions*: string 11 | postCodeAdditions*: string 12 | -------------------------------------------------------------------------------- /src/nimscripter/vmconversion.nim: -------------------------------------------------------------------------------- 1 | import std/[macros, macrocache, typetraits] 2 | import "$nim"/compiler/[renderer, ast, idents] 3 | import assume/typeit 4 | 5 | type 6 | VMParseError* = object of CatchableError ## Error raised when an object cannot be parsed. 7 | 8 | proc toVm*[T: enum or bool](a: T): Pnode = newIntNode(nkIntLit, a.BiggestInt) 9 | proc toVm*[T: char](a: T): Pnode = newIntNode(nkUInt8Lit, a.BiggestInt) 10 | 11 | proc toVm*(a: int8): Pnode = newIntNode(nkInt8Lit, a.BiggestInt) 12 | proc toVm*(a: int16): Pnode = newIntNode(nkInt16Lit, a.BiggestInt) 13 | proc toVm*(a: int32): Pnode = newIntNode(nkInt32Lit, a.BiggestInt) 14 | proc toVm*(a: int64): Pnode = newIntNode(nkint64Lit, a.BiggestInt) 15 | proc toVm*(a: int): Pnode = newIntNode(nkIntLit, a.BiggestInt) 16 | 17 | proc toVm*(a: uint8): Pnode = newIntNode(nkuInt8Lit, a.BiggestInt) 18 | proc toVm*(a: uint16): Pnode = newIntNode(nkuInt16Lit, a.BiggestInt) 19 | proc toVm*(a: uint32): Pnode = newIntNode(nkuInt32Lit, a.BiggestInt) 20 | proc toVm*(a: uint64): Pnode = newIntNode(nkuint64Lit, a.BiggestInt) 21 | proc toVm*(a: uint): Pnode = newIntNode(nkuIntLit, a.BiggestInt) 22 | 23 | proc toVm*(a: float32): Pnode = newFloatNode(nkFloat32Lit, BiggestFloat(a)) 24 | proc toVm*(a: float64): Pnode = newFloatNode(nkFloat64Lit, BiggestFloat(a)) 25 | proc toVm*(a: string): PNode = newStrNode(nkStrLit, a) 26 | proc toVm*[T: proc](a: T): PNode = newNode(nkNilLit) 27 | 28 | proc toVm*[T](s: set[T]): PNode = 29 | result = newNode(nkCurly) 30 | let count = high(T).ord - low(T).ord 31 | result.sons.setLen(count) 32 | for val in s: 33 | let offset = val.ord - low(T).ord 34 | result[offset] = toVm(val) 35 | 36 | proc toVm*[T: openArray](obj: T): PNode 37 | proc toVm*[T: tuple](obj: T): PNode 38 | proc toVm*[T: object](obj: T): PNode 39 | proc toVm*[T: ref](obj: T): PNode 40 | proc toVm*[T: distinct](a: T): PNode = toVm(distinctBase(T, true)(a)) 41 | 42 | 43 | template raiseParseError(t: typedesc): untyped = 44 | raise newException(VMParseError, "Cannot convert to: " & $t) 45 | 46 | proc extractType(typ: NimNode): NimNode = 47 | let impl = typ.getTypeInst 48 | impl[^1] 49 | 50 | const intLits = {nkCharLit..nkUInt64Lit} 51 | proc fromVm*(t: typedesc[SomeOrdinal or char], node: PNode): t = 52 | if node.kind in intLits: 53 | t(node.intVal) 54 | else: 55 | raiseParseError(t) 56 | 57 | proc fromVm*(t: typedesc[SomeFloat], node: PNode): t = 58 | if node.kind in nkFloatLiterals: 59 | t(node.floatVal) 60 | else: 61 | raiseParseError(t) 62 | 63 | proc fromVm*(t: typedesc[string], node: PNode): string = 64 | if node.kind in {nkStrLit, nkTripleStrLit, nkRStrLit}: 65 | node.strVal 66 | else: 67 | raiseParseError(t) 68 | 69 | proc fromVm*[T](t: typedesc[set[T]], node: Pnode): t = 70 | if node.kind == nkCurly: 71 | for val in node.items: 72 | if val != nil: 73 | case val.kind 74 | of nkRange: 75 | for x in fromVm(T, val[0])..fromVm(T, val[1]): 76 | result.incl x 77 | else: 78 | result.incl fromVm(T, val) 79 | else: 80 | raiseParseError(set[T]) 81 | 82 | proc fromVm*(t: typedesc[proc]): typeof(t) = nil 83 | 84 | proc fromVm*[T: object](obj: typedesc[T], vmNode: PNode): T 85 | proc fromVm*[T: tuple](obj: typedesc[T], vmNode: Pnode): T 86 | proc fromVm*[T: ref object](obj: typedesc[T], vmNode: PNode): T 87 | proc fromVm*[T: ref(not object)](obj: typedesc[T], vmNode: PNode): T 88 | proc fromVm*(obj: typedesc[(distinct)], vmNode: PNode): obj # Mixin's don't work apparently 89 | 90 | proc fromVm*[T: proc](obj: typedesc[T], vmNode: PNode): T = nil 91 | 92 | 93 | proc fromVm*[T](obj: typedesc[seq[T]], vmNode: Pnode): obj = 94 | mixin fromVm 95 | if vmNode.kind in {nkBracket, nkBracketExpr}: 96 | result.setLen(vmNode.sons.len) 97 | for i, x in vmNode.pairs: 98 | result[i] = fromVm(T, x) 99 | else: 100 | raiseParseError(obj) 101 | 102 | 103 | proc fromVm*[Idx, T](obj: typedesc[array[Idx, T]], vmNode: Pnode): obj = 104 | if vmNode.kind in {nkBracket, nkBracketExpr}: 105 | for i, x in vmNode.pairs: 106 | result[Idx(i - obj.low.ord)] = fromVm(T, x) 107 | else: 108 | raiseParseError(array[Idx, T]) 109 | 110 | proc fromVm*[T: tuple](obj: typedesc[T], vmNode: Pnode): T = 111 | if vmNode.kind == nkTupleConstr: 112 | var index = 0 113 | for x in result.fields: 114 | x = fromVm(typeof(x), vmNode[index]) 115 | inc index 116 | else: 117 | raiseParseError(T) 118 | 119 | proc fromVm*(obj: typedesc[(distinct)], vmNode: PNode): obj = 120 | mixin fromVm 121 | obj(fromVm(distinctBase(obj), vmNode)) 122 | 123 | proc hasRecCase(n: NimNode): bool = 124 | for son in n: 125 | if son.kind == nnkRecCase: 126 | return true 127 | 128 | proc baseSym(n: NimNode): NimNode = 129 | if n.kind == nnkSym: 130 | n 131 | else: 132 | n.basename 133 | 134 | proc replaceGenerics(n: NimNode, genTyp: seq[(NimNode, NimNode)]) = 135 | ## Replaces all instances of a typeclass with a generic type, 136 | ## used in generated headers for the VM. 137 | for i in 0 ..< n.len: 138 | var x = n[i] 139 | if x.kind in {nnkSym, nnkIdent}: 140 | for (name, typ) in genTyp: 141 | if x.eqIdent(name): 142 | n[i] = typ 143 | else: 144 | replaceGenerics(x, genTyp) 145 | 146 | proc fromVm*[T: object](obj: typedesc[T], vmNode: PNode): T = 147 | if vmNode.kind == nkObjConstr: 148 | var ind = 1 149 | typeIt(result, {titAllFields, titDeclaredOrder}): 150 | if it.isAccessible: 151 | {.cast(uncheckedAssign).}: 152 | it = fromVm(typeof(it), vmNode[ind][1]) 153 | inc ind 154 | else: 155 | raiseParseError(T) 156 | 157 | proc fromVm*[T: ref object](obj: typedesc[T], vmNode: PNode): T = 158 | case vmNode.kind 159 | of nkNilLit: 160 | result = nil 161 | of nkObjConstr: 162 | new result 163 | result[] = fromVm(typeof(result[]), vmNode) 164 | else: 165 | raiseParseError(T) 166 | 167 | proc fromVm*[T: ref(not object)](obj: typedesc[T], vmNode: PNode): T = 168 | if vmNode.kind != nkNilLit: 169 | new result 170 | result[] = fromVm(typeof(result[]), vmNode) 171 | 172 | proc toVm*[T: openArray](obj: T): PNode = 173 | result = newNode(nkBracketExpr) 174 | for x in obj: 175 | result.add toVm(x) 176 | 177 | proc toVm*[T: tuple](obj: T): PNode = 178 | result = newNode(nkTupleConstr) 179 | for x in obj.fields: 180 | result.add toVm(x) 181 | 182 | proc toVm*[T: object](obj: T): PNode = 183 | result = newNode(nkObjConstr) 184 | result.add newNode(nkEmpty) 185 | typeit(obj, {titAllFields}): 186 | result.add newNode(nkEmpty) 187 | var i = 1 188 | typeIt(obj, {titAllFields, titDeclaredOrder}): 189 | if it.isAccessible: 190 | result[i] = newNode(nkExprColonExpr) 191 | result[i].add newNode(nkEmpty) 192 | result[i].add toVm(it) 193 | inc i 194 | 195 | proc toVm*[T: ref](obj: T): PNode = 196 | if obj.isNil: 197 | newNode(nkNilLit) 198 | else: 199 | toVM(obj[]) 200 | -------------------------------------------------------------------------------- /src/nimscripter/vmops.nim: -------------------------------------------------------------------------------- 1 | ## This module contains the a template to implement procedures similar to those that are normally in the `nimscript` module 2 | import std/[os, osproc] 3 | import nimscripter/expose 4 | 5 | proc exec(s: string) = 6 | if execShellCmd(s) != 0: 7 | raise newException(OSError, s) 8 | 9 | proc gorgeEx(cmd: string): tuple[output: string, exitCode: int] = 10 | execCmdEx(cmd) 11 | 12 | proc listFiles(dir: string): seq[string] = 13 | for kind, path in walkDir(dir): 14 | if kind == pcFile: 15 | result.add path 16 | 17 | proc listDirs(dir: string): seq[string] = 18 | for kind, path in walkDir(dir): 19 | if kind == pcDir: 20 | result.add path 21 | 22 | proc rmDir(dir: string, checkDir = false) = removeDir(dir, checkDir) 23 | proc rmFile(file: string) = removeFile(file) 24 | proc mvDir(source, dest: string, checkDir = false) = moveDir(source, dest) 25 | proc mvFile(source, dest: string) = moveFile(source, dest) 26 | proc cd(dir: string) = setCurrentDir(dir) 27 | proc mkDir(dir: string) = createDir dir 28 | proc cpFile(f, to: string) = copyFile f, to 29 | proc cpDir(f, to: string) = copyDir f, to 30 | 31 | template addVmops*(module: untyped) = 32 | ## Adds the ops to the provided `module` 33 | ## this covers most of what the nimscript provides, and adds some more. 34 | exportTo(module, 35 | getCurrentDir, 36 | setCurrentDir, 37 | cd, 38 | mvDir, 39 | mvFile, 40 | execShellCmd, 41 | exec, 42 | existsOrCreateDir, 43 | tryRemoveFile, 44 | listFiles, 45 | listDirs, 46 | rmDir, 47 | rmFile, 48 | vmops.gorgeEx, 49 | mkDir, 50 | cpFile, 51 | cpDir 52 | ) 53 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") 2 | switch("define", "scripted") -------------------------------------------------------------------------------- /tests/example/first.nims: -------------------------------------------------------------------------------- 1 | import json 2 | let a* = ComplexObject( 3 | someInt: 300, 4 | someBool: true, 5 | someString: "heel ya", 6 | secondaryBool: true, 7 | someOtherString: "Really cool?" 8 | ) 9 | doStuff(a) 10 | doStuffA(SomeRef(a: 100)) 11 | doStuffB(@[10, 20, 30, 10, 50, 100]) 12 | doThingWithDist(DistType 100) 13 | 14 | proc testObj*(c: ComplexObject) = 15 | assert $c == "(someInt: 320, someBool: false, someIntTwo: 42)" 16 | 17 | proc test*(a: int, b: float) = 18 | assert 10 == a 19 | assert 20d == b 20 | 21 | proc testRef*(j: SomeRef) = echo j[] 22 | 23 | proc fromJson*: JsonNode = %* a 24 | 25 | proc testJson*(j: JsonNode) = 26 | let c = j.to(ComplexObject) 27 | assert $c == """(someInt: 300, someBool: true, someString: "heel ya", secondaryBool: true, someOtherString: "Really cool?")""" 28 | 29 | proc recObj*(r: RecObject) = 30 | assert r.b == {"hello": "world"}.toTable 31 | assert r.next != nil 32 | 33 | proc testTuple*(t: ((int, int), int, int, SomeRef)) = 34 | assert t[0] == (100, 200) 35 | assert t[1] == 200 36 | assert t[2] == 300 37 | assert t[3].a == 300 38 | 39 | proc testDistinct*(t: DistType): DistType = t 40 | 41 | proc getCharSet*(s: set[char]): set[char] = s 42 | proc getByteSet*(s: set[byte]): set[byte] = s 43 | proc getIntSet*(s: set[355..357]): set[355..357] = s 44 | proc getEnumSet*(s: set[SomeEnum]): set[SomeEnum] = s 45 | 46 | proc getArray*(a: array[5, int]): array[5, int] = a 47 | proc getSeq*(a: seq[int]): seq[int] = a 48 | proc getString*(s: string): string = s 49 | 50 | proc getRefSeq*(a: ref seq[int]): ref seq[int] = a 51 | 52 | proc getProc*(a: proc(){.nimcall.}): proc(){.nimcall.} = a 53 | 54 | template makeNumTest(T: typedesc[SomeOrdinal or char or SomeFloat]) = 55 | proc `get T`*(a: T): T = a 56 | 57 | makeNumTest(char) 58 | makeNumTest(bool) 59 | makeNumTest(SomeEnum) 60 | 61 | makeNumTest(uint) 62 | makeNumTest(int) 63 | 64 | makeNumTest(uint8) 65 | makeNumTest(int8) 66 | 67 | makeNumTest(uint16) 68 | makeNumTest(int16) 69 | 70 | makeNumTest(uint32) 71 | makeNumTest(int32) 72 | 73 | makeNumTest(uint64) 74 | makeNumTest(int64) 75 | 76 | makeNumTest(float) 77 | makeNumTest(float32) 78 | 79 | 80 | assert testSink(100) == 100 81 | -------------------------------------------------------------------------------- /tests/example/objects.nim: -------------------------------------------------------------------------------- 1 | import std/tables 2 | export tables 3 | type 4 | ComplexObject* = object 5 | someInt*: int 6 | case someBool*: bool 7 | of true: 8 | someString*: string 9 | case secondaryBool*: bool 10 | of true: 11 | someOtherString*: string 12 | else: discard 13 | else: 14 | someIntTwo*: int 15 | SomeRef* = ref object 16 | a*: int 17 | SomeEnum* {.pure.} = enum 18 | a, b, c, d 19 | SomeVarObject* = ref object 20 | case kind*: SomeEnum 21 | of a: 22 | b*: float 23 | of d: 24 | c*: int 25 | else: 26 | d*: string 27 | RecObject* = ref object 28 | next*: RecObject 29 | b*: Table[string, string] 30 | DistType* = distinct int 31 | -------------------------------------------------------------------------------- /tests/taddinvariable.nim: -------------------------------------------------------------------------------- 1 | import nimscripter, nimscripter/variables 2 | import std/unittest 3 | addVariable(myImpl, productName, string) 4 | addVariable(myImpl, doJump, bool) 5 | addVariable(myImpl, appId, int) 6 | const 7 | scriptProcs = implNimScriptModule(myImpl) # This emits our exported code 8 | ourScript = NimScriptFile""" 9 | productName = "bbb" 10 | doJump = productName == "bbb" 11 | appId = 300 12 | """ 13 | 14 | suite "Variable addins": 15 | test "Ensure assignment works": 16 | let intr = loadScript(ourScript, scriptProcs) 17 | check intr.getGlobalVariable[:string]("productName") == "bbb" 18 | check intr.getGlobalVariable[:bool]("doJump") 19 | check intr.getGlobalVariable[:int]("appId") == 300 20 | -------------------------------------------------------------------------------- /tests/texamples.nim: -------------------------------------------------------------------------------- 1 | import nimscripter 2 | import std/unittest 3 | suite "Readme Examples": 4 | test("Example 1"): 5 | proc doThing(): int = 42 6 | exportTo(myImpl, doThing) # The name of our "nimscript module" is `myImpl` 7 | const 8 | scriptProcs = implNimScriptModule(myImpl) # This emits our exported code 9 | ourScript = NimScriptFile("assert doThing() == 42") # Convert to `NimScriptFile` for loading from strings 10 | let intr = loadScript(ourScript, scriptProcs) # Load our script with our code and using our system `stdlib`(not portable) 11 | 12 | test("Example 2"): 13 | const script = NimScriptFile"proc fancyStuff*(a: int) = assert a in [10, 300]" # Notice `fancyStuff` is exported 14 | let intr = loadScript(script) # We are not exposing any procedures hence single parameter 15 | intr.invoke(fancyStuff, 10) # Calls `fancyStuff(10)` in vm 16 | intr.invoke(fancyStuff, 300) # Calls `fancyStuff(300)` in vm 17 | 18 | test("Example 3"): 19 | addCallable(test3): 20 | proc fancyStuff(a: int) # Has checks for the nimscript to ensure it's definition doesnt change to something unexpected. 21 | const 22 | addins = implNimscriptModule(test3) 23 | script = NimScriptFile"proc fancyStuff*(a: int) = assert a in [10, 300]" # Notice `fancyStuff` is exported 24 | let intr = loadScript(script, addins) # This adds in out checks for the proc 25 | intr.invoke(fancyStuff, 10) # Calls `fancyStuff(10)` in vm 26 | intr.invoke(fancyStuff, 300) # Calls `fancyStuff(300)` in vm -------------------------------------------------------------------------------- /tests/tgeneral.nim: -------------------------------------------------------------------------------- 1 | import nimscripter 2 | import nimscripter/[vmconversion, variables] 3 | import example/objects 4 | import std/[json, unittest, os] 5 | 6 | suite("General A(fromFile)"): 7 | var compl: ComplexObject 8 | proc doStuff(a: ComplexObject) = compl = a 9 | proc doStuffA(a: SomeRef) = check a.a == 100 10 | proc doStuffB(a: seq[int]) = check a == @[10, 20, 30, 10, 50, 100] 11 | proc doThingWithDist(a: DistType) = check int(a) == 100 12 | proc testSink(i: sink int): int = i 13 | 14 | exportTo(test, 15 | doStuff, 16 | doStuffA, 17 | doStuffB, 18 | doThingWithDist, 19 | testSink, 20 | DistType, 21 | ComplexObject, 22 | SomeRef, 23 | RecObject, 24 | SomeEnum 25 | ) 26 | const addins = implNimscriptModule(test) 27 | let intr = loadScript(NimScriptPath("tests/example/first.nims"), addins, modules = ["tables"]) 28 | 29 | test("nums"): 30 | check intr.invoke(testDistinct, DistType(100), returnType = DistType).int == 100 31 | 32 | check intr.invoke(getuint8, 128u8, returnType = uint8) == 128u8 33 | check intr.invoke(getint8, -123i8, returnType = int8) == -123i8 34 | 35 | check intr.invoke(getuint16, 32131u16, returnType = uint16) == 32131u16 36 | check intr.invoke(getint16, -321i16, returnType = int16) == -321i16 37 | 38 | check intr.invoke(getuint32, 32131u32, returnType = uint32) == 32131u32 39 | check intr.invoke(getint32, -321i32, returnType = int32) == -321i32 40 | 41 | check intr.invoke(getuint64, 32131u64, returnType = uint64) == 32131u64 42 | check intr.invoke(getint64, -321i64, returnType = int64) == -321i64 43 | 44 | check intr.invoke(getfloat32, 3.1415926535f, returnType = float32) == 3.1415926535f 45 | check intr.invoke(getfloat, 42.424242, returnType = float64) == 42.424242 46 | 47 | check intr.invoke(getChar, 'a', returnType = char) == 'a' 48 | check intr.invoke(getbool, true, returnType = bool) == true 49 | check intr.invoke(getSomeEnum, a, returnType = SomeEnum) == a 50 | 51 | var myRefSeq = new seq[int] 52 | myRefSeq[] = @[10, 20, 30] 53 | 54 | check intr.invoke(getRefSeq, myRefSeq, returnType = typeof(myRefSeq))[] == myRefSeq[] 55 | myRefSeq = nil 56 | check intr.invoke(getRefSeq, myRefSeq, returnType = typeof(myRefSeq)).isNil 57 | check intr.invoke(getProc, proc(){.nimcall.} = discard, returnType = proc(){.nimcall.}).isNil 58 | 59 | type AnObject = ref object 60 | a, b: int 61 | when false: 62 | a, b: int 63 | let aVal = fromVm(AnObject, AnObject(a: 100, b: 20).toVm) 64 | check aVal.a == 100 65 | check aVal.b == 20 66 | 67 | type QueryParams = distinct seq[(string, string)] # Silly error due to mixins 68 | 69 | check compiles((discard fromVm(QueryParams, nil))) 70 | 71 | 72 | test("parseErrors"): 73 | expect(VMParseError): 74 | discard intr.invoke(getfloat, 3.14, returnType = SomeEnum) 75 | 76 | expect(VMParseError): 77 | discard intr.invoke(getUint64, 10u64, returnType = float) 78 | 79 | expect(VMParseError): 80 | discard intr.invoke(getChar, 'a', returnType = string) 81 | 82 | expect(VMParseError): 83 | discard intr.getGlobalVariable[:seq[int]]("a") 84 | 85 | expect(VMParseError): 86 | discard intr.getGlobalVariable[: (int, int)]("a") 87 | 88 | test("sets"): 89 | const 90 | charSet = {'a'..'z', '0'} 91 | byteSet = {0u8..32u8, 100u8..127u8} 92 | intSet = {range[355..357](355), 356} 93 | enumSet = {a, b, c} 94 | check intr.get.invoke(getCharSet, charSet, returnType = set[char]) == charSet 95 | check intr.get.invoke(getByteSet, byteSet, returnType = set[byte]) == byteSet 96 | check intr.get.invoke(getIntSet, intSet, returnType = set[355..357]) == intSet 97 | check intr.get.invoke(getEnumSet, enumSet, returnType = set[SomeEnum]) == enumSet 98 | 99 | test("colls"): 100 | const 101 | arr = [1, 2, 3, 4, 5] 102 | seq1 = @arr 103 | seq2 = @[3, 6, 8, 9, 10] 104 | str1 = "Hello" 105 | str2 = "world" 106 | check intr.invoke(getArray, arr, returnType = array[5, int]) == arr 107 | check intr.invoke(getSeq, seq1, returnType = seq[int]) == seq1 108 | check intr.invoke(getSeq, seq2, returnType = seq[int]) == seq2 109 | check intr.invoke(getString, str1, returnType = string) == str1 110 | check intr.invoke(getString, str2, returnType = string) == str2 111 | 112 | test("Object tests"): 113 | let res = intr.get.invoke(fromJson, returnType = JsonNode) 114 | check $res == """{"someInt":300,"someBool":true,"someString":"heel ya","secondaryBool":true,"someOtherString":"Really cool?"}""" 115 | intr.invoke(testObj, ComplexObject(someBool: false, someInt: 320, someintTwo: 42)) 116 | intr.invoke(test, 10, 20d, returnType = void) 117 | intr.invoke(testTuple, ((100, 200), 200, 300, SomeRef(a: 300))) 118 | intr.invoke(recObj, RecObject(next: RecObject(), b: {"hello": "world"}.toTable)) 119 | intr.invoke(testJson, %* compl) 120 | 121 | suite("General B(fromstring)"): 122 | test("save / load state"): 123 | const file = "var someVal* = 52\nproc setVal* = someVal = 32" 124 | var intr = loadScript(NimScriptFile(file)) 125 | 126 | check intr.getGlobalVariable[:int]("someVal") == 52 127 | intr.invoke(setVal) 128 | check intr.getGlobalVariable[:int]("someVal") == 32 129 | intr.loadScriptWithState(NimScriptFile(file)) 130 | check intr.getGlobalVariable[:int]("someVal") == 32 131 | 132 | test("Dynamic invoke"): 133 | const script = NimScriptFile"proc fancyStuff*(a: int) = assert a in [10, 300]" 134 | let intr = loadScript(script) 135 | intr.get.invokeDynamic("fancyStuff", 10) 136 | 137 | test("Get global variables macro"): 138 | let script = NimScriptFile""" 139 | let required* = "main" 140 | let defaultValueExists* = "foo" 141 | """ 142 | 143 | let intr = loadScript script 144 | 145 | getGlobalNimsVars intr: 146 | required: string 147 | optional: Option[string] 148 | defaultValue: int = 1 149 | defaultValueExists = "bar" 150 | 151 | check required == "main" 152 | check optional.isNone 153 | check defaultValue == 1 154 | check defaultValueExists == "foo" 155 | 156 | test "Use Nimble": 157 | let nimblePath = getHomeDir() / ".nimble" / "pkgs" 158 | let intr = loadScript(NimscriptFile"", searchPaths = @[nimblePath]) 159 | 160 | import nimscripter/vmops 161 | test "cmpic issue": 162 | const script = """ 163 | import std/os ## <-- this import 164 | proc build*(): bool = 165 | assert getCurrentDir().lastPathPart == "nimscripter" 166 | true 167 | 168 | when isMainModule: 169 | discard build() 170 | """ 171 | addVmops(buildpackModule) 172 | addCallable(buildpackModule): 173 | proc build(): bool 174 | const addins = implNimscriptModule(buildpackModule) 175 | discard loadScript(NimScriptFile(script), addins) 176 | 177 | test "Export complex variables": 178 | var 179 | objA = ComplexObject( 180 | someInt: -44, 181 | someBool: true, 182 | someString: "aaa", 183 | secondaryBool: true, 184 | someOtherString: "bbb" 185 | ) 186 | objB = SomeVarObject( 187 | kind: SomeEnum.c, 188 | d: "somevar" 189 | ) 190 | objC = RecObject( 191 | next: RecObject( 192 | next: RecObject( 193 | b: {"a":"A", "b":"B"}.toTable() 194 | ) 195 | ) 196 | ) 197 | enumA = SomeEnum.c 198 | 199 | const script = """ 200 | assert objA.someInt == -44 201 | assert objA.someBool == true 202 | assert objA.someString == "aaa" 203 | assert objA.secondaryBool == true 204 | assert objA.someOtherString == "bbb" 205 | assert enumA == SomeEnum.c 206 | assert objB.kind == SomeEnum.c 207 | assert objB.d == "somevar" 208 | assert objC.next.next.b["a"] == "A" 209 | assert objC.next.next.b["b"] == "B" 210 | """ 211 | exportTo(objTestModule, SomeEnum, ComplexObject, SomeVarObject, RecObject, enumA, objA, objB, objC) 212 | const addins = implNimscriptModule(objTestModule) 213 | check loadScript(NimScriptFile(script), addins, modules=["std/tables"]).isSome 214 | 215 | 216 | test "Ensure we cache intepreters for a direct call": 217 | const script = NimScriptFile"doThing(); proc fancyStuff*(a: int) = assert a == 10" 218 | var i = 0 219 | proc doThing() = inc i 220 | addCallable(myTest): 221 | proc fancyStuff(a: int) 222 | exportTo( 223 | myTest, 224 | doThing) 225 | const addins = implNimscriptModule(myTest) 226 | loadScript(script, addins).invoke(fancyStuff, 10) 227 | check i == 1 228 | -------------------------------------------------------------------------------- /tests/tpegs.nim: -------------------------------------------------------------------------------- 1 | import nimscripter, nimscripter/variables 2 | import std/unittest 3 | import std/[strutils, pegs] 4 | 5 | suite "case object tests": 6 | 7 | test("test peg"): 8 | const script = NimScriptFile dedent""" 9 | import pegs 10 | let testPeg* = peg"'hello'" 11 | """ 12 | let intr = loadScript(script) 13 | getGlobalNimsVars intr: 14 | testPeg: Peg 15 | check $testPeg == "'hello'" 16 | -------------------------------------------------------------------------------- /tests/tvmops.nim: -------------------------------------------------------------------------------- 1 | import nimscripter 2 | import nimscripter/vmops 3 | 4 | const script = """ 5 | proc build*(): bool = 6 | echo "building nim... " 7 | echo getCurrentDir() 8 | echo "done" 9 | true 10 | 11 | when isMainModule: 12 | discard build() 13 | """ 14 | addVmops(buildpackModule) 15 | addCallable(buildpackModule): 16 | proc build(): bool 17 | const addins = implNimscriptModule(buildpackModule) 18 | discard loadScript(NimScriptFile(script), addins) --------------------------------------------------------------------------------