733 lines
19 KiB
Go
733 lines
19 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.rawtext.club/sloum/slope/termios"
|
|
ln "github.com/peterh/liner"
|
|
)
|
|
|
|
type proc struct {
|
|
params expression
|
|
body expression
|
|
en *env
|
|
}
|
|
|
|
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":
|
|
if len(e) < 2 {
|
|
value = exception("Invalid 'quote' syntax - too few arguments")
|
|
break
|
|
}
|
|
value = stringUnescapeEval(e[1])
|
|
case "if":
|
|
if len(e) < 3 {
|
|
value = exception("Invalid 'if' syntax - too few arguments")
|
|
break
|
|
}
|
|
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 {
|
|
value = exception("Invalid 'and' syntax - too few arguments")
|
|
break
|
|
}
|
|
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 = eval(item, en)
|
|
if !AnythingToBool(value).(bool) {
|
|
break OuterAnd
|
|
}
|
|
}
|
|
}
|
|
case "or":
|
|
if len(e) < 2 {
|
|
value = exception("Invalid 'or' syntax - too few arguments")
|
|
break
|
|
}
|
|
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 = eval(item, en)
|
|
if AnythingToBool(value).(bool) {
|
|
break OuterOr
|
|
}
|
|
}
|
|
}
|
|
case "case":
|
|
if len(e) < 3 {
|
|
value = exception("Invalid 'case' syntax - too few arguments")
|
|
}
|
|
target := eval(e[1], en)
|
|
CaseLoop:
|
|
for _, exp := range e[2:] {
|
|
switch i := exp.(type) {
|
|
case []expression:
|
|
if len(i) < 2 {
|
|
value = exception("Invalid 'case' case, cases must take the form: `(<test> <expression>)")
|
|
break CaseLoop
|
|
}
|
|
if i[0] == "else" || i[0] == symbol("else") {
|
|
value = eval(i[1], en)
|
|
break CaseLoop
|
|
}
|
|
if reflect.DeepEqual(eval(i[0], en), target) {
|
|
value = eval(i[1], en)
|
|
break CaseLoop
|
|
}
|
|
default:
|
|
value = exception("Invalid 'case' case, cases must take the form: `(<test> <expression>)")
|
|
break CaseLoop
|
|
}
|
|
}
|
|
if value == nil {
|
|
value = make([]expression, 0)
|
|
}
|
|
|
|
case "cond":
|
|
if len(e) < 2 {
|
|
value = exception("Invalid 'cond' syntax - too few arguments")
|
|
break
|
|
}
|
|
CondLoop:
|
|
for _, exp := range e[1:] {
|
|
switch i := exp.(type) {
|
|
case []expression:
|
|
if len(i) < 2 {
|
|
value = exception("Invalid 'cond' case, cases must take the form: `(<test> <expression>)")
|
|
break CondLoop
|
|
}
|
|
if i[0] == "else" || i[0] == symbol("else") {
|
|
value = eval(i[1], en)
|
|
break CondLoop
|
|
}
|
|
if AnythingToBool(eval(i[0], en)).(bool) {
|
|
value = eval(i[1], en)
|
|
break CondLoop
|
|
}
|
|
default:
|
|
value = exception("Invalid 'cond' case, cases must take the form: `(<test> <expression>)")
|
|
break CondLoop
|
|
}
|
|
}
|
|
if value == nil {
|
|
value = make([]expression, 0)
|
|
}
|
|
case "for":
|
|
if len(e) < 3 {
|
|
value = exception("'for' requires at least two arguments: a list of initializers and a test")
|
|
break
|
|
}
|
|
// TODO follow the lisp do style: https://www.tutorialspoint.com/lisp/lisp_do.htm
|
|
|
|
// Set up args/iterators
|
|
args, ok := e[1].([]expression)
|
|
if !ok {
|
|
value = exception("'for' expected a list of arguments and values as its first argument, a non-list value was received")
|
|
break
|
|
}
|
|
newEnv := &env{make(map[symbol]expression), en}
|
|
updates := []expression{symbol("begin")}
|
|
for _, v := range args {
|
|
arg, ok := v.([]expression)
|
|
if varname, varok := arg[0].(symbol); varok && ok && len(arg) >= 2 {
|
|
newEnv.vars[varname] = eval(arg[1], en)
|
|
} else {
|
|
value = exception("'for' expected its first argument to be a list of lists, each with a symbol, a value, and an optional update expression. A list was given, but it contained a non-list value")
|
|
break
|
|
}
|
|
if len(arg) >= 3 {
|
|
updates = append(updates, []expression{symbol("set!"), arg[0], arg[2]})
|
|
}
|
|
}
|
|
|
|
// Get test and return expression
|
|
testReturn, ok := e[2].([]expression)
|
|
if !ok || len(testReturn) == 0 {
|
|
value = exception("'for' expected a list containing a logic test and an optional return value expression for its second argument, a non-list was given")
|
|
break
|
|
}
|
|
test := testReturn[0]
|
|
var returnExpression expression = []expression{symbol("list")}
|
|
if len(testReturn) > 1 {
|
|
returnExpression = testReturn[1]
|
|
}
|
|
|
|
body := []expression{symbol("begin")}
|
|
body = append(body, e[3:]...)
|
|
|
|
for {
|
|
// Check the condition by evaluating the test
|
|
if !AnythingToBool(eval(test, newEnv)).(bool) {
|
|
break
|
|
}
|
|
|
|
// Run the body code
|
|
eval(body, newEnv)
|
|
|
|
// Run the iteration updates
|
|
eval(updates, newEnv)
|
|
}
|
|
|
|
value = eval(returnExpression, newEnv)
|
|
case "set!":
|
|
if len(e) < 3 {
|
|
value = exception("Invalid 'set!' syntax - too few arguments")
|
|
break
|
|
}
|
|
v, ok := e[1].(symbol)
|
|
if !ok {
|
|
value = exception("'set!' expected a symbol as its first argument, a non-symbol was provided")
|
|
break
|
|
}
|
|
if strings.Contains(string(v), "::") {
|
|
value = exception("'set!' cannot modify a symbol reference within a module, designated by '::' within a symbol name")
|
|
break
|
|
}
|
|
val := eval(e[2], en)
|
|
ex, err := en.Find(v)
|
|
if err != nil {
|
|
value = exception(err.Error())
|
|
} else {
|
|
ex.vars[v] = val
|
|
value = val
|
|
}
|
|
case "define":
|
|
if len(e) < 3 {
|
|
value = exception("Invalid 'define' syntax - too few arguments")
|
|
break
|
|
}
|
|
sym, ok := e[1].(symbol)
|
|
if !ok {
|
|
value = exception("'define' expects a symbol as its first argument")
|
|
break
|
|
}
|
|
if strings.Contains(string(sym), "::") {
|
|
value = exception("'define' cannot create a symbol reference within a module, designated by '::' within a symbol name")
|
|
break
|
|
}
|
|
val := eval(e[2], en)
|
|
en.vars[sym] = val
|
|
value = val
|
|
case "lambda", "λ":
|
|
if len(e) < 3 {
|
|
value = exception("'lambda' expects at least three arguments")
|
|
break
|
|
}
|
|
b := []expression{symbol("begin")}
|
|
b = append(b, e[2:]...)
|
|
value = proc{e[1], stringUnescapeEval(b), en}
|
|
case "macro":
|
|
if len(e) < 3 {
|
|
value = exception("'macro' expects at least three arguments")
|
|
break
|
|
}
|
|
b := []expression{symbol("begin")}
|
|
b = append(b, e[2:]...)
|
|
value = macro{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 "usage":
|
|
var procSigRE = regexp.MustCompile(`(?s)(\()([^() ]+\b)([^)]*)(\))(?:(\s*=>)([^\n]*))?(.*)`)
|
|
var replacer = "\033[40;33;1m$1\033[95m$2\033[92m$3\033[33m$4\033[94m$5\033[36m$6\033[0m$7"
|
|
|
|
if len(e) < 2 {
|
|
// XXX - Branch for viewing list of all std procs
|
|
var out strings.Builder
|
|
header := "(usage [[procedure: symbol]])\n\n\033[1;4mKnown Symbols\033[0m\n\n"
|
|
out.WriteString(procSigRE.ReplaceAllString(header, replacer))
|
|
keys := make([]string, 0, len(usageStrings))
|
|
for key, _ := range usageStrings {
|
|
keys = append(keys, key)
|
|
}
|
|
|
|
var width int = 60
|
|
if globalenv.vars[symbol("slope-interactive?")] != false {
|
|
width, _ = termios.GetWindowSize()
|
|
}
|
|
printedWidth := 0
|
|
sort.Strings(keys)
|
|
for i := range keys {
|
|
if printedWidth+26 >= width {
|
|
out.WriteRune('\n')
|
|
printedWidth = 0
|
|
}
|
|
out.WriteString(fmt.Sprintf("%-26s", keys[i]))
|
|
printedWidth += 26
|
|
}
|
|
|
|
if len(namespaces) > 0 {
|
|
out.WriteString("\n\n\033[1;4mKnown Modules\033[0m\n\n")
|
|
taken := make(map[string]bool)
|
|
for k, v := range altnamespaces {
|
|
out.WriteString(fmt.Sprintf("%-12s -> %s\n", v, k))
|
|
taken[v] = true
|
|
}
|
|
for k := range namespaces {
|
|
if _, ok := taken[string(k)]; !ok {
|
|
out.WriteString(string(k))
|
|
out.WriteRune('\n')
|
|
}
|
|
}
|
|
}
|
|
|
|
SysoutPrint(out.String(), Sysout)
|
|
value = make([]expression, 0)
|
|
break
|
|
} else if len(e) == 2 {
|
|
proc, ok := e[1].(string)
|
|
if !ok {
|
|
p, ok2 := e[1].(symbol)
|
|
if !ok2 {
|
|
value = exception("'usage' expected a string or symbol as its first argument, a non-string non-symbol value was given")
|
|
break
|
|
}
|
|
proc = string(p)
|
|
}
|
|
if strings.HasSuffix(proc, "::") {
|
|
// XXX - Print list of module procedures
|
|
ns := proc[:len(proc)-2]
|
|
module := ns
|
|
altName, ok := altnamespaces[ns]
|
|
if ok {
|
|
module = altName
|
|
}
|
|
useMap, err := GetUsageMap(module)
|
|
if err != nil {
|
|
value = exception("'usage' encountered an error: " + err.Error())
|
|
break
|
|
}
|
|
SysoutPrint(fmt.Sprintf("\033[1;4m%s's Known Symbols\033[0m\n\n", ns), Sysout)
|
|
for k := range useMap {
|
|
SysoutPrint(fmt.Sprintf("%v\n", k), Sysout)
|
|
}
|
|
} else if strings.Contains(proc, "::") {
|
|
// XXX - Show info for a module symbol
|
|
pair := strings.SplitN(proc, "::", 2)
|
|
if len(pair) != 2 {
|
|
value = exception("'usage' was given an invalid module/symbol format")
|
|
break
|
|
}
|
|
ns := pair[0]
|
|
module := ns
|
|
altName, ok := altnamespaces[ns]
|
|
if ok {
|
|
module = altName
|
|
}
|
|
useMap, err := GetUsageMap(module)
|
|
if err != nil {
|
|
value = exception("'usage' encountered an error: " + err.Error())
|
|
break
|
|
}
|
|
subFunc, ok := useMap[pair[1]]
|
|
if !ok {
|
|
value = exception("'usage' could not find the requested symbol within the " + ns + "module's usage data")
|
|
break
|
|
}
|
|
SysoutPrint(fmt.Sprintf("%v\n", procSigRE.ReplaceAllString(subFunc, replacer)), Sysout)
|
|
|
|
} else {
|
|
// XXX - Show info for a builtin
|
|
v, ok := usageStrings[proc]
|
|
if !ok {
|
|
SysoutPrint(fmt.Sprintf("%q does not have a usage definition\n", proc), Sysout)
|
|
} else {
|
|
SysoutPrint(fmt.Sprintf("%v\n\n", procSigRE.ReplaceAllString(v, replacer)), Sysout)
|
|
}
|
|
}
|
|
}
|
|
value = make([]expression, 0)
|
|
case "load":
|
|
if en.outer != nil {
|
|
value = exception("'load' is only callable from the global/top-level")
|
|
break
|
|
}
|
|
files := make([]expression, 0, len(e)-1)
|
|
for _, fp := range e[1:] {
|
|
var p string
|
|
if _, ok := fp.([]expression); ok {
|
|
p = String(eval(fp, en), false)
|
|
} else {
|
|
p = String(fp, false)
|
|
}
|
|
files = append(files, p)
|
|
}
|
|
loadFiles(files)
|
|
value = symbol("ok")
|
|
case "load-mod":
|
|
if len(e) < 2 {
|
|
value = exception("'load-mod' expected a module name, no value was given")
|
|
break
|
|
}
|
|
fullLoadEnv := env{make(map[symbol]expression), &globalenv}
|
|
modName := e[1]
|
|
|
|
var p string
|
|
if _, ok := modName.([]expression); ok {
|
|
p = String(eval(modName, en), false)
|
|
} else {
|
|
p = String(modName, false)
|
|
}
|
|
modEnv, err := RunModule(p, false)
|
|
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
|
|
}
|
|
|
|
altName := ""
|
|
if len(e) > 2 {
|
|
if _, ok := modName.([]expression); ok {
|
|
altName = String(eval(e[2], en), false)
|
|
} else {
|
|
altName = String(e[2], false)
|
|
}
|
|
}
|
|
|
|
namespaces[p] = fullLoadEnv
|
|
if altName != "" {
|
|
altnamespaces[altName] = p
|
|
}
|
|
value = symbol("ok")
|
|
case "load-mod-file":
|
|
fullLoadEnv := env{make(map[symbol]expression), &globalenv}
|
|
for _, fp := range e[1:] {
|
|
var p string
|
|
if _, ok := fp.([]expression); ok {
|
|
p = String(eval(fp, en), false)
|
|
} else {
|
|
p = String(fp, false)
|
|
}
|
|
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
|
|
}
|
|
}
|
|
for k, v := range fullLoadEnv.vars {
|
|
globalenv.vars[k] = v
|
|
}
|
|
value = symbol("ok")
|
|
case "exists?":
|
|
if len(e) == 0 {
|
|
value = exception("'exists?' expects at least one symbol or string, no values were given")
|
|
}
|
|
value = true
|
|
var err error
|
|
for i := range e[1:] {
|
|
doPanic := panicOnException
|
|
if doPanic {
|
|
panicOnException = false
|
|
}
|
|
// TODO make this work with interpreter forms
|
|
_, ok := e[i].([]expression)
|
|
if ok {
|
|
_, err = en.Find(symbol(String(eval(e[i+1], en), false)))
|
|
} else {
|
|
_, err = en.Find(symbol(String(e[i+1], false)))
|
|
}
|
|
if doPanic {
|
|
panicOnException = true
|
|
}
|
|
if err != nil {
|
|
value = false
|
|
break
|
|
}
|
|
}
|
|
case "inspect":
|
|
// Make sure we are attached to a tty
|
|
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 {
|
|
value = false
|
|
break
|
|
}
|
|
if fileInfo, _ := os.Stdin.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 {
|
|
value = false
|
|
break
|
|
}
|
|
initialState, _ := ln.TerminalMode()
|
|
liner := ln.NewLiner()
|
|
linerState, _ := ln.TerminalMode()
|
|
defer liner.Close()
|
|
histFile := ExpandedAbsFilepath(filepath.Join(getModBaseDir(), "..", historyFilename))
|
|
if f, e := os.Open(histFile); e == nil {
|
|
liner.ReadHistory(f)
|
|
f.Close()
|
|
}
|
|
// Set up completion
|
|
liner.SetTabCompletionStyle(ln.TabCircular)
|
|
liner.SetCompleter(func(l string) (c []string) {
|
|
if len(l) == 0 {
|
|
return
|
|
}
|
|
lastIndex := strings.LastIndexAny(l, "( \n")
|
|
c = append(c, completeFromMap(usageStrings, l, lastIndex)...)
|
|
c = append(c, completeFromMap(getAllModFuncNames(), l, lastIndex)...)
|
|
sort.Strings(c)
|
|
return
|
|
})
|
|
inspectID := "inspect"
|
|
if len(e) > 1 {
|
|
inspectID = String(eval(e[1], en), false)
|
|
}
|
|
var text strings.Builder
|
|
var cont bool
|
|
var raw bool
|
|
var match bool
|
|
for {
|
|
globalenv.vars[symbol("slope-interactive?")] = true
|
|
if linerState != nil {
|
|
linerState.ApplyMode()
|
|
}
|
|
in := prompt(liner, cont, inspectID)
|
|
if initialState != nil {
|
|
initialState.ApplyMode()
|
|
}
|
|
if in == "uninspect" || in == "(uninspect)" {
|
|
break
|
|
}
|
|
if len(strings.TrimSpace(in)) == 0 && !raw {
|
|
continue
|
|
}
|
|
text.WriteString(in)
|
|
text.WriteRune('\n')
|
|
|
|
match, raw = stringParensMatch(text.String())
|
|
if !match {
|
|
cont = true
|
|
} else {
|
|
cont = false
|
|
outputResult(text.String(), en)
|
|
text.Reset()
|
|
}
|
|
}
|
|
value = true
|
|
case "apply":
|
|
if len(e) < 3 {
|
|
value = exception("'apply' expects two arguments: a procedure and an argument list, too few arguments were given")
|
|
break
|
|
}
|
|
args := eval(e[2], en)
|
|
// TODO make this work so that we dont have to eval things here
|
|
switch item := args.(type) {
|
|
case []expression:
|
|
value = eval(append([]expression{e[1]}, item...), en)
|
|
default:
|
|
value = eval([]expression{e[1], item}, en)
|
|
}
|
|
case "eval":
|
|
if len(e) < 1 {
|
|
value = exception("'eval' expects a string and an optional boolean to indicate that a string should be parsed and evaluated, but was not given any arguments")
|
|
}
|
|
sParse := false
|
|
if len(e) >= 3 {
|
|
v, ok := e[2].(bool)
|
|
if ok {
|
|
sParse = v
|
|
}
|
|
}
|
|
switch item := eval(e[1], en).(type) {
|
|
case []expression:
|
|
if _, ok := item[0].(symbol); !ok {
|
|
value = item
|
|
} else {
|
|
value = eval(item, en)
|
|
v, ok := value.(string)
|
|
if ok && sParse {
|
|
p := Parse(v)
|
|
value = eval(p.([]expression)[0], en)
|
|
}
|
|
}
|
|
case string:
|
|
if sParse {
|
|
p := Parse(item)
|
|
if l, ok := p.([]expression)[0].([]expression); ok {
|
|
if _, ok := l[0].(symbol); ok {
|
|
value = eval(p.([]expression)[0], en)
|
|
} else {
|
|
value = l
|
|
}
|
|
} else {
|
|
value = eval(l[0], en)
|
|
}
|
|
} else {
|
|
value = item
|
|
}
|
|
default:
|
|
value = item
|
|
}
|
|
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
|
|
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 {
|
|
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)
|
|
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 unescapeString(e)
|
|
case []expression:
|
|
values := make([]expression, len(e))
|
|
for i, x := range e {
|
|
values[i] = stringUnescapeEval(x)
|
|
}
|
|
return values
|
|
default:
|
|
return e
|
|
}
|
|
}
|