1470 lines
36 KiB
Go
1470 lines
36 KiB
Go
/*
|
|
Copyright (C) 2023 Brian Evans (aka sloum). All rights reserved.
|
|
|
|
This source code is available under the terms of the ffsl, or,
|
|
Floodgap Free Software License. A copy of the license has been
|
|
provided as the file 'LICENSE' in the same folder as this source
|
|
code file. If for some reason it is not present, you can find the
|
|
terms of version 1 of the FFSL at the following URL:
|
|
|
|
https://www.floodgap.com/software/ffsl/license.html
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
listRefSugar *regexp.Regexp = regexp.MustCompile(`^([^\s\[\]]+)\[([^\s\[\]]+)\]$`)
|
|
)
|
|
|
|
|
|
/*
|
|
TODO Add the following?
|
|
|
|
- path-abs, path-base, path-join, path-dir, path-glob
|
|
- cd, mkdir
|
|
- Update `file-remove` to also work on directories? Or add separate `rm` that can optionally be recursive?
|
|
- map
|
|
- filter! and each! are already there and because of how the stack works reduce can be achieved via each!
|
|
|
|
|
|
*/
|
|
|
|
|
|
func callKeyword(kw token, r *tokenReader, en *env) error {
|
|
switch kw.val.(string) {
|
|
case "+":
|
|
return libAdd(kw.line, kw.file)
|
|
case "-":
|
|
return libSubtract(kw.line, kw.file)
|
|
case "*":
|
|
return libMultiply(kw.line, kw.file)
|
|
case "/":
|
|
return libDivide(kw.line, kw.file)
|
|
case "var!":
|
|
return libVar(kw.line, r, en, kw.file)
|
|
case "scoped-var!":
|
|
return libVarPlus(kw.line, r, en, kw.file)
|
|
case "set!":
|
|
return libSet(kw.line, r, en, kw.file)
|
|
case "scoped-set!":
|
|
return libSetPlus(kw.line, r, en, kw.file)
|
|
case "dup":
|
|
return libDuplicate(kw.line, kw.file)
|
|
case "swap":
|
|
return libSwap(kw.line, kw.file)
|
|
case "over":
|
|
return libOver(kw.line, kw.file)
|
|
case "rot":
|
|
return libRot(kw.line, kw.file)
|
|
case "drop":
|
|
return libDrop(kw.line, kw.file)
|
|
case "cast":
|
|
return libConvertType(kw.line, kw.file)
|
|
case "and":
|
|
return libLogicalAnd(kw.line, kw.file)
|
|
case "or":
|
|
return libLogicalOr(kw.line, kw.file)
|
|
case "=":
|
|
return libEqual(kw.line, kw.file)
|
|
case ">":
|
|
return libGreaterThan(kw.line, kw.file)
|
|
case "<":
|
|
return libLessThan(kw.line, kw.file)
|
|
case "stackdump":
|
|
return libStackDump()
|
|
case "clearstack":
|
|
return libClearStack()
|
|
case "stackdepth":
|
|
return libStackDepth(kw.line, kw.file)
|
|
case "length":
|
|
return libLength(kw.line, kw.file)
|
|
case "append!", "=>!":
|
|
return libAppendSpecial(kw.line, r, en, kw.file)
|
|
case "append", "=>":
|
|
return libAppend(kw.line, kw.file)
|
|
case "list-get", "<-":
|
|
return libGetListItem(kw.line, r, en, kw.file)
|
|
case "list-set!", "->!":
|
|
return libSetListItemSpecial(kw.line, r, en, kw.file)
|
|
case "list-set", "->":
|
|
return libSetListItem(kw.line, kw.file)
|
|
case "file-append":
|
|
return libFileAppend(kw.line, kw.file)
|
|
case "file-exists?":
|
|
return libFileExists(kw.line, kw.file)
|
|
case "file-read":
|
|
return libFileRead(kw.line, kw.file)
|
|
case "file-write":
|
|
return libFileAppend(kw.line, kw.file)
|
|
case "docstring!":
|
|
return libDocstring(kw.line, r, en, kw.file)
|
|
case "input":
|
|
return libInput(kw.line, kw.file)
|
|
case "re-match?":
|
|
return libReMatch(kw.line, kw.file)
|
|
case "re-find":
|
|
return libReFind(kw.line, kw.file)
|
|
case "re-replace":
|
|
return libReReplace(kw.line, kw.file)
|
|
case "slice":
|
|
return libSlice(kw.line, kw.file)
|
|
case "type":
|
|
return libType(kw.line, kw.file)
|
|
case "net-get":
|
|
return libNetGet(kw.line, kw.file)
|
|
case "throw":
|
|
return libThrow(kw.line, kw.file)
|
|
case "import":
|
|
return libImport(kw.line, kw.file, en)
|
|
case "each!":
|
|
return libEach(kw.line, r, en, kw.file)
|
|
case "filter!":
|
|
return libFilter(kw.line, r, en, kw.file)
|
|
default:
|
|
return fmt.Errorf("Unknown keyword: `%s`", kw.line, kw.val.(string))
|
|
}
|
|
}
|
|
|
|
/*
|
|
TODO
|
|
- [X] split (implemented as `/` overload: `STRING STRING /` -> LIST)
|
|
- [X] join (implemented as `LIST STRING *` -> STRING)
|
|
- [X] `+` supports `STRING INT +` to add a rune to a string as a char, would bytes be better?
|
|
- [X] input (scans a line of text)
|
|
- [X] net-get (should support http/s, gopher, gemini)
|
|
- [X] regex-match?
|
|
- [X] regex-find
|
|
- [ ] regex-replace
|
|
- [ ] string-index-of
|
|
- [X] slice, takes two INT from stack detailing the range, the range is fully inclusive and overflows of avialable list are prevented
|
|
- [X] Use `+` for list join if two lists are passed (use append to add to a list)
|
|
- [X] type, for runtime type checking
|
|
*/
|
|
|
|
|
|
func libAdd(line int, fp string) error {
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v1.kind == STRING && v2.kind == STRING {
|
|
// String join
|
|
err = globalStack.Push(token{STRING, v1.val.(string) + v2.val.(string), line, fp})
|
|
} else if v1.kind == LIST && v2.kind == LIST {
|
|
// List join
|
|
l1 := v1.val.(list)
|
|
l2 := v2.val.(list)
|
|
nl := make([]token, len(l1.body)+len(l2.body))
|
|
for i, lt := range l1.body {
|
|
nl[i] = lt
|
|
}
|
|
for i, lt := range l2.body {
|
|
nl[i+len(l1.body)] = lt
|
|
}
|
|
err = globalStack.Push(token{LIST, list{nl}, line, fp})
|
|
} else if v1.kind == STRING && v2.kind == INT {
|
|
i := v2.val.(int)
|
|
if i < 0 {
|
|
return fmt.Errorf("Adding a rune to a string is only valid with positive integers, got %d", i)
|
|
}
|
|
s := v1.val.(string)
|
|
s += string(rune(i))
|
|
err = globalStack.Push(token{STRING, s, line, fp})
|
|
} else if v1.kind == INT && v2.kind == INT {
|
|
err = globalStack.Push(token{INT, v1.val.(int) + v2.val.(int), line, fp})
|
|
} else if v1.kind == FLOAT && v2.kind == FLOAT {
|
|
err = globalStack.Push(token{FLOAT, v1.val.(float64) + v2.val.(float64), line, fp})
|
|
} else if v1.kind == FLOAT && v2.kind == INT {
|
|
err = globalStack.Push(token{FLOAT, v1.val.(float64) + float64(v2.val.(int)), line, fp})
|
|
} else if v1.kind == INT && v2.kind == FLOAT {
|
|
err = globalStack.Push(token{FLOAT, v2.val.(float64) + float64(v1.val.(int)), line, fp})
|
|
} else {
|
|
fmt.Println("STACK-DUMP: ", globalStack)
|
|
return fmt.Errorf("Cannot `+` %s and %s", kindToString(v1.kind), kindToString(v2.kind))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libSubtract(line int, fp string) error {
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v1.kind == STRING && v2.kind == STRING {
|
|
s := strings.Replace(v1.val.(string), v2.val.(string), "", -1)
|
|
err = globalStack.Push(token{STRING, s, line, fp})
|
|
} else if v1.kind == INT && v2.kind == INT {
|
|
err = globalStack.Push(token{INT, v1.val.(int) - v2.val.(int), line, fp})
|
|
} else if v1.kind == FLOAT && v2.kind == FLOAT {
|
|
err = globalStack.Push(token{FLOAT, v1.val.(float64) - v2.val.(float64), line, fp})
|
|
} else if v1.kind == FLOAT && v2.kind == INT {
|
|
err = globalStack.Push(token{FLOAT, v1.val.(float64) - float64(v2.val.(int)), line, fp})
|
|
} else if v1.kind == INT && v2.kind == FLOAT {
|
|
err = globalStack.Push(token{FLOAT, float64(v1.val.(int)) - v2.val.(float64) , line, fp})
|
|
} else {
|
|
return fmt.Errorf("Cannot `-` %s and %s", kindToString(v1.kind), kindToString(v2.kind))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libMultiply(line int, fp string) error {
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v1.kind == STRING && v2.kind == INT {
|
|
s := strings.Repeat(v1.val.(string), v2.val.(int))
|
|
err = globalStack.Push(token{STRING, s, line, fp})
|
|
} else if v1.kind == LIST && v2.kind == STRING {
|
|
l := v1.val.(list)
|
|
s := l.Join(v2.val.(string))
|
|
err = globalStack.Push(token{STRING, s, line, fp})
|
|
} else if v1.kind == INT && v2.kind == INT {
|
|
err = globalStack.Push(token{INT, v1.val.(int) * v2.val.(int), line, fp})
|
|
} else if v1.kind == FLOAT && v2.kind == FLOAT {
|
|
err = globalStack.Push(token{FLOAT, v1.val.(float64) * v2.val.(float64), line, fp})
|
|
} else if v1.kind == FLOAT && v2.kind == INT {
|
|
err = globalStack.Push(token{FLOAT, v1.val.(float64) * float64(v2.val.(int)), line, fp})
|
|
} else if v1.kind == INT && v2.kind == FLOAT {
|
|
err = globalStack.Push(token{FLOAT, v2.val.(float64) * float64(v1.val.(int)), line, fp})
|
|
} else {
|
|
return fmt.Errorf("Cannot `*` %s and %s", kindToString(v1.kind), kindToString(v2.kind))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libDivide(line int, fp string) error {
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v1.kind == STRING && v2.kind == STRING {
|
|
l := strings.SplitN(v1.val.(string), v2.val.(string), -1)
|
|
ts := make([]token, len(l))
|
|
for i := range l {
|
|
ts[i] = token{STRING, l[i], line, fp}
|
|
}
|
|
err = globalStack.Push(token{LIST, list{ts}, line, fp})
|
|
} else if v1.kind == INT && v2.kind == INT {
|
|
if v2.val.(int) == 0 {
|
|
return fmt.Errorf("Division by zero")
|
|
}
|
|
err = globalStack.Push(token{INT, v1.val.(int) / v2.val.(int), line, fp})
|
|
} else if v1.kind == FLOAT && v2.kind == FLOAT {
|
|
if v2.val.(float64) == 0.0 {
|
|
return fmt.Errorf("Division by zero")
|
|
}
|
|
err = globalStack.Push(token{FLOAT, v1.val.(float64) / v2.val.(float64), line, fp})
|
|
} else if v1.kind == FLOAT && v2.kind == INT {
|
|
if v2.val.(int) == 0 {
|
|
return fmt.Errorf("Division by zero")
|
|
}
|
|
err = globalStack.Push(token{FLOAT, v1.val.(float64) / float64(v2.val.(int)), line, fp})
|
|
} else if v1.kind == INT && v2.kind == FLOAT {
|
|
if v2.val.(float64) == 0.0 {
|
|
return fmt.Errorf("Division by zero")
|
|
}
|
|
err = globalStack.Push(token{FLOAT, float64(v1.val.(int)) / v2.val.(float64) , line, fp})
|
|
} else {
|
|
return fmt.Errorf("Cannot `/` %s and %s", kindToString(v1.kind), kindToString(v2.kind))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libAppend(line int, fp string) error {
|
|
valueToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
targetToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch targetToken.kind {
|
|
case LIST:
|
|
l := targetToken.val.(list)
|
|
err = l.Append(valueToken)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(token{LIST, l, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case STRING:
|
|
s := targetToken.val.(string)
|
|
s = s + toString(valueToken, false)
|
|
err = globalStack.Push(token{STRING, s, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("Cannot `append` %s to %s", kindToString(valueToken.kind), kindToString(targetToken.kind))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libAppendSpecial(line int, r *tokenReader, en *env, fp string) error {
|
|
valueToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t, err := r.Read()
|
|
if err != nil {
|
|
return fmt.Errorf("Unexpectedly reached EOF")
|
|
}
|
|
|
|
var fromVariable bool
|
|
var targetToken token = t
|
|
var targetTokenenv *env
|
|
if t.kind == SYMBOL {
|
|
targetTokenenv, err = en.Find(t.val.(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
targetToken = targetTokenenv.vars[t.val.(string)]
|
|
fromVariable = true
|
|
}
|
|
|
|
switch targetToken.kind {
|
|
case LIST:
|
|
l := targetToken.val.(list)
|
|
err = l.Append(valueToken)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if fromVariable {
|
|
targetTokenenv.vars[t.val.(string)] = token{LIST, l, line, fp}
|
|
} else {
|
|
err := globalStack.Push(token{LIST, l, targetToken.line, targetToken.file})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case STRING:
|
|
s := targetToken.val.(string) + toString(valueToken, false)
|
|
if fromVariable {
|
|
targetTokenenv.vars[t.val.(string)] = token{STRING, s, targetToken.line, targetToken.file}
|
|
} else {
|
|
targetToken.val = s
|
|
err := globalStack.Push(token{STRING, s, targetToken.line, targetToken.file})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
default:
|
|
return fmt.Errorf("`=>` cannot append to %s", kindToString(targetToken.kind))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func libSetListItem(line int, fp string) error {
|
|
valueToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
indexToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if indexToken.kind != INT {
|
|
return fmt.Errorf("`list-set`/`->` expected an INT index after a value but found %s", kindToString(indexToken.kind))
|
|
}
|
|
|
|
listToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if listToken.kind != LIST {
|
|
return fmt.Errorf("`list-set`/`->` expected an LIST after an INT index and a value but found %s", kindToString(listToken.kind))
|
|
}
|
|
l := listToken.val.(list)
|
|
err = l.Set(indexToken.val.(int), valueToken)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(token{LIST, l, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libSetListItemSpecial(line int, r *tokenReader, en *env, fp string) error {
|
|
valueToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
indexToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if indexToken.kind != INT {
|
|
return fmt.Errorf("`list-set!`/`->!` expected an INT on TOS but got %s", kindToString(indexToken.kind))
|
|
}
|
|
|
|
t, err := r.Read()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if t.kind != SYMBOL {
|
|
return fmt.Errorf("`list-set!`/`->!` expected to read in a SYMBOL but found %s", kindToString(t.kind))
|
|
}
|
|
|
|
targetTokenEnv, err := en.Find(t.val.(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
targetToken := targetTokenEnv.vars[t.val.(string)]
|
|
if targetToken.kind != LIST {
|
|
return fmt.Errorf("`list-set!`/`->!` cannot set a value in type %s", kindToString(targetToken.kind))
|
|
}
|
|
|
|
l := targetToken.val.(list)
|
|
err = l.Set(indexToken.val.(int), valueToken)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libGetListItem(line int, r *tokenReader, en *env, fp string) error {
|
|
indexToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if indexToken.kind != INT {
|
|
return fmt.Errorf("`<-`/`list-get` expected an INT underneath a LIST on the stack, LIST found but found %s instead of INT", kindToString(indexToken.kind))
|
|
}
|
|
listToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if listToken.kind != LIST {
|
|
return fmt.Errorf("`<-`/`list-get` expected a LIST on TOS, but found %s", kindToString(listToken.kind))
|
|
}
|
|
l := listToken.val.(list)
|
|
val, err := l.Get(indexToken.val.(int))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libVar(line int, r *tokenReader, en *env, fp string) error {
|
|
typeToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if typeToken.kind != TYPE {
|
|
return fmt.Errorf("`var!` expects TYPE on the top of the stack")
|
|
}
|
|
newToken := token{}
|
|
newToken.kind = typeToken.val.(int)
|
|
newToken.line = line
|
|
newToken.file = fp
|
|
|
|
t, err := r.Read()
|
|
if err != nil {
|
|
return fmt.Errorf("Unexpectedly reached EOF")
|
|
}
|
|
if t.kind != SYMBOL {
|
|
return fmt.Errorf("A non-symbol value was given as an identifier to `var!`")
|
|
}
|
|
newToken.val = typeInit(newToken.kind)
|
|
en.Add(t.val.(string), newToken)
|
|
return nil
|
|
}
|
|
|
|
|
|
func libVarPlus(line int, r *tokenReader, en *env, fp string) error {
|
|
scope, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if scope.kind != INT {
|
|
return fmt.Errorf("`scoped-var!` expected an INT declaring how many scopes to jump up, found %s", kindToString(scope.kind))
|
|
}
|
|
for i := scope.val.(int); i > 0; i-- {
|
|
if en.parent != nil {
|
|
en = en.parent
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
typeToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if typeToken.kind != TYPE {
|
|
return fmt.Errorf("`scoped-var!` expects a TYPE literal on the the stack after an INT, found %s", kindToString(typeToken.kind))
|
|
}
|
|
newToken := token{}
|
|
newToken.kind = typeToken.val.(int)
|
|
newToken.line = line
|
|
newToken.file = fp
|
|
|
|
t, err := r.Read()
|
|
if err != nil {
|
|
return fmt.Errorf("Unexpectedly reached EOF")
|
|
}
|
|
if t.kind != SYMBOL {
|
|
return fmt.Errorf("A non-symbol value was given as an identifier to `scoped-var!`")
|
|
}
|
|
newToken.val = typeInit(newToken.kind)
|
|
en.Add(t.val.(string), newToken)
|
|
return nil
|
|
}
|
|
|
|
func libSet(line int, r *tokenReader, en *env, f string) error {
|
|
t, err := r.Read()
|
|
if err != nil {
|
|
return fmt.Errorf("Unexpectedly reached EOF")
|
|
}
|
|
if t.kind != SYMBOL {
|
|
return fmt.Errorf("A non-symbol value was given as an identifier to `set!`")
|
|
}
|
|
e, err := en.Find(t.val.(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
current := e.vars[t.val.(string)]
|
|
if v1.kind != current.kind {
|
|
return fmt.Errorf("Cannot `set!` %s to %s", kindToString(v1.kind), kindToString(current.kind))
|
|
}
|
|
e.vars[t.val.(string)] = v1
|
|
return nil
|
|
}
|
|
|
|
// ANY INT scoped-set! SYMBOL
|
|
// INT represents the number of scopes to jump up
|
|
func libSetPlus(line int, r *tokenReader, en *env, fp string) error {
|
|
t, err := r.Read()
|
|
if err != nil {
|
|
return fmt.Errorf("Unexpectedly reached EOF")
|
|
}
|
|
if t.kind != SYMBOL {
|
|
return fmt.Errorf("A non-symbol value was given as an identifier to `scoped-set!`")
|
|
}
|
|
scope, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if scope.kind != INT {
|
|
return fmt.Errorf("`scoped-set!` expected an INT declaring how many scopes to jump up")
|
|
}
|
|
for i := scope.val.(int); i > 0; i-- {
|
|
if en.parent != nil {
|
|
en = en.parent
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
e, err := en.Find(t.val.(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
current := e.vars[t.val.(string)]
|
|
if v1.kind != current.kind {
|
|
return fmt.Errorf("Cannot assign %s to %s var", kindToString(v1.kind), kindToString(current.kind))
|
|
}
|
|
e.vars[t.val.(string)] = v1
|
|
return nil
|
|
}
|
|
|
|
func libDuplicate(line int, fp string) error {
|
|
err := globalStack.Push(globalStack.Peek())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libSwap(line int, fp string) error {
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(v2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(v1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libOver(line int, fp string) error {
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(v1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(v2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(v1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libRot(line int, fp string) error {
|
|
v3, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(v2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(v3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(v1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libDrop(line int, fp string) error {
|
|
_, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libLogicalAnd(line int, fp string) error {
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if toBoolRaw(v1) && toBoolRaw(v2) {
|
|
err = globalStack.Push(token{BOOL, true, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
err = globalStack.Push(token{BOOL, false, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libLogicalOr(line int, fp string) error {
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if toBoolRaw(v1) || toBoolRaw(v2) {
|
|
err = globalStack.Push(token{BOOL, true, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
err = globalStack.Push(token{BOOL, false, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// `=` works at a value level and does not refer to
|
|
// the equality of memory adresses. As such, two lists
|
|
// with the same contents are equal even if they do
|
|
// not occupy the same memory/refer to the same object
|
|
func libEqual(line int, fp string) error {
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v1.kind != v2.kind {
|
|
err = globalStack.Push(token{BOOL, false, line, fp})
|
|
} else {
|
|
var equal bool
|
|
switch v1.kind {
|
|
case BOOL:
|
|
equal = v1.val.(bool) == v2.val.(bool)
|
|
case INT, TYPE:
|
|
equal = v1.val.(int) == v2.val.(int)
|
|
case STRING:
|
|
equal = v1.val.(string) == v2.val.(string)
|
|
case FLOAT:
|
|
equal = v1.val.(float64) == v2.val.(float64)
|
|
case LIST:
|
|
l1 := v1.val.(list)
|
|
l2 := v2.val.(list)
|
|
equal = (l1.String() == l2.String())
|
|
}
|
|
err = globalStack.Push(token{BOOL, equal, line, fp})
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libGreaterThan(line int, fp string) error {
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v1.kind == v2.kind {
|
|
switch v1.kind {
|
|
case STRING:
|
|
err = globalStack.Push(token{BOOL, v1.val.(string) > v2.val.(string), line, fp})
|
|
case INT:
|
|
err = globalStack.Push(token{BOOL, v1.val.(int) > v2.val.(int), line, fp})
|
|
case FLOAT:
|
|
err = globalStack.Push(token{BOOL, v1.val.(float64) > v2.val.(float64), line, fp})
|
|
default:
|
|
return fmt.Errorf("%s is not a comparable type", kindToString(v1.kind))
|
|
}
|
|
} else if (v1.kind == INT && v2.kind == FLOAT) {
|
|
err = globalStack.Push(token{BOOL, float64(v1.val.(int)) > v2.val.(float64), line, fp})
|
|
} else if (v1.kind == FLOAT && v2.kind == INT) {
|
|
err = globalStack.Push(token{BOOL, v1.val.(float64) > float64(v2.val.(int)), line, fp})
|
|
} else {
|
|
return fmt.Errorf("%s and %s cannot be compared", kindToString(v1.kind), kindToString(v2.kind))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libLessThan(line int, fp string) error {
|
|
v2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if v1.kind == v2.kind {
|
|
switch v1.kind {
|
|
case STRING:
|
|
err = globalStack.Push(token{BOOL, v1.val.(string) < v2.val.(string), line, fp})
|
|
case INT:
|
|
err = globalStack.Push(token{BOOL, v1.val.(int) < v2.val.(int), line, fp})
|
|
case FLOAT:
|
|
err = globalStack.Push(token{BOOL, v1.val.(float64) < v2.val.(float64), line, fp})
|
|
case BOOL:
|
|
err = globalStack.Push(token{BOOL, false, line, fp})
|
|
default:
|
|
return fmt.Errorf("%s is not a comparable type", kindToString(v1.kind))
|
|
}
|
|
} else if (v1.kind == INT && v2.kind == FLOAT) {
|
|
err = globalStack.Push(token{BOOL, float64(v1.val.(int)) < v2.val.(float64), line, fp})
|
|
} else if (v1.kind == FLOAT && v2.kind == INT) {
|
|
err = globalStack.Push(token{BOOL, v1.val.(float64) < float64(v2.val.(int)), line, fp})
|
|
} else {
|
|
return fmt.Errorf("%s and %s cannot be compared", kindToString(v1.kind), kindToString(v2.kind))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libStackDump() error {
|
|
fmt.Fprintf(os.Stderr, "\n%s\n", globalStack.String())
|
|
return nil
|
|
}
|
|
|
|
func libClearStack() error {
|
|
globalStack.sp = 0
|
|
return nil
|
|
}
|
|
|
|
func libLength(line int, fp string) error {
|
|
t, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch t.kind {
|
|
case LIST:
|
|
l := t.val.(list)
|
|
err = globalStack.Push(token{INT, len(l.body), line, fp})
|
|
case STRING:
|
|
s := t.val.(string)
|
|
err = globalStack.Push(token{INT, len(s), line, fp})
|
|
default:
|
|
err = fmt.Errorf("%s does not have a length", kindToString(t.kind))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func libConvertType(line int, fp string) error {
|
|
typeToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if typeToken.kind != TYPE {
|
|
return fmt.Errorf("`cast` expected a TYPE on top of stack")
|
|
}
|
|
fromToken, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
toKind, fromKind := typeToken.val.(int), fromToken.kind
|
|
|
|
if fromKind == toKind {
|
|
err = globalStack.Push(fromToken)
|
|
} else if fromKind == INT && toKind == FLOAT {
|
|
err = globalStack.Push(token{FLOAT, float64(fromToken.val.(int)), line, fp})
|
|
|
|
} else if fromKind == FLOAT && toKind == INT {
|
|
err = globalStack.Push(token{INT, int(math.Floor(fromToken.val.(float64))), line, fp})
|
|
|
|
} else if toKind == STRING {
|
|
err = globalStack.Push(token{STRING, toString(fromToken, false), line, fp})
|
|
|
|
} else if fromKind == STRING && toKind == INT {
|
|
i, err := tokenStringToInt(fromToken.val.(string))
|
|
if err != nil {
|
|
return fmt.Errorf("%s (STRING: %q)", err.Error(), fromToken.val.(string))
|
|
}
|
|
err = globalStack.Push(token{INT, i, line, fp})
|
|
} else if fromKind == STRING && toKind == FLOAT {
|
|
f, err := strconv.ParseFloat(fromToken.val.(string), 64)
|
|
if err != nil {
|
|
return fmt.Errorf("Could not convert STRING to FLOAT (STRING: %q)", fromToken.val.(string))
|
|
}
|
|
err = globalStack.Push(token{FLOAT, f, line, fp})
|
|
|
|
} else if toKind == BOOL {
|
|
err = globalStack.Push(toBool(fromToken))
|
|
|
|
} else if fromKind == TYPE && toKind == INT {
|
|
err = globalStack.Push(token{INT, fromToken.val.(int), line, fp})
|
|
|
|
} else if toKind == TYPE {
|
|
// TODO This is wrong
|
|
// converting anything to type should take on the type of the fromKind
|
|
err = globalStack.Push(token{TYPE, fromToken.kind, line, fp})
|
|
} else {
|
|
return fmt.Errorf("Could not convert %s to %s", kindToString(fromKind),kindToString(toKind))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libFileExists(line int, fp string) error {
|
|
p, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if p.kind != STRING {
|
|
return fmt.Errorf("`file-exists?` expected a STRING path on TOS but got %s", kindToString(p.kind))
|
|
}
|
|
path := ExpandedAbsFilepath(p.val.(string))
|
|
_, err = os.Stat(path)
|
|
if err == nil {
|
|
err = globalStack.Push(token{BOOL, true, line, fp})
|
|
} else {
|
|
err = globalStack.Push(token{BOOL, false, line, fp})
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libFileRemove(line int, fp string) error {
|
|
p, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if p.kind != STRING {
|
|
return fmt.Errorf("`file-remove` expected a STRING path on TOS but got %s", kindToString(p.kind))
|
|
}
|
|
path := ExpandedAbsFilepath(p.val.(string))
|
|
err = os.RemoveAll(path)
|
|
if err != nil {
|
|
return fmt.Errorf("`file-remove` could not remove the file(s): %s", path)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libFileAppend(line int, fp string) error {
|
|
p, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if p.kind != STRING {
|
|
return fmt.Errorf("`file-append` expected a STRING path on TOS but got %s", kindToString(p.kind))
|
|
}
|
|
data, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := ExpandedAbsFilepath(p.val.(string))
|
|
s := toString(data, false)
|
|
|
|
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("`file-append` could not open or create the path: %s", path)
|
|
}
|
|
defer f.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("`file-append` opened but could not write to the path: %s", path)
|
|
}
|
|
_, err = f.WriteString(s)
|
|
return nil
|
|
}
|
|
|
|
func libFileRead(line int, fp string) error {
|
|
p, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if p.kind != STRING {
|
|
return fmt.Errorf("`file-read` expected a STRING path on TOS but got %s", kindToString(p.kind))
|
|
}
|
|
path := ExpandedAbsFilepath(p.val.(string))
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return fmt.Errorf("`file-read` could not read from the file at path: %s", path)
|
|
}
|
|
err = globalStack.Push(token{STRING, string(b), line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libDocstring(line int, r *tokenReader, en *env, fp string) error {
|
|
t, err := r.Read()
|
|
if err != nil {
|
|
return fmt.Errorf("Unexpectedly reached EOF")
|
|
}
|
|
// There is a situation here where the parser does not expect
|
|
// to see a structure (proc, while, if) here and may mangle
|
|
// parsing, since there will be no close to the declaration.
|
|
// Make the parser smart enough to handle this case.
|
|
if t.kind == KEYWORD || t.kind == PROC || t.kind == IF || t.kind == WHILE {
|
|
var s string
|
|
switch t.kind {
|
|
case IF:
|
|
s = "if"
|
|
case WHILE:
|
|
s = "while"
|
|
case PROC:
|
|
if t.val.(proc).hasArg {
|
|
s = "proc!"
|
|
} else {
|
|
s = "proc"
|
|
}
|
|
default:
|
|
s = t.val.(string)
|
|
}
|
|
v, ok := kwDocstrings[s]
|
|
if ok {
|
|
err = globalStack.Push(token{STRING, fmt.Sprintf("`%s`\n%s", s, v), line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
} else if t.kind != SYMBOL && t.kind != KEYWORD {
|
|
return fmt.Errorf("`docstring!` expected to read in a SYMBOL or KEYWORD but found %s", kindToString(t.kind))
|
|
}
|
|
|
|
targetTokenEnv, err := en.Find(t.val.(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
targetToken := targetTokenEnv.vars[t.val.(string)]
|
|
if targetToken.kind != PROC {
|
|
return fmt.Errorf("`docstring!` cannot read a docstring from %s", kindToString(targetToken.kind))
|
|
}
|
|
p := targetToken.val.(proc)
|
|
s := fmt.Sprintf("`%s`\n%s", t.val.(string), p.doc)
|
|
err = globalStack.Push(token{STRING, s, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libInput(line int, fp string) error {
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
scanner.Scan()
|
|
text := scanner.Text()
|
|
err := globalStack.Push(token{STRING, text, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
|
|
func libReMatch(line int, fp string) error {
|
|
t2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !(t1.kind == STRING && t2.kind == STRING) {
|
|
return fmt.Errorf("`re-match?` expected two strings from the stack but found %s and %s", kindToString(t1.kind), kindToString(t2.kind))
|
|
}
|
|
re := regexp.MustCompile(t1.val.(string))
|
|
err = globalStack.Push(token{BOOL, re.MatchString(t2.val.(string)), line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libReFind(line int, fp string) error {
|
|
t2, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t1, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !(t1.kind == STRING && t2.kind == STRING) {
|
|
return fmt.Errorf("`re-find` expected two strings from the stack but found %s and %s", kindToString(t1.kind), kindToString(t2.kind))
|
|
}
|
|
re := regexp.MustCompile(t1.val.(string))
|
|
matches := re.FindStringSubmatch(t2.val.(string))
|
|
tokens := make([]token, len(matches))
|
|
for i, s := range matches {
|
|
tokens[i] = token{STRING, s, line, fp}
|
|
}
|
|
err = globalStack.Push(token{LIST, list{tokens}, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libReReplace(line int, fp string) error {
|
|
replacement, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
input, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pattern, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
re := regexp.MustCompile(toString(pattern, false))
|
|
s := re.ReplaceAllString(toString(input, false), toString(replacement, false))
|
|
err = globalStack.Push(token{STRING, s, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func libSlice(line int, fp string) error {
|
|
to, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
from, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if to.kind != INT || from.kind != INT {
|
|
return fmt.Errorf("`slice` expected two integers from the stack but found %s and %s", kindToString(from.kind), kindToString(to.kind))
|
|
}
|
|
data, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t := to.val.(int)
|
|
f := from.val.(int)-1
|
|
switch data.kind {
|
|
case STRING:
|
|
s := data.val.(string)
|
|
if t > len(s) {
|
|
t = len(s)
|
|
}
|
|
if f < 0 {
|
|
f = 0
|
|
}
|
|
if f > t {
|
|
f = t
|
|
}
|
|
err = globalStack.Push(token{STRING, s[f:t], line, fp})
|
|
case LIST:
|
|
li := data.val.(list)
|
|
l := li.body
|
|
if t > len(l) {
|
|
t = len(l)
|
|
}
|
|
if f < 0 {
|
|
f = 0
|
|
}
|
|
if f > t {
|
|
f = t
|
|
}
|
|
err = globalStack.Push(token{LIST, list{l[f:t]}, line, fp})
|
|
default:
|
|
return fmt.Errorf("Cannot `slice` %s\n", line, kindToString(data.kind))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libStackDepth(line int, fp string) error {
|
|
err := globalStack.Push(token{INT, globalStack.Depth(), line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libType(line int, fp string) error {
|
|
val, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = globalStack.Push(token{TYPE, val.kind, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libNetGet(line int, fp string) error {
|
|
val, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
respList := list{[]token{
|
|
token{BOOL, false, line, fp},
|
|
token{INT, -1, line, fp},
|
|
token{STRING, "The given string could not be parsed as a url", line, fp},
|
|
}}
|
|
u, err := url.Parse(toString(val, false))
|
|
if err != nil {
|
|
err = globalStack.Push(token{LIST, respList, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
switch u.Scheme {
|
|
case "http", "https":
|
|
resp, err := http.Get(u.String())
|
|
if err != nil {
|
|
respList.body[2] = token{STRING, err.Error(), line, fp}
|
|
break
|
|
}
|
|
defer resp.Body.Close()
|
|
success := true
|
|
status := resp.StatusCode
|
|
if status < 200 || status > 299 {
|
|
success = false
|
|
}
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
respList.body[1] = token{INT, status, line, fp}
|
|
respList.body[2] = token{STRING, "Could not read response body, or reading was interuppted", line, fp}
|
|
break
|
|
}
|
|
respList.body[0] = token{BOOL, success, line, fp}
|
|
respList.body[1] = token{INT, status, line, fp}
|
|
respList.body[2] = token{STRING, string(body), line, fp}
|
|
case "gemini":
|
|
status, resp := GeminiRequest(u, 0)
|
|
if status == 1 || status == 2 {
|
|
respList.body[0] = token{BOOL, true, line, fp}
|
|
} else {
|
|
respList.body[0] = token{BOOL, false, line, fp}
|
|
}
|
|
respList.body[1] = token{INT, status, line, fp}
|
|
respList.body[2] = token{STRING, resp, line, fp}
|
|
case "gopher":
|
|
if u.Port() == "" {
|
|
u.Host = u.Host + ":70"
|
|
}
|
|
conn, err := net.Dial("tcp", u.Host)
|
|
if err != nil {
|
|
respList.body[2] = token{STRING, err.Error(), line, fp}
|
|
break
|
|
}
|
|
defer conn.Close()
|
|
p := u.Path
|
|
if len(u.Path) < 2 {
|
|
p = "/" + "\n"
|
|
} else {
|
|
p = p[2:] + "\n"
|
|
}
|
|
_, err = conn.Write([]byte(p))
|
|
if err != nil {
|
|
respList.body[2] = token{STRING, err.Error(), line, fp}
|
|
break
|
|
}
|
|
resp, err := io.ReadAll(conn)
|
|
if err != nil {
|
|
respList.body[2] = token{STRING, err.Error(), line, fp}
|
|
break
|
|
}
|
|
respList.body[0] = token{BOOL, true, line, fp}
|
|
respList.body[2] = token{STRING, string(resp), line, fp}
|
|
default:
|
|
return fmt.Errorf("Unsupported URL scheme: %s\n", line, u.Scheme)
|
|
}
|
|
err = globalStack.Push(token{LIST, respList, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libThrow(line int, fp string) error {
|
|
e, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return fmt.Errorf("%s", toString(e, false))
|
|
}
|
|
|
|
func libImport(line int, fp string, en *env) error {
|
|
t, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if t.kind != STRING {
|
|
return fmt.Errorf("`import` expects a STRING on TOS, not %s", kindToString(t.kind))
|
|
}
|
|
if en.Imported(t.val.(string)) {
|
|
return nil
|
|
}
|
|
switch t.val.(string) {
|
|
case "std":
|
|
err = lexParseInterpret(stdImport, en, "std")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
en.imports[t.val.(string)] = true
|
|
case "math":
|
|
err = lexParseInterpret(mathImport, en, "math")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
en.imports[t.val.(string)] = true
|
|
case "stack":
|
|
err = lexParseInterpret(stackImport, en, "stack")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
en.imports[t.val.(string)] = true
|
|
default:
|
|
p := ExpandedAbsFilepath(t.val.(string))
|
|
s, err := readFile(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = lexParseInterpret(s, en, p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
en.imports[t.val.(string)] = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libEach(line int, r *tokenReader, en *env, fp string) error {
|
|
li, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if li.kind != LIST {
|
|
return fmt.Errorf("'each!' expected a LIST on TOS but found %s", kindToString(li.kind))
|
|
}
|
|
l := li.val.(list).body
|
|
|
|
t, err := r.Read()
|
|
if err != nil {
|
|
return fmt.Errorf("Unexpectedly reached EOF")
|
|
}
|
|
if t.kind == SYMBOL {
|
|
e, err := en.Find(t.val.(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
val := e.vars[t.val.(string)]
|
|
if val.kind != PROC {
|
|
return fmt.Errorf("Cannot apply %s to list item in `each!`, expected PROC or KEYWORD", kindToString(val.kind))
|
|
}
|
|
} else if t.kind != KEYWORD {
|
|
return fmt.Errorf("A non-SYMBOL/non-KEYWORD value was given as an identifier to `each!`")
|
|
}
|
|
for i := range l {
|
|
err = interpret([]token{l[i], t}, en)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func libFilter(line int, r *tokenReader, en *env, fp string) error {
|
|
li, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if li.kind != LIST {
|
|
return fmt.Errorf("'filter!' expected a LIST on TOS but found %s", kindToString(li.kind))
|
|
}
|
|
l := li.val.(list).body
|
|
|
|
t, err := r.Read()
|
|
if err != nil {
|
|
return fmt.Errorf("Unexpectedly reached EOF")
|
|
}
|
|
if t.kind == SYMBOL {
|
|
e, err := en.Find(t.val.(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
val := e.vars[t.val.(string)]
|
|
if val.kind != PROC {
|
|
return fmt.Errorf("Cannot apply %s to list item in `filter!`, expected PROC or KEYWORD", kindToString(val.kind))
|
|
}
|
|
} else if t.kind != KEYWORD {
|
|
return fmt.Errorf("A non-SYMBOL/non-KEYWORD value was given as an identifier to `filter!`")
|
|
}
|
|
newList := make([]token, 0, len(l))
|
|
for i := range l {
|
|
err = interpret([]token{l[i], t}, en)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b, err := globalStack.Pop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if toBoolRaw(b) {
|
|
newList = append(newList, l[i])
|
|
}
|
|
}
|
|
err = globalStack.Push(token{LIST, list{newList}, line, fp})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|