summaryrefslogtreecommitdiff
path: root/vm0/vm.go
blob: 4c726ed493f7e4cd4414dc9888f6d2f0c4293a7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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 }