2021-07-26 05:31:22 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2021-09-04 21:14:30 +00:00
|
|
|
"sort"
|
|
|
|
"strings"
|
2021-07-26 05:31:22 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type proc struct {
|
2021-07-31 20:25:20 +00:00
|
|
|
params expression
|
|
|
|
body expression
|
|
|
|
en *env
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func eval(exp expression, en *env) (value expression) {
|
|
|
|
switch e := exp.(type) {
|
|
|
|
case symbol:
|
|
|
|
value = en.Find(e).vars[e]
|
2021-09-05 21:47:47 +00:00
|
|
|
case string:
|
|
|
|
value = unescapeString(e)
|
|
|
|
case bool, number:
|
2021-08-17 20:44:24 +00:00
|
|
|
value = e
|
|
|
|
case exception:
|
|
|
|
if panicOnException {
|
|
|
|
panic(string(e))
|
|
|
|
}
|
2021-08-09 22:53:27 +00:00
|
|
|
value = e
|
2021-07-26 05:31:22 +00:00
|
|
|
case []expression:
|
|
|
|
switch car, _ := e[0].(symbol); car {
|
|
|
|
case "quote":
|
2021-07-27 03:19:59 +00:00
|
|
|
if len(e) < 2 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("Invalid 'quote' syntax - too few arguments")
|
|
|
|
break
|
2021-07-27 03:19:59 +00:00
|
|
|
}
|
2021-07-26 05:31:22 +00:00
|
|
|
value = e[1]
|
|
|
|
case "if":
|
2021-07-30 23:03:25 +00:00
|
|
|
if len(e) < 3 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("Invalid 'if' syntax - too few arguments")
|
|
|
|
break
|
2021-07-27 03:19:59 +00:00
|
|
|
}
|
2021-07-26 05:31:22 +00:00
|
|
|
if AnythingToBool(eval(e[1], en)).(bool) {
|
|
|
|
value = eval(e[2], en)
|
2021-07-30 23:03:25 +00:00
|
|
|
} else if len(e) > 3 {
|
2021-07-26 05:31:22 +00:00
|
|
|
value = eval(e[3], en)
|
2021-07-30 23:03:25 +00:00
|
|
|
} else {
|
|
|
|
value = make([]expression, 0)
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
2021-08-01 22:13:25 +00:00
|
|
|
case "and":
|
|
|
|
if len(e) < 2 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("Invalid 'and' syntax - too few arguments")
|
|
|
|
break
|
2021-08-01 22:13:25 +00:00
|
|
|
}
|
2021-09-04 14:45:35 +00:00
|
|
|
OuterAnd:
|
2021-08-01 22:13:25 +00:00
|
|
|
for i := range e[1:] {
|
|
|
|
switch item := e[i+1].(type) {
|
|
|
|
case []expression:
|
|
|
|
value = eval(item, en)
|
|
|
|
if !AnythingToBool(value).(bool) {
|
|
|
|
break OuterAnd
|
|
|
|
}
|
|
|
|
default:
|
2021-08-25 22:16:13 +00:00
|
|
|
value = eval(item, en)
|
2021-08-01 22:13:25 +00:00
|
|
|
if !AnythingToBool(value).(bool) {
|
|
|
|
break OuterAnd
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case "or":
|
|
|
|
if len(e) < 2 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("Invalid 'or' syntax - too few arguments")
|
|
|
|
break
|
2021-08-01 22:13:25 +00:00
|
|
|
}
|
2021-09-04 14:45:35 +00:00
|
|
|
OuterOr:
|
2021-08-01 22:13:25 +00:00
|
|
|
for i := range e[1:] {
|
|
|
|
switch item := e[i+1].(type) {
|
|
|
|
case []expression:
|
|
|
|
value = eval(item, en)
|
|
|
|
if AnythingToBool(value).(bool) {
|
|
|
|
break OuterOr
|
|
|
|
}
|
|
|
|
default:
|
2021-08-25 22:16:13 +00:00
|
|
|
value = eval(item, en)
|
2021-08-01 22:13:25 +00:00
|
|
|
if AnythingToBool(value).(bool) {
|
|
|
|
break OuterOr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-27 03:19:59 +00:00
|
|
|
case "cond":
|
|
|
|
if len(e) < 2 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("Invalid 'cond' syntax - too few arguments")
|
|
|
|
break
|
2021-07-27 03:19:59 +00:00
|
|
|
}
|
2021-09-19 23:05:11 +00:00
|
|
|
CondLoop:
|
2021-07-27 03:19:59 +00:00
|
|
|
for _, exp := range e[1:] {
|
|
|
|
switch i := exp.(type) {
|
|
|
|
case []expression:
|
|
|
|
if len(i) < 2 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("Invalid 'cond' case, cases must take the form: `(<test> <expression>)")
|
2021-09-08 03:35:08 +00:00
|
|
|
break CondLoop
|
2021-07-27 03:19:59 +00:00
|
|
|
}
|
2021-08-02 06:06:52 +00:00
|
|
|
if i[0] == "else" || i[0] == symbol("else") {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = eval(i[1], en)
|
2021-09-08 03:35:08 +00:00
|
|
|
break CondLoop
|
2021-08-02 06:06:52 +00:00
|
|
|
}
|
|
|
|
if AnythingToBool(eval(i[0], en)).(bool) {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = eval(i[1], en)
|
2021-09-08 03:35:08 +00:00
|
|
|
break CondLoop
|
2021-07-27 03:19:59 +00:00
|
|
|
}
|
|
|
|
default:
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("Invalid 'cond' case, cases must take the form: `(<test> <expression>)")
|
2021-09-08 03:35:08 +00:00
|
|
|
break CondLoop
|
2021-07-27 03:19:59 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-26 05:31:22 +00:00
|
|
|
case "set!":
|
2021-07-27 03:19:59 +00:00
|
|
|
if len(e) < 3 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("Invalid 'set!' syntax - too few arguments")
|
|
|
|
break
|
2021-07-27 03:19:59 +00:00
|
|
|
}
|
2021-08-23 22:39:48 +00:00
|
|
|
v, ok := e[1].(symbol)
|
|
|
|
if !ok {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("'set!' expected a symbol as its first argument, a non-symbol was provided")
|
|
|
|
break
|
2021-08-23 22:39:48 +00:00
|
|
|
}
|
2021-08-16 21:53:52 +00:00
|
|
|
val := eval(e[2], en)
|
|
|
|
en.Find(v).vars[v] = val
|
|
|
|
value = val
|
2021-07-26 05:31:22 +00:00
|
|
|
case "define":
|
2021-07-27 03:19:59 +00:00
|
|
|
if len(e) < 3 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("Invalid 'define' syntax - too few arguments")
|
|
|
|
break
|
2021-07-27 03:19:59 +00:00
|
|
|
}
|
|
|
|
if _, ok := e[1].(symbol); !ok {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("'define' expects a symbol as its first argument")
|
|
|
|
break
|
2021-07-27 03:19:59 +00:00
|
|
|
}
|
2021-08-16 21:53:52 +00:00
|
|
|
val := eval(e[2], en)
|
|
|
|
en.vars[e[1].(symbol)] = val
|
|
|
|
value = val
|
2021-08-02 21:37:57 +00:00
|
|
|
case "lambda", "λ":
|
|
|
|
if len(e) < 3 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("'lambda' expects at least three arguments")
|
|
|
|
break
|
2021-08-02 21:37:57 +00:00
|
|
|
}
|
2021-08-01 21:17:02 +00:00
|
|
|
b := []expression{symbol("begin")}
|
|
|
|
b = append(b, e[2:]...)
|
|
|
|
value = proc{e[1], b, en}
|
2021-07-26 05:31:22 +00:00
|
|
|
case "begin":
|
|
|
|
for _, i := range e[1:] {
|
|
|
|
value = eval(i, en)
|
|
|
|
}
|
2021-07-31 06:31:08 +00:00
|
|
|
case "begin0":
|
|
|
|
for ii, i := range e[1:] {
|
|
|
|
if ii == 0 {
|
|
|
|
value = eval(i, en)
|
|
|
|
} else {
|
|
|
|
eval(i, en)
|
|
|
|
}
|
|
|
|
}
|
2021-08-23 22:39:48 +00:00
|
|
|
case "usage":
|
2021-09-04 21:14:30 +00:00
|
|
|
if len(e) < 2 {
|
|
|
|
fmt.Print("(usage [[procedure: symbol]])\n\n\033[1;4mKnown procedures\033[0m\n\n")
|
|
|
|
keys := make([]string, 0, len(usageStrings))
|
|
|
|
var out strings.Builder
|
|
|
|
for key, _ := range usageStrings {
|
|
|
|
keys = append(keys, key)
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
for i := range keys {
|
|
|
|
out.WriteString(fmt.Sprintf("%-26s", keys[i]))
|
2021-09-19 23:05:11 +00:00
|
|
|
if (i+1)%2 == 0 {
|
2021-09-04 21:14:30 +00:00
|
|
|
out.WriteRune('\n')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Println(out.String())
|
2021-09-07 05:44:27 +00:00
|
|
|
value = make([]expression, 0)
|
|
|
|
break
|
2021-09-04 21:14:30 +00:00
|
|
|
}
|
2021-08-23 22:39:48 +00:00
|
|
|
proc, ok := e[1].(string)
|
|
|
|
if !ok {
|
|
|
|
p, ok2 := e[1].(symbol)
|
|
|
|
if !ok2 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = exception("'usage' expected a string or symbol as its first argument, a non-string non-symbol value was given")
|
2021-08-23 22:39:48 +00:00
|
|
|
}
|
|
|
|
proc = string(p)
|
|
|
|
}
|
|
|
|
v, ok := usageStrings[proc]
|
|
|
|
if !ok {
|
|
|
|
fmt.Printf("%q does not have a usage definition\n", proc)
|
|
|
|
} else {
|
|
|
|
fmt.Println(v)
|
|
|
|
}
|
|
|
|
value = make([]expression, 0)
|
2021-08-02 21:37:57 +00:00
|
|
|
case "load":
|
|
|
|
if en.outer != nil {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = expression("'load' is only callable from the global/top-level")
|
|
|
|
break
|
2021-08-02 21:37:57 +00:00
|
|
|
}
|
|
|
|
for _, fp := range e[1:] {
|
|
|
|
if _, ok := fp.(string); !ok {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = expression("'load' expects filepaths as a string, a non-string value was encountered")
|
|
|
|
break
|
2021-08-02 21:37:57 +00:00
|
|
|
}
|
2021-08-17 05:20:12 +00:00
|
|
|
}
|
|
|
|
loadFiles(e[1:])
|
|
|
|
value = symbol("ok")
|
|
|
|
case "load-mod":
|
|
|
|
fullLoadEnv := env{make(map[symbol]expression), &globalenv}
|
|
|
|
for _, fp := range e[1:] {
|
|
|
|
if p, ok := fp.(string); !ok {
|
|
|
|
panic("'load-mod' expects filepaths as a string, a non-string value was encountered")
|
|
|
|
} else {
|
2021-08-17 21:59:34 +00:00
|
|
|
modEnv, err := RunModule(p, false)
|
2021-08-17 05:20:12 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Errorf("'load-mod' failed loading module %s: %s", p, err.Error()))
|
|
|
|
}
|
|
|
|
for k, v := range modEnv.vars {
|
|
|
|
fullLoadEnv.vars[k] = v
|
|
|
|
}
|
2021-08-17 21:59:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for k, v := range fullLoadEnv.vars {
|
|
|
|
globalenv.vars[k] = v
|
|
|
|
}
|
|
|
|
value = symbol("ok")
|
|
|
|
case "load-mod-file":
|
|
|
|
fullLoadEnv := env{make(map[symbol]expression), &globalenv}
|
|
|
|
for _, fp := range e[1:] {
|
|
|
|
if p, ok := fp.(string); !ok {
|
|
|
|
panic("'load-mod-file' expects relative filepaths as a string, a non-string value was encountered")
|
|
|
|
} else {
|
|
|
|
modEnv, err := RunModule(p, true)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Errorf("'load-mod-file' failed loading module %s: %s", p, err.Error()))
|
|
|
|
}
|
|
|
|
for k, v := range modEnv.vars {
|
|
|
|
fullLoadEnv.vars[k] = v
|
|
|
|
}
|
2021-08-17 05:20:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for k, v := range fullLoadEnv.vars {
|
|
|
|
globalenv.vars[k] = v
|
2021-08-02 21:37:57 +00:00
|
|
|
}
|
|
|
|
value = symbol("ok")
|
2021-08-03 22:37:41 +00:00
|
|
|
case "filter":
|
|
|
|
if len(e) < 3 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = expression("'filter' expects two arguments: a procedure and an argument list, too few arguments were given")
|
|
|
|
break
|
2021-08-03 22:37:41 +00:00
|
|
|
}
|
|
|
|
proc := eval(e[1], en)
|
|
|
|
list, ok := eval(e[2], en).([]expression)
|
|
|
|
if !ok {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = expression("'filter' expects a list as its second argument, a non-list value was given")
|
|
|
|
break
|
2021-08-03 22:37:41 +00:00
|
|
|
}
|
|
|
|
val := make([]expression, 0)
|
|
|
|
for i := range list {
|
|
|
|
app := apply(proc, list[i:i+1])
|
|
|
|
v := AnythingToBool(app).(bool)
|
|
|
|
if v {
|
|
|
|
val = append(val, list[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
value = val
|
2021-08-03 21:08:39 +00:00
|
|
|
case "apply":
|
|
|
|
if len(e) < 3 {
|
2021-09-07 05:44:27 +00:00
|
|
|
value = expression("'apply' expects two arguments: a procedure and an argument list, too few arguments were given")
|
|
|
|
break
|
2021-08-03 21:08:39 +00:00
|
|
|
}
|
|
|
|
args := eval(e[2], en)
|
|
|
|
switch item := args.(type) {
|
|
|
|
case []expression:
|
2021-09-07 05:44:27 +00:00
|
|
|
value = apply(eval(e[1], en), item)
|
2021-08-03 21:08:39 +00:00
|
|
|
default:
|
2021-09-07 05:44:27 +00:00
|
|
|
value = apply(eval(e[1], en), []expression{item})
|
2021-08-03 21:08:39 +00:00
|
|
|
}
|
2021-07-26 05:31:22 +00:00
|
|
|
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)
|
2021-07-31 20:25:20 +00:00
|
|
|
}
|
|
|
|
default:
|
2021-07-26 05:31:22 +00:00
|
|
|
panic("Unknown expression type encountered during EVAL")
|
2021-07-31 20:25:20 +00:00
|
|
|
}
|
2021-09-07 05:44:27 +00:00
|
|
|
|
|
|
|
if e, ok := value.(exception); panicOnException && ok {
|
|
|
|
panic(string(e))
|
|
|
|
}
|
2021-07-31 20:25:20 +00:00
|
|
|
return
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
2021-09-19 23:05:11 +00:00
|
|
|
if len(params)-variadic(params) > len(args) {
|
2021-08-13 21:11:47 +00:00
|
|
|
return expression(fmt.Sprintf("Lambda expected %d arguments but received %d", len(params), len(args)))
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
for i, param := range params {
|
2021-08-01 03:04:26 +00:00
|
|
|
if param.(symbol) == symbol("args-list") {
|
2021-09-19 23:05:11 +00:00
|
|
|
if len(args) >= len(params) {
|
|
|
|
en.vars[param.(symbol)] = args[i:]
|
|
|
|
} else {
|
|
|
|
en.vars[param.(symbol)] = make([]expression, 0)
|
|
|
|
}
|
2021-08-01 03:04:26 +00:00
|
|
|
break
|
|
|
|
}
|
2021-07-26 05:31:22 +00:00
|
|
|
en.vars[param.(symbol)] = args[i]
|
|
|
|
}
|
|
|
|
default:
|
2021-08-01 06:31:38 +00:00
|
|
|
en.vars[params.(symbol)] = args
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
value = eval(p.body, en)
|
|
|
|
default:
|
|
|
|
panic("Unknown procedure type encountered during APPLY")
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|