summaryrefslogtreecommitdiff
path: root/vm0/vm.go
blob: 62344ed67f8df95eb7272fea81a0be18da1572cc (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
147
148
149
150
151
152
153
154
155
156
157
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 }