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
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. - Added the parameter ``val`` for the ``CritBitTree[int].inc`` proc.
- An exception raised from ``test`` block of ``unittest`` now show its type in - An exception raised from ``test`` block of ``unittest`` now show its type in
error message 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 ### Language additions

View file

@ -1,7 +1,7 @@
# #
# #
# The Nim Compiler # The Nim Compiler
# (c) Copyright 2013 Andreas Rumpf # (c) Copyright 2018 Andreas Rumpf
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -9,27 +9,108 @@
## exposes the Nim VM to clients. ## exposes the Nim VM to clients.
import import
ast, modules, passes, passaux, condsyms, ast, astalgo, modules, passes, condsyms,
options, nimconf, sem, semdata, llstream, vm, modulegraphs, idents options, sem, semdata, llstream, vm, vmdef,
modulegraphs, idents, os
proc execute*(program: string) = type
passes.gIncludeFile = includeModule Interpreter* = ref object ## Use Nim as an interpreter with this object
passes.gImportModule = importModule mainModule: PSym
initDefines() graph: ModuleGraph
loadConfigs(DefaultConfig) scriptName: string
initDefines() iterator exportedSymbols*(i: Interpreter): PSym =
defineSymbol("nimvm") assert i != nil
defineSymbol("nimscript") assert i.mainModule != nil, "no main module selected"
when hasFFI: defineSymbol("nimffi") var it: TTabIter
registerPass(verbosePass) var s = initTabIter(it, i.mainModule.tab)
registerPass(semPass) while s != nil:
registerPass(evalPass) yield s
s = nextIter(it, i.mainModule.tab)
searchPaths.add options.libpath proc selectUniqueSymbol*(i: Interpreter; name: string;
var graph = newModuleGraph() 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 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) incl(m.flags, sfMainModule)
compileSystemModule(graph,cache) var vm = newCtx(m, cache, graph)
processModule(graph,m, llStreamOpen(program), nil, cache) 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()