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