summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2018-05-29 01:18:50 +0200
committerAndreas Rumpf <rumpf_a@web.de>2018-05-29 01:18:50 +0200
commit06122ff7116e699a50c968bb0192e6faf00fa6ef (patch)
tree67d09a5389d799427c21e9ae9340097fab439e74
parentae1f6895faa645625a7e3486988d25c6d06b0e49 (diff)
downloadNim-06122ff7116e699a50c968bb0192e6faf00fa6ef.tar.gz
rewrote nimeval.nim; added tcompilerapi example to show how the compiler can be used as an API
-rw-r--r--changelog.md5
-rw-r--r--compiler/nimeval.nim127
-rw-r--r--tests/compilerapi/exposed.nim3
-rw-r--r--tests/compilerapi/myscript.nim7
-rw-r--r--tests/compilerapi/tcompilerapi.nim36
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