summaryrefslogtreecommitdiff
path: root/vm0
diff options
context:
space:
mode:
Diffstat (limited to 'vm0')
-rw-r--r--vm0/func.go73
-rw-r--r--vm0/vm.go146
-rw-r--r--vm0/vm_test.go26
3 files changed, 245 insertions, 0 deletions
diff --git a/vm0/func.go b/vm0/func.go
new file mode 100644
index 0000000..6c95383
--- /dev/null
+++ b/vm0/func.go
@@ -0,0 +1,73 @@
+package vm0
+
+import (
+ "reflect"
+ "strings"
+
+ "github.com/gnolang/parscan/parser"
+)
+
+var types = map[string]reflect.Type{
+ "int": reflect.TypeOf(0),
+ "string": reflect.TypeOf(""),
+}
+
+func (i *Interp) callFunc(n *parser.Node) {
+ fp := i.fp
+ l := len(i.stack)
+ nargs := i.stack[l-1].(int)
+ args := make([]reflect.Value, nargs)
+ for j := range args {
+ args[nargs-j-1] = reflect.ValueOf(i.stack[l-2-j])
+ }
+ f := reflect.ValueOf(i.stack[l-2-nargs])
+ out := f.Call(args)
+ i.fp = fp
+ i.stack = i.stack[:l-2-nargs]
+ for _, v := range out {
+ i.push(v.Interface())
+ }
+}
+
+func (i *Interp) declareFunc(r *parser.Node, scope string) {
+ fname := r.Child[0].Content()
+
+ // Add symbols for input and output function arguments.
+ inArgs := r.Child[1].Child
+ fscope := strings.TrimPrefix(scope+"/"+fname+"/", "/")
+ in := make([]reflect.Type, len(inArgs))
+ for j, c := range inArgs {
+ i.sym[fscope+c.Content()] = j
+ in[j] = types[c.Child[0].Content()]
+ }
+ var out []reflect.Type
+ if len(r.Child) > 3 { // function has return values
+ if i.IsBlock(r.Child[2]) {
+ outArgs := r.Child[2].Child
+ out = make([]reflect.Type, len(outArgs))
+ for j, c := range outArgs {
+ out[j] = types[c.Content()]
+ }
+ } else {
+ out = []reflect.Type{types[r.Child[2].Content()]}
+ }
+ }
+ funT := reflect.FuncOf(in, out, false)
+
+ // Generate a wrapper function which will run function body AST.
+ f := reflect.MakeFunc(funT, func(args []reflect.Value) (res []reflect.Value) {
+ i.fp = len(i.stack) // fp will be restored by caller (callFunc).
+ for _, arg := range args {
+ i.push(arg.Interface())
+ }
+ i.Run(r.Child[len(r.Child)-1], fscope)
+ b := len(i.stack) - len(out)
+ for j := range out {
+ res = append(res, reflect.ValueOf(i.stack[b+j]))
+ }
+ return res
+ })
+
+ // Add a symbol for newly created func.
+ i.sym[scope+fname] = i.push(f.Interface()) - i.fp
+}
diff --git a/vm0/vm.go b/vm0/vm.go
new file mode 100644
index 0000000..4c726ed
--- /dev/null
+++ b/vm0/vm.go
@@ -0,0 +1,146 @@
+package vm0
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/gnolang/parscan/lang/golang"
+ "github.com/gnolang/parscan/parser"
+)
+
+type Interp struct {
+ *parser.Parser
+ stack []any // stack memory space
+ fp int // frame pointer: index of current frame in stack
+ sym map[string]int // symbol table, maps scoped identifiers to offsets relative to fp
+}
+
+func New(p *parser.Parser) (i *Interp) {
+ i = &Interp{Parser: p, stack: []any{}, sym: map[string]int{}}
+ i.sym["println"] = i.push(fmt.Println)
+ return i
+}
+
+func (i *Interp) Eval(src string) (r []any, err error) {
+ n, err := i.Parse(src)
+ if err != nil {
+ return nil, err
+ }
+ for _, nod := range n {
+ r, err = i.Run(nod, "")
+ if err != nil {
+ break
+ }
+ }
+ return
+}
+
+// Run implements a stack based virtual machine which directly walks the AST.
+func (i *Interp) Run(node *parser.Node, scope string) ([]any, error) {
+ stop := false
+
+ node.Walk2(nil, 0, func(n, a *parser.Node, k int) (ok bool) {
+ // Node pre-order processing.
+ switch n.Kind {
+ case golang.StmtBloc:
+ if a != nil && a.Kind == golang.IfStmt {
+ // Control-flow in 'if' sub-tree
+ if k == 1 {
+ // 'if' first body branch, evaluated when condition is true.
+ if len(a.Child) > 2 {
+ return i.peek().(bool) // keep condition on stack for else branch
+ }
+ return i.pop().(bool)
+ }
+ // 'else' body branch, evaluated when condition is false.
+ return !i.pop().(bool)
+ }
+ case golang.FuncDecl:
+ i.declareFunc(n, scope)
+ return false
+ }
+ return true
+ }, func(n, a *parser.Node, k int) (ok bool) {
+ // Node post-order processing.
+ if stop {
+ return false
+ }
+ l := len(i.stack)
+ switch n.Kind {
+ case golang.NumberLit:
+ num, _ := strconv.Atoi(n.Content()) // TODO(marc): compute num value at scanning.
+ i.push(num)
+ case golang.StringLit:
+ i.push(n.Block())
+ case golang.InfOp:
+ i.stack[l-2] = i.stack[l-2].(int) < i.stack[l-1].(int)
+ i.stack = i.stack[:l-1]
+ case golang.AddOp:
+ i.stack[l-2] = i.stack[l-2].(int) + i.stack[l-1].(int)
+ i.stack = i.stack[:l-1]
+ case golang.SubOp:
+ i.stack[l-2] = i.stack[l-2].(int) - i.stack[l-1].(int)
+ i.stack = i.stack[:l-1]
+ case golang.MulOp:
+ i.stack[l-2] = i.stack[l-2].(int) * i.stack[l-1].(int)
+ i.stack = i.stack[:l-1]
+ case golang.AssignOp, golang.DefOp:
+ i.stack[i.stack[l-2].(int)] = i.stack[l-1]
+ i.stack = i.stack[:l-2]
+ case golang.ReturnStmt:
+ stop = true
+ return false
+ case golang.CallExpr:
+ i.push(len(n.Child[1].Child)) // number of arguments to call
+ i.callFunc(n)
+ case golang.Ident:
+ name := n.Content()
+ v, sc, ok := i.getSym(name, scope)
+ fp := i.fp
+ if sc == "" {
+ fp = 0
+ }
+ if ok {
+ if a.Content() == ":=" {
+ i.push(fp + v) // reference for assign (absolute index)
+ break
+ }
+ i.push(i.stack[fp+v]) // value
+ break
+ }
+ if a.Content() != ":=" {
+ fmt.Println("error: undefined:", name, "scope:", scope)
+ }
+ v = i.push(any(nil)) - i.fp // v is the address of new value, relative to frame pointer
+ i.sym[scope+name] = v // bind scoped name to address in symbol table
+ i.push(v)
+ }
+ return true
+ })
+ return nil, nil
+}
+
+// getSym searches for an existing symbol starting from the deepest scope.
+func (i *Interp) getSym(name, scope string) (index int, sc string, ok bool) {
+ for {
+ if index, ok = i.sym[scope+name]; ok {
+ return index, scope, ok
+ }
+ scope = strings.TrimSuffix(scope, "/")
+ j := strings.LastIndex(scope, "/")
+ if j == -1 {
+ scope = ""
+ break
+ }
+ if scope = scope[:j]; scope == "" {
+ break
+ }
+ }
+ index, ok = i.sym[name]
+ return index, scope, ok
+}
+
+func (i *Interp) peek() any { return i.stack[len(i.stack)-1] }
+func (i *Interp) push(v any) (l int) { l = len(i.stack); i.stack = append(i.stack, v); return }
+func (i *Interp) pop() (v any) { l := len(i.stack) - 1; v = i.stack[l]; i.stack = i.stack[:l]; return }
diff --git a/vm0/vm_test.go b/vm0/vm_test.go
new file mode 100644
index 0000000..0e8896b
--- /dev/null
+++ b/vm0/vm_test.go
@@ -0,0 +1,26 @@
+package vm0
+
+import (
+ "os"
+ "testing"
+
+ "github.com/gnolang/parscan/lang/golang"
+)
+
+func TestEval(t *testing.T) {
+ i := New(golang.GoParser)
+ t.Logf("%#v\n", i.Parser)
+ //i.Eval("println(2*5)")
+ //n, _ := i.Parse("println(2*5)")
+ //n, _ := i.Parse(`a := 2 + 5`)
+ src := `a := 2`
+ nodes, err := i.Parse(src)
+ if err != nil {
+ t.Errorf("error %v", err)
+ }
+ i.Adot(nodes, os.Getenv("DOT"))
+ for _, n := range nodes {
+ v, err := i.Run(n, "")
+ t.Log(v, err)
+ }
+}