rewrote nimeval.nim; added tcompilerapi example to show how the compiler can be used as an API

This commit is contained in:
Andreas Rumpf
2018-05-29 01:18:50 +02:00
parent ae1f6895fa
commit 06122ff711
5 changed files with 152 additions and 20 deletions

View File

@@ -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

View File

@@ -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
ast, astalgo, modules, passes, condsyms,
options, sem, semdata, llstream, vm, vmdef,
modulegraphs, idents, os
proc execute*(program: string) =
passes.gIncludeFile = includeModule
passes.gImportModule = importModule
initDefines()
loadConfigs(DefaultConfig)
type
Interpreter* = ref object ## Use Nim as an interpreter with this object
mainModule: PSym
graph: ModuleGraph
scriptName: string
initDefines()
defineSymbol("nimvm")
defineSymbol("nimscript")
when hasFFI: defineSymbol("nimffi")
registerPass(verbosePass)
registerPass(semPass)
registerPass(evalPass)
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)
searchPaths.add options.libpath
var graph = newModuleGraph()
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."

View File

@@ -0,0 +1,3 @@
proc addFloats*(x, y, z: float): float =
discard "implementation overriden by tcompilerapi.nim"

View File

@@ -0,0 +1,7 @@
import exposed
echo "top level statements are executed!"
proc hostProgramRunsThis*(a, b: float): float =
result = addFloats(a, b, 1.0)

View File

@@ -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()