slope/interpreter.go

197 lines
4.6 KiB
Go
Raw Normal View History

package main
import (
"fmt"
)
type proc struct {
params expression
body expression
en *env
}
func eval(exp expression, en *env) (value expression) {
switch e := exp.(type) {
case symbol:
value = en.Find(e).vars[e]
case string:
value = unescapeString(e)
case bool, number:
value = e
case []expression:
switch car, _ := e[0].(symbol); car {
case "quote":
if len(e) < 2 {
panic("Invalid 'quote' syntax - too few arguments")
}
value = e[1]
case "if":
if len(e) < 3 {
panic("Invalid 'if' syntax - too few arguments")
}
if AnythingToBool(eval(e[1], en)).(bool) {
value = eval(e[2], en)
} else if len(e) > 3 {
value = eval(e[3], en)
} else {
value = make([]expression, 0)
}
case "and":
if len(e) < 2 {
panic("Invalid 'and' syntax - too few arguments")
}
OuterAnd:
for i := range e[1:] {
switch item := e[i+1].(type) {
case []expression:
value = eval(item, en)
if !AnythingToBool(value).(bool) {
break OuterAnd
}
default:
value = item
if !AnythingToBool(value).(bool) {
break OuterAnd
}
}
}
case "or":
if len(e) < 2 {
panic("Invalid 'or' syntax - too few arguments")
}
OuterOr:
for i := range e[1:] {
switch item := e[i+1].(type) {
case []expression:
value = eval(item, en)
if AnythingToBool(value).(bool) {
break OuterOr
}
default:
value = item
if AnythingToBool(value).(bool) {
break OuterOr
}
}
}
case "cond":
if len(e) < 2 {
panic("Invalid 'cond' syntax - too few arguments")
}
// CondLoop:
for _, exp := range e[1:] {
switch i := exp.(type) {
case []expression:
if len(i) < 2 {
panic("Invalid 'cond' case, cases must take the form: `(<test> <expression>)")
}
if i[0] == "else" || i[0] == symbol("else") {
return eval(i[1], en)
}
if AnythingToBool(eval(i[0], en)).(bool) {
return eval(i[1], en)
}
default:
panic("Invalid 'cond' case, cases must take the form: `(<test> <expression>)")
}
value = make([]expression, 0)
}
case "set!":
if len(e) < 3 {
panic("Invalid 'set!' syntax - too few arguments")
}
v := e[1].(symbol)
en.Find(v).vars[v] = eval(e[2], en)
value = symbol("ok")
case "define":
if len(e) < 3 {
panic("Invalid 'define' syntax - too few arguments")
}
if _, ok := e[1].(symbol); !ok {
panic("'define' expects a symbol as its first argument")
}
en.vars[e[1].(symbol)] = eval(e[2], en)
value = symbol("ok")
case "lambda", "λ":
if len(e) < 3 {
panic("'lambda' expects at least three arguments")
}
2021-08-01 21:17:02 +00:00
b := []expression{symbol("begin")}
b = append(b, e[2:]...)
value = proc{e[1], b, en}
case "begin":
for _, i := range e[1:] {
value = eval(i, en)
}
case "begin0":
for ii, i := range e[1:] {
if ii == 0 {
value = eval(i, en)
} else {
eval(i, en)
}
}
case "load":
if en.outer != nil {
panic("'load' is only callable from the global/top-level")
}
for _, fp := range e[1:] {
if _, ok := fp.(string); !ok {
panic("'load' expects filepaths as a string, a non-string value was encountered")
}
loadFiles(e[1:])
}
value = symbol("ok")
2021-08-03 21:08:39 +00:00
case "apply":
if len(e) < 3 {
panic("'apply' expects two arguments: a procedure and an argument list, too few arguments were given")
}
args := eval(e[2], en)
switch item := args.(type) {
case []expression:
return apply(eval(e[1], en), item)
default:
return apply(eval(e[1], en), []expression{item})
}
default:
operands := e[1:]
values := make([]expression, len(operands))
for i, x := range operands {
values[i] = eval(x, en)
}
value = apply(eval(e[0], en), values)
}
default:
panic("Unknown expression type encountered during EVAL")
}
return
}
func apply(procedure expression, args []expression) (value expression) {
switch p := procedure.(type) {
case func(...expression) expression:
value = p(args...)
case proc: // Mostly used by lambda
en := &env{make(vars), p.en}
switch params := p.params.(type) {
case []expression:
if len(params) > len(args) {
panic(fmt.Sprintf("Lambda expected %d arguments but received %d", len(params), len(args)))
}
for i, param := range params {
if param.(symbol) == symbol("args-list") {
en.vars[param.(symbol)] = args[i:]
break
}
en.vars[param.(symbol)] = args[i]
}
default:
en.vars[params.(symbol)] = args
}
value = eval(p.body, en)
default:
panic("Unknown procedure type encountered during APPLY")
}
return
}