diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2018-05-29 01:18:50 +0200 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2018-05-29 01:18:50 +0200 |
commit | 06122ff7116e699a50c968bb0192e6faf00fa6ef (patch) | |
tree | 67d09a5389d799427c21e9ae9340097fab439e74 | |
parent | ae1f6895faa645625a7e3486988d25c6d06b0e49 (diff) | |
download | Nim-06122ff7116e699a50c968bb0192e6faf00fa6ef.tar.gz |
rewrote nimeval.nim; added tcompilerapi example to show how the compiler can be used as an API
-rw-r--r-- | changelog.md | 5 | ||||
-rw-r--r-- | compiler/nimeval.nim | 127 | ||||
-rw-r--r-- | tests/compilerapi/exposed.nim | 3 | ||||
-rw-r--r-- | tests/compilerapi/myscript.nim | 7 | ||||
-rw-r--r-- | tests/compilerapi/tcompilerapi.nim | 36 |
5 files changed, 155 insertions, 23 deletions
diff --git a/changelog.md b/changelog.md index 813c4caf8..46a221700 100644 --- a/changelog.md +++ b/changelog.md @@ -78,6 +78,11 @@ - Added the parameter ``val`` for the ``CritBitTree[int].inc`` proc. - An exception raised from ``test`` block of ``unittest`` now show its type in error message +- The ``compiler/nimeval`` API was rewritten to simplify the "compiler as an + API". Using the Nim compiler and its VM as a scripting engine has never been + easier. See ``tests/compilerapi/tcompilerapi.nim`` for an example of how to + use the Nim VM in a native Nim application. + ### Language additions diff --git a/compiler/nimeval.nim b/compiler/nimeval.nim index 714efcdda..d5df6a9df 100644 --- a/compiler/nimeval.nim +++ b/compiler/nimeval.nim @@ -1,7 +1,7 @@ # # # The Nim Compiler -# (c) Copyright 2013 Andreas Rumpf +# (c) Copyright 2018 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -9,27 +9,108 @@ ## exposes the Nim VM to clients. import - ast, modules, passes, passaux, condsyms, - options, nimconf, sem, semdata, llstream, vm, modulegraphs, idents - -proc execute*(program: string) = - passes.gIncludeFile = includeModule - passes.gImportModule = importModule - initDefines() - loadConfigs(DefaultConfig) - - initDefines() - defineSymbol("nimvm") - defineSymbol("nimscript") - when hasFFI: defineSymbol("nimffi") - registerPass(verbosePass) - registerPass(semPass) - registerPass(evalPass) - - searchPaths.add options.libpath - var graph = newModuleGraph() + ast, astalgo, modules, passes, condsyms, + options, sem, semdata, llstream, vm, vmdef, + modulegraphs, idents, os + +type + Interpreter* = ref object ## Use Nim as an interpreter with this object + mainModule: PSym + graph: ModuleGraph + scriptName: string + +iterator exportedSymbols*(i: Interpreter): PSym = + assert i != nil + assert i.mainModule != nil, "no main module selected" + var it: TTabIter + var s = initTabIter(it, i.mainModule.tab) + while s != nil: + yield s + s = nextIter(it, i.mainModule.tab) + +proc selectUniqueSymbol*(i: Interpreter; name: string; + symKinds: set[TSymKind]): PSym = + ## Can be used to access a unique symbol of ``name`` and + ## the given ``symKinds`` filter. + assert i != nil + assert i.mainModule != nil, "no main module selected" + let n = getIdent(i.graph.cache, name) + var it: TIdentIter + var s = initIdentIter(it, i.mainModule.tab, n) + result = nil + while s != nil: + if s.kind in symKinds: + if result == nil: result = s + else: return nil # ambiguous + s = nextIdentIter(it, i.mainModule.tab) + +proc selectRoutine*(i: Interpreter; name: string): PSym = + ## Selects a declared rountine (proc/func/etc) from the main module. + ## The routine needs to have the export marker ``*``. The only matching + ## routine is returned and ``nil`` if it is overloaded. + result = selectUniqueSymbol(i, name, {skTemplate, skMacro, skFunc, + skMethod, skProc, skConverter}) + +proc callRoutine*(i: Interpreter; routine: PSym; args: openArray[PNode]): PNode = + assert i != nil + result = vm.execProc(PCtx i.graph.vm, routine, args) + +proc declareRoutine*(i: Interpreter; pkg, module, name: string; + impl: proc (a: VmArgs) {.closure, gcsafe.}) = + assert i != nil + let vm = PCtx(i.graph.vm) + vm.registerCallback(pkg & "." & module & "." & name, impl) + +proc evalScript*(i: Interpreter; scriptStream: PLLStream = nil) = + ## This can also be used to *reload* the script. + assert i != nil + assert i.mainModule != nil, "no main module selected" + initStrTable(i.mainModule.tab) + i.mainModule.ast = nil + + let s = if scriptStream != nil: scriptStream + else: llStreamOpen(findFile(i.graph.config, i.scriptName), fmRead) + processModule(i.graph, i.mainModule, s, nil, i.graph.cache) + +proc findNimStdLib*(): string = + ## Tries to find a path to a valid "system.nim" file. + ## Returns "" on failure. + try: + let nimexe = os.findExe("nim") + if nimexe.len == 0: return "" + result = nimexe.splitPath()[0] /../ "lib" + if not fileExists(result / "system.nim"): + when defined(unix): + result = nimexe.expandSymlink.splitPath()[0] /../ "lib" + if not fileExists(result / "system.nim"): return "" + except OSError, ValueError: + return "" + +proc createInterpreter*(scriptName: string; + searchPaths: openArray[string]; + flags: TSandboxFlags = {}): Interpreter = + var conf = newConfigRef() var cache = newIdentCache() - var m = makeStdinModule(graph) + var graph = newModuleGraph(cache, conf) + initDefines(conf.symbols) + defineSymbol(conf.symbols, "nimscript") + defineSymbol(conf.symbols, "nimconfig") + registerPass(graph, semPass) + registerPass(graph, evalPass) + + for p in searchPaths: + conf.searchPaths.add(p) + if conf.libpath.len == 0: conf.libpath = p + + var m = graph.makeModule(scriptName) incl(m.flags, sfMainModule) - compileSystemModule(graph,cache) - processModule(graph,m, llStreamOpen(program), nil, cache) + var vm = newCtx(m, cache, graph) + vm.mode = emRepl + vm.features = flags + graph.vm = vm + graph.compileSystemModule(cache) + result = Interpreter(mainModule: m, graph: graph, scriptName: scriptName) + +proc destroyInterpreter*(i: Interpreter) = + ## destructor. + discard "currently nothing to do." diff --git a/tests/compilerapi/exposed.nim b/tests/compilerapi/exposed.nim new file mode 100644 index 000000000..73becd93d --- /dev/null +++ b/tests/compilerapi/exposed.nim @@ -0,0 +1,3 @@ + +proc addFloats*(x, y, z: float): float = + discard "implementation overriden by tcompilerapi.nim" diff --git a/tests/compilerapi/myscript.nim b/tests/compilerapi/myscript.nim new file mode 100644 index 000000000..083385b6f --- /dev/null +++ b/tests/compilerapi/myscript.nim @@ -0,0 +1,7 @@ + +import exposed + +echo "top level statements are executed!" + +proc hostProgramRunsThis*(a, b: float): float = + result = addFloats(a, b, 1.0) diff --git a/tests/compilerapi/tcompilerapi.nim b/tests/compilerapi/tcompilerapi.nim new file mode 100644 index 000000000..00c9bc523 --- /dev/null +++ b/tests/compilerapi/tcompilerapi.nim @@ -0,0 +1,36 @@ +discard """ + output: '''top level statements are executed! +2.0 +''' +""" + +## Example program that demonstrates how to use the +## compiler as an API to embed into your own projects. + +import "../../compiler" / [ast, vmdef, vm, nimeval] +import std / [os] + +proc main() = + let std = findNimStdLib() + if std.len == 0: + quit "cannot find Nim's standard library" + + var intr = createInterpreter("myscript.nim", [std, getAppDir()]) + intr.declareRoutine("*", "exposed", "addFloats", proc (a: VmArgs) = + setResult(a, getFloat(a, 0) + getFloat(a, 1) + getFloat(a, 2)) + ) + + intr.evalScript() + + let foreignProc = selectRoutine(intr, "hostProgramRunsThis") + if foreignProc == nil: + quit "script does not export a proc of the name: 'hostProgramRunsThis'" + let res = intr.callRoutine(foreignProc, [newFloatNode(nkFloatLit, 0.9), + newFloatNode(nkFloatLit, 0.1)]) + if res.kind == nkFloatLit: + echo res.floatVal + else: + echo "bug!" + destroyInterpreter(intr) + +main() \ No newline at end of file |