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 }
|