summaryrefslogtreecommitdiff
path: root/vm0
diff options
context:
space:
mode:
authorMarc Vertes <marc.vertes@tendermint.com>2023-10-12 10:51:58 +0200
committerGitHub <noreply@github.com>2023-10-12 10:51:58 +0200
commit37b9da32d3b911091deb254f6cba2a137c471287 (patch)
treeb4451de0fa0473a937a77d39fd1f8a4f87c8f60d /vm0
parenta21b9b12ad865a19ff687645082f9093c4101039 (diff)
move to a direct byte code compiler (#8)
* chore: refactor to keep only the new parser and bytecode vm * scanner: remove Token.value field * scanner: remove scanner.kind field * chore: move language specification in lang package This avoid a cyclic dependency in scanner_test which can now use the golang/GoSpec language specification for Go. * clean code * scanner: export scanner fields Also parser now generate function calls, including externals. * chore: fix lint issues * parser: handle strings * wip * parser: implement support for 'if, else, else if' statements Resolving labels in the compiler still in progress. * parser: support if statements, improve compiler * improve handling of functions * improve support of local variables * scanner: trim leading and trailing spaces * fixes to make fibonacci work * parser: improve README, fix function parameters parsing
Diffstat (limited to 'vm0')
-rw-r--r--vm0/README.md27
-rw-r--r--vm0/func.go75
-rw-r--r--vm0/vm.go157
-rw-r--r--vm0/vm_test.go26
4 files changed, 0 insertions, 285 deletions
diff --git a/vm0/README.md b/vm0/README.md
deleted file mode 100644
index fc89429..0000000
--- a/vm0/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# vm0
-
-vm0 is a virtual machine executing directly the syntax tree.
-
-```mermaid
-graph LR
-s[ ] --> |source| a(scanner)
---> |tokens| b(parser)
---> |AST| c(vm)
-subgraph vm0
- c
-end
-style s height:0px;
-```
-
-The execution is performed by walking the AST and evaluating each
-visited node.
-
-
-## Motivation
-
-- have a reference execution model for each defined language
-- usable for compilation time evaluation
-- to modelize similar VMs (i.e. gnovm)
-- to validate and compare with other VMs (once it is itself validated)
-- could serve as a basis for AST based symbolic execution (to be
- investigated)
diff --git a/vm0/func.go b/vm0/func.go
deleted file mode 100644
index ee503bc..0000000
--- a/vm0/func.go
+++ /dev/null
@@ -1,75 +0,0 @@
-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())
- }
- if err := i.Run(r.Child[len(r.Child)-1], fscope); err != nil {
- panic(err)
- }
- 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
deleted file mode 100644
index 62344ed..0000000
--- a/vm0/vm.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package vm0
-
-import (
- "fmt"
- "os"
- "strings"
-
- "github.com/gnolang/parscan/parser"
-)
-
-const debug = true
-
-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) (res any, err error) {
- n := &parser.Node{}
- if n.Child, err = i.Parse(src, n); err != nil {
- return
- }
- if debug {
- n.Dot(os.Getenv("DOT"), "")
- }
- for _, nod := range n.Child {
- if err = i.Run(nod, ""); 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) (err error) {
- stop := false
-
- node.Walk2(nil, 0, func(n, a *parser.Node, k int) (ok bool) {
- // Node pre-order processing.
- switch n.Kind {
- case parser.BlockStmt:
- if a != nil && a.Kind == parser.StmtIf {
- // 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 parser.DeclFunc:
- 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 parser.LiteralNumber:
- switch v := n.Value().(type) {
- case int64:
- i.push(int(v))
- case error:
- err = v
- return false
- default:
- err = fmt.Errorf("type not supported: %T\n", v)
- return false
- }
- case parser.LiteralString:
- i.push(n.Block())
- case parser.OpInferior:
- i.stack[l-2] = i.stack[l-2].(int) < i.stack[l-1].(int)
- i.stack = i.stack[:l-1]
- case parser.OpAdd:
- i.stack[l-2] = i.stack[l-2].(int) + i.stack[l-1].(int)
- i.stack = i.stack[:l-1]
- case parser.OpSubtract:
- i.stack[l-2] = i.stack[l-2].(int) - i.stack[l-1].(int)
- i.stack = i.stack[:l-1]
- case parser.OpMultiply:
- i.stack[l-2] = i.stack[l-2].(int) * i.stack[l-1].(int)
- i.stack = i.stack[:l-1]
- case parser.OpAssign, parser.OpDefine:
- i.stack[i.stack[l-2].(int)] = i.stack[l-1]
- i.stack = i.stack[:l-2]
- case parser.StmtReturn:
- stop = true
- return false
- case parser.ExprCall:
- i.push(len(n.Child[1].Child)) // number of arguments to call
- i.callFunc(n)
- case parser.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
-}
-
-// 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
deleted file mode 100644
index a4a24e6..0000000
--- a/vm0/vm_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-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, nil)
- if err != nil {
- t.Errorf("error %v", err)
- }
- i.Adot(nodes, os.Getenv("DOT"))
- for _, n := range nodes {
- err := i.Run(n, "")
- t.Log(err)
- }
-}