From 0f677d5b1ae3f812dd834b22e475f7d85eacd81c Mon Sep 17 00:00:00 2001 From: sloum Date: Tue, 9 May 2023 14:07:19 -0700 Subject: [PATCH] Moves special forms into their own file and fixes apply special form to work with nested lists properly --- interpreter.go | 356 ++-------------------------------------------- main.go | 2 +- specialForms.go | 365 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 374 insertions(+), 349 deletions(-) diff --git a/interpreter.go b/interpreter.go index c808e96..4dbc611 100644 --- a/interpreter.go +++ b/interpreter.go @@ -2,14 +2,7 @@ package main import ( "fmt" - "os" - "path/filepath" - "regexp" - "sort" "strings" - - "git.rawtext.club/sloum/slope/termios" - ln "github.com/peterh/liner" ) type proc struct { @@ -87,354 +80,21 @@ func eval(exp expression, en *env) (value expression) { case "begin0": value = specialBegin0(e, 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) + value = specialUsage(e) 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") + value = specialLoad(e, en) 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") + value = specialLoadMod(e, en) 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") + value = specialLoadModFile(e, en) 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 { - environ := en - symbolAsString := String(e[i+1], false) - if i := strings.Index(symbolAsString, "::"); i >= 0 { - modname := symbolAsString[:i] - alias, ok := altnamespaces[modname] - if !ok { - e, ok2 := namespaces[modname] - if !ok2 { - return false - } - environ = &e - } else { - e := namespaces[alias] - environ = &e - } - symbolAsString = symbolAsString[i+2:] - } - _, err = environ.Find(symbol(symbolAsString)) - } - if doPanic { - panicOnException = true - } - if err != nil { - value = false - break - } - } + value = specialExists(e, en) 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') - - var brokenString bool - match, raw, brokenString = stringParensMatch(text.String()) - if !match && !brokenString { - cont = true - } else { - cont = false - outputResult(text.String(), en) - text.Reset() - } - } - value = true + value = specialInspect(e, en) 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) - } + value = specialApply(e, 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(p.([]expression)[0], en) - } - } else { - value = item - } - default: - value = item - } + value = specialEval(e, en) default: operands := e[1:] values := make([]expression, len(operands)) diff --git a/main.go b/main.go index e23ebef..a0c30c0 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ import ( "golang.design/x/clipboard" ) -const version = "1.2.13" +const version = "1.2.14" const globalLibPath = "/usr/local/lib/slope/modules/" diff --git a/specialForms.go b/specialForms.go index 4f6fbeb..5c8fb06 100644 --- a/specialForms.go +++ b/specialForms.go @@ -1,8 +1,16 @@ package main import ( + "fmt" + "os" + "path/filepath" "reflect" + "regexp" + "sort" "strings" + + "git.rawtext.club/sloum/slope/termios" + ln "github.com/peterh/liner" ) func specialAnd(e []expression, en *env) expression { @@ -28,6 +36,20 @@ func specialAnd(e []expression, en *env) expression { return value } +func specialApply(e []expression, en *env) expression { + if len(e) < 3 { + return exception("'apply' expects two arguments: a procedure and an argument list, too few arguments were given") + } + args := eval(e[2], en) + // TODO make this work so that we dont have to eval things here + switch item := args.(type) { + case []expression: + return apply(eval(e[1], en), item, true) + default: + return apply(eval(e[1], en), []expression{item}, true) + } +} + func specialBegin0(e []expression, en *env) expression { var v expression for ii, i := range e[1:] { @@ -131,6 +153,97 @@ func specialDefine(e []expression, en *env) expression { return val } +func specialEval(e []expression, en *env) expression { + if len(e) < 1 { + return 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 { + return item + } else { + value := eval(item, en) + v, ok := value.(string) + if ok && sParse { + p := Parse(v) + return eval(p.([]expression)[0], en) + } + return value + } + case string: + if sParse { + p := Parse(item) + if l, ok := p.([]expression)[0].([]expression); ok { + if _, ok := l[0].(symbol); ok { + return eval(p.([]expression)[0], en) + } else { + return l + } + } else { + return eval(p.([]expression)[0], en) + } + } else { + return item + } + default: + return item + } +} + +func specialExists(e []expression, en *env) expression { + if len(e) == 0 { + return exception("'exists?' expects at least one symbol or string, no values were given") + } + 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 { + environ := en + symbolAsString := String(e[i+1], false) + if i := strings.Index(symbolAsString, "::"); i >= 0 { + modname := symbolAsString[:i] + alias, ok := altnamespaces[modname] + if !ok { + e, ok2 := namespaces[modname] + if !ok2 { + if doPanic { + panicOnException = true + } + return false + } + environ = &e + } else { + e := namespaces[alias] + environ = &e + } + symbolAsString = symbolAsString[i+2:] + } + _, err = environ.Find(symbol(symbolAsString)) + } + if doPanic { + panicOnException = true + } + if err != nil { + return false + } + } + return true +} + func specialFor(e []expression, en *env) expression { if len(e) < 3 { return exception("'for' requires at least two arguments: a list of initializers and a test") @@ -199,6 +312,75 @@ func specialIf(e []expression, en *env) expression { return make([]expression, 0) } +func specialInspect(e []expression, en *env) expression { + // Make sure we are attached to a tty + if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 { + return false + } + if fileInfo, _ := os.Stdin.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 { + return false + } + + 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') + + var brokenString bool + match, raw, brokenString = stringParensMatch(text.String()) + if !match && !brokenString { + cont = true + } else { + cont = false + outputResult(text.String(), en) + text.Reset() + } + } + return true +} + func specialLambda(e []expression, en *env) expression { if len(e) < 3 { return exception("'lambda' expects at least three arguments") @@ -232,6 +414,84 @@ func specialLambda(e []expression, en *env) expression { return proc{e[1], stringUnescapeEval(b), en, predicates} } +func specialLoad(e []expression, en *env) expression { + if en.outer != nil { + return exception("'load' is only callable from the global/top-level") + } + 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) + return true +} + +func specialLoadMod(e []expression, en *env) expression { + if len(e) < 2 { + return exception("'load-mod' expected a module name, no value was given") + } + 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 { + return exception(fmt.Sprintf("'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 + } + return true +} + +func specialLoadModFile(e []expression, en *env) expression { + 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 { + return exception(fmt.Sprintf("'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 + } + return true +} + func specialMacro(e []expression, en *env) expression { if len(e) < 3 { return exception("'macro' expects at least three arguments") @@ -295,3 +555,108 @@ func specialSet(e []expression, en *env) expression { return val } +func specialUsage(e []expression) expression { + 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) + return make([]expression, 0) + } else if len(e) == 2 { + proc, ok := e[1].(string) + if !ok { + p, ok2 := e[1].(symbol) + if !ok2 { + return exception("'usage' expected a string or symbol as its first argument, a non-string non-symbol value was given") + } + 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 { + return exception("'usage' encountered an error: " + err.Error()) + } + 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 { + return exception("'usage' was given an invalid module/symbol format") + } + ns := pair[0] + module := ns + altName, ok := altnamespaces[ns] + if ok { + module = altName + } + useMap, err := GetUsageMap(module) + if err != nil { + return exception("'usage' encountered an error: " + err.Error()) + } + subFunc, ok := useMap[pair[1]] + if !ok { + return exception("'usage' could not find the requested symbol within the " + ns + "module's usage data") + } + 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) + } + } + } + return make([]expression, 0) +}