slope/interpreter.go

220 lines
5.5 KiB
Go

package main
import (
"fmt"
"strings"
)
type proc struct {
params expression
body expression
en *env
predicates map[symbol]symbol
}
type macro struct {
params expression
body expression
en *env
}
func eval(exp expression, en *env) (value expression) {
switch e := exp.(type) {
case symbol:
environ := en
if strings.Contains(string(e), "::") {
parts := strings.SplitN(string(e), "::", 2)
if len(parts) == 2 {
ans, ok1 := altnamespaces[parts[0]]
ns, ok2 := namespaces[parts[0]]
if ok1 {
ns, ok2 = namespaces[ans]
}
if ok2 {
environ = &ns
e = symbol(parts[1])
}
}
}
ex, err := environ.Find(e)
if err != nil {
value = exception(err.Error())
} else {
value = ex.vars[e]
}
case string:
value = e
case bool, number:
value = e
case exception:
if panicOnException {
panic(string(e))
}
value = e
case []expression:
switch car, _ := e[0].(symbol); car {
case "quote":
value = specialQuote(e)
case "if":
value = specialIf(e, en)
case "and":
value = specialAnd(e, en)
case "or":
value = specialOr(e, en)
case "case":
value = specialCase(e, en)
case "cond":
value = specialCond(e, en)
case "for":
value = specialFor(e, en)
case "set!":
value = specialSet(e, en)
case "define":
value = specialDefine(e, en)
case "lambda", "λ":
value = specialLambda(e, en)
case "macro":
value = specialMacro(e, en)
case "begin":
value = specialBegin(e, en)
case "begin0":
value = specialBegin0(e, en)
case "usage":
value = specialUsage(e)
case "load":
value = specialLoad(e, en)
case "load-mod":
value = specialLoadMod(e, en)
case "load-mod-file":
value = specialLoadModFile(e, en)
case "exists?":
value = specialExists(e, en)
case "inspect":
value = specialInspect(e, en)
case "apply":
value = specialApply(e, en)
case "eval":
value = specialEval(e, en)
default:
operands := e[1:]
values := make([]expression, len(operands))
first := eval(e[0], en)
_, isMacro := first.(macro)
for i, x := range operands {
if isMacro {
values[i] = x
} else {
values[i] = eval(x, en)
}
}
value = apply(first, values, e[0])
}
default:
panic("Unknown expression type encountered during EVAL")
}
if e, ok := value.(exception); panicOnException && ok {
panic(string(e))
}
return
}
func apply(procedure expression, args []expression, name expression) (value expression) {
switch p := procedure.(type) {
case func(...expression) expression:
value = p(args...)
case macro:
en := &env{make(vars), p.en}
switch params := p.params.(type) {
case []expression:
if variadic(params) > len(args) {
return exception(fmt.Sprintf("Macro %q expected %d argument(s) but received %d", String(name, false), len(params), len(args)))
}
for i, param := range params {
if param.(symbol) == symbol("args-list") || param.(symbol) == symbol("...") {
if len(args) >= len(params) {
en.vars[param.(symbol)] = args[i:]
} else {
en.vars[param.(symbol)] = make([]expression, 0)
}
break
}
en.vars[param.(symbol)] = args[i]
}
default:
en.vars[params.(symbol)] = args
}
value = eval(p.body, en)
case proc: // Mostly used by lambda
// FIXME the parsing or the application of lists is donked in certain contexts...
// not sure what is happening: (apply display-lines [1 2 [3 4]]) does not work
en := &env{make(vars), p.en}
switch params := p.params.(type) {
case []expression:
if variadic(params) > len(args) {
return exception(fmt.Sprintf("Lambda %q expected %d argument(s) but received %d", String(name, false), len(params), len(args)))
}
for i, param := range params {
variadic := false
if param.(symbol) == symbol("args-list") || param.(symbol) == symbol("...") {
variadic = true
}
result := true
if v, ok := p.predicates[param.(symbol)]; ok {
if variadic {
r := apply(eval(v, p.en), []expression{args[i:]}, v)
result = AnythingToBool(r).(bool)
} else {
r := apply(eval(v, p.en), []expression{args[i]}, v)
result = AnythingToBool(r).(bool)
}
if !result {
return exception(fmt.Sprintf("Type Error: In call to %q argument %d failed to fulfill predicate %q", String(name, false), i+1, v))
}
}
if variadic {
if len(args) >= len(params) {
en.vars[param.(symbol)] = args[i:]
} else {
en.vars[param.(symbol)] = make([]expression, 0)
}
break
}
en.vars[param.(symbol)] = args[i]
}
default:
// This wont come up with changes to how lambdas without a () structure work
// hint: they are given a ()
en.vars[params.(symbol)] = args
}
value = eval(p.body, en)
default:
panic(fmt.Sprintf("Unexpected procedure symbol encountered during APPLY. Expected \"procedure\", got %q: %s", globalenv.vars["type"].(func(...expression) expression)(procedure), String(procedure, true)))
}
return
}
// Because lambda and quote expressions do not have their
// content evaluated at creation strings were not getting
// unescaped. As such, they were not usable in the same way
// as strings used elsewhere. This recursively unescapes all
// of the strings as need be so that string behavior is
// consistent accross the system.
func stringUnescapeEval(exp expression) expression {
switch e := exp.(type) {
case string:
return e
case []expression:
values := make([]expression, len(e))
for i, x := range e {
values[i] = stringUnescapeEval(x)
}
return values
default:
return e
}
}