/
world.go
155 lines (137 loc) · 3.77 KB
/
world.go
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
// package script provides a script interpreter for input files and GUI commands.
package script
import (
"fmt"
"go/token"
"strings"
)
// World stores an interpreted program's state
// like declared variables and functions.
type World struct {
*scope
toplevel *scope
}
// scope stores identifiers
type scope struct {
Identifiers map[string]Expr // set of defined identifiers
parent *scope // parent scope, if any
Doc map[string]string // documentation for identifiers
}
func NewWorld() *World {
w := new(World)
w.scope = new(scope)
w.toplevel = w.scope
w.toplevel.Doc = make(map[string]string)
w.LoadStdlib() // loads into toplevel
return w
}
func (w *scope) init() {
if w.Identifiers == nil {
w.Identifiers = make(map[string]Expr)
}
}
// adds a native variable to the world. E.g.:
// var x = 3.14
// world.Var("x", &x)
// world.MustEval("x") // returns 3.14
func (w *scope) Var(name string, addr interface{}, doc ...string) {
w.declare(name, newReflectLvalue(addr), doc...)
}
// Hack for fixing the closure caveat:
// Decleare the time variable, the only variable closures close over.
func (w *scope) TVar(name string, addr interface{}, doc ...string) {
w.declare(name, &TVar{newReflectLvalue(addr)}, doc...)
}
// adds a native variable to the world. It cannot be changed from script.
// var x = 3.14
// world.ROnly("x", &x)
// world.MustEval("x") // returns 3.14
// world.MustExec("x=2") // fails: cannot assign to x
func (w *scope) ROnly(name string, addr interface{}, doc ...string) {
w.declare(name, newReflectROnly(addr), doc...)
}
// adds a constant. Cannot be changed in any way.
func (w *scope) Const(name string, val interface{}, doc ...string) {
switch v := val.(type) {
default:
panic(fmt.Errorf("const of type %v not handled", typ(v))) // todo: const using reflection
case float64:
w.declare(name, floatLit(v), doc...)
case int:
w.declare(name, intLit(v), doc...)
}
}
// adds a special variable to the world. Upon assignment,
// v's Set() will be called.
func (w *scope) LValue(name string, v LValue, doc ...string) {
w.declare(name, v, doc...)
}
// adds a native function to the world. E.g.:
// world.Func("sin", math.Sin)
// world.MustEval("sin(0)") // returns 0
func (w *scope) Func(name string, f interface{}, doc ...string) {
w.declare(name, newFunction(f), doc...)
}
// add identifier but check that it's not declared yet.
func (w *scope) declare(key string, value Expr, doc ...string) {
if ok := w.safeDeclare(key, value); !ok {
panic("identifier " + key + " already defined")
}
w.document(key, doc...)
}
func (w *scope) safeDeclare(key string, value Expr) (ok bool) {
w.init()
lname := strings.ToLower(key)
if _, ok := w.Identifiers[lname]; ok {
return false
}
w.Identifiers[lname] = value
return true
}
// resolve identifier in this scope or its parents
func (w *scope) resolve(pos token.Pos, name string) Expr {
w.init()
lname := strings.ToLower(name)
if v, ok := w.Identifiers[lname]; ok {
return v
} else {
if w.parent != nil {
return w.parent.resolve(pos, name)
}
panic(err(pos, "undefined:", name))
}
}
func (w *World) Resolve(identifier string) (e Expr) {
defer func() {
err := recover()
if err != nil {
e = nil // not found
}
}()
e = w.toplevel.resolve(0, identifier)
return
}
// add documentation for identifier
func (w *scope) document(ident string, doc ...string) {
if w.Doc != nil { // means we want doc for this scope (toplevel only)
switch len(doc) {
default:
panic("too many doc strings for " + ident)
case 0:
w.Doc[ident] = ""
case 1:
w.Doc[ident] = doc[0]
}
}
}
func (w *World) EnterScope() {
par := w.scope
w.scope = new(scope)
w.scope.parent = par
}
func (w *World) ExitScope() {
w.scope = w.scope.parent
if w.scope == nil { // went above toplevel
panic("bug")
}
}