felise/keywords.go

1499 lines
37 KiB
Go
Raw Normal View History

/*
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"
2023-06-01 22:16:17 +00:00
"os"
"regexp"
"strconv"
"strings"
)
2023-06-14 20:51:43 +00:00
/*
TODO Add the following?
- cd, mkdir
- Update `file-remove` to also work on directories? Or add separate `rm` that can optionally be recursive?
- map
2023-06-14 20:51:43 +00:00
*/
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)
2023-06-01 22:16:17 +00:00
case "=":
return libEqual(kw.line, kw.file)
2023-06-01 22:16:17 +00:00
case ">":
return libGreaterThan(kw.line, kw.file)
2023-06-01 22:16:17 +00:00
case "<":
return libLessThan(kw.line, kw.file)
2023-06-01 22:16:17 +00:00
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)
2023-06-05 15:33:11 +00:00
case "file-append":
2023-11-28 05:24:20 +00:00
return libFileAppend(kw.line, kw.file, true)
2023-06-05 15:33:11 +00:00
case "file-exists?":
return libFileExists(kw.line, kw.file)
2023-06-05 15:33:11 +00:00
case "file-read":
return libFileRead(kw.line, kw.file)
2023-06-05 15:33:11 +00:00
case "file-write":
2023-11-28 05:24:20 +00:00
return libFileAppend(kw.line, kw.file, false)
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)
2023-06-13 20:22:22 +00:00
case "import":
return libImport(kw.line, kw.file, en)
case "each!":
return libEach(kw.line, r, en, kw.file)
2023-06-14 21:03:05 +00:00
case "filter!":
return libFilter(kw.line, r, en, kw.file)
default:
return fmt.Errorf("Unknown keyword: `%s`", kw.line, kw.val.(string))
}
}
func libAdd(line int, fp string) error {
2023-06-01 22:16:17 +00:00
v2, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
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 if v1.kind == STRING {
err = globalStack.Push(token{STRING, v1.val.(string) + toString(v2, false), 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 {
2023-06-01 22:16:17 +00:00
v2, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
if v1.kind == STRING {
s := strings.Replace(v1.val.(string), toString(v2, false), "", -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 if v1.kind == LIST {
l := v1.val.(list).body
nl := make([]token, 0, len(l))
for i := range l {
if l[i].kind != v2.kind {
nl = append(nl, l[i])
} else if v2.kind == LIST {
l1 := l[i].val.(list)
l2 := v2.val.(list)
if l1.String() != l2.String() {
nl = append(nl, l[i])
}
} else if v2 != l[i] {
nl = append(nl, l[i])
}
}
err = globalStack.Push(token{LIST, list{nl}, 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 {
2023-06-01 22:16:17 +00:00
v2, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
if v1.kind == STRING && v2.kind == INT {
if v2.val.(int) < 0 {
err = globalStack.Push(token{STRING, "", line, fp})
} else {
s := strings.Repeat(v1.val.(string), v2.val.(int))
err = globalStack.Push(token{STRING, s, line, fp})
}
} else if v1.kind == STRING && v2.kind == FLOAT {
if v2.val.(float64) < 0.0 {
err = globalStack.Push(token{STRING, "", line, fp})
} else {
s := strings.Repeat(v1.val.(string), int(v2.val.(float64)))
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 {
2023-06-01 22:16:17 +00:00
v2, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
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 {
2023-06-01 22:16:17 +00:00
typeToken, err := globalStack.Pop()
if err != nil {
return err
}
2023-06-01 22:16:17 +00:00
if typeToken.kind != TYPE {
return fmt.Errorf("`var!` expects TYPE on the top of the stack")
}
2023-06-01 22:16:17 +00:00
newToken := token{}
newToken.kind = typeToken.val.(int)
newToken.line = line
newToken.file = fp
2023-06-01 22:16:17 +00:00
t, err := r.Read()
if err != nil {
return fmt.Errorf("Unexpectedly reached EOF")
}
2023-06-01 22:16:17 +00:00
if t.kind != SYMBOL {
return fmt.Errorf("A non-symbol value was given as an identifier to `var!`")
2023-06-01 22:16:17 +00:00
}
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
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
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 {
2023-06-01 22:16:17 +00:00
v2, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
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 {
2023-06-01 22:16:17 +00:00
v2, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
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 {
2023-06-01 22:16:17 +00:00
_, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
return nil
}
func libLogicalAnd(line int, fp string) error {
2023-06-01 22:16:17 +00:00
v2, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
if toBoolRaw(v1) && toBoolRaw(v2) {
err = globalStack.Push(token{BOOL, true, line, fp})
if err != nil {
return err
}
2023-06-01 22:16:17 +00:00
} else {
err = globalStack.Push(token{BOOL, false, line, fp})
if err != nil {
return err
}
2023-06-01 22:16:17 +00:00
}
return nil
}
func libLogicalOr(line int, fp string) error {
2023-06-01 22:16:17 +00:00
v2, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
if toBoolRaw(v1) || toBoolRaw(v2) {
err = globalStack.Push(token{BOOL, true, line, fp})
if err != nil {
return err
}
2023-06-01 22:16:17 +00:00
} else {
err = globalStack.Push(token{BOOL, false, line, fp})
if err != nil {
return err
}
2023-06-01 22:16:17 +00:00
}
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 {
2023-06-01 22:16:17 +00:00
v2, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
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})
2023-06-01 22:16:17 +00:00
}
if err != nil {
return err
}
2023-06-01 22:16:17 +00:00
return nil
}
func libGreaterThan(line int, fp string) error {
2023-06-01 22:16:17 +00:00
v2, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
if v1.kind == v2.kind {
switch v1.kind {
case STRING:
err = globalStack.Push(token{BOOL, v1.val.(string) > v2.val.(string), line, fp})
2023-06-01 22:16:17 +00:00
case INT:
err = globalStack.Push(token{BOOL, v1.val.(int) > v2.val.(int), line, fp})
2023-06-01 22:16:17 +00:00
case FLOAT:
err = globalStack.Push(token{BOOL, v1.val.(float64) > v2.val.(float64), line, fp})
2023-06-01 22:16:17 +00:00
default:
return fmt.Errorf("%s is not a comparable type", kindToString(v1.kind))
2023-06-01 22:16:17 +00:00
}
} else if (v1.kind == INT && v2.kind == FLOAT) {
err = globalStack.Push(token{BOOL, float64(v1.val.(int)) > v2.val.(float64), line, fp})
2023-06-01 22:16:17 +00:00
} else if (v1.kind == FLOAT && v2.kind == INT) {
err = globalStack.Push(token{BOOL, v1.val.(float64) > float64(v2.val.(int)), line, fp})
2023-06-01 22:16:17 +00:00
} else {
return fmt.Errorf("%s and %s cannot be compared", kindToString(v1.kind), kindToString(v2.kind))
2023-06-01 22:16:17 +00:00
}
if err != nil {
return err
}
2023-06-01 22:16:17 +00:00
return nil
}
func libLessThan(line int, fp string) error {
2023-06-01 22:16:17 +00:00
v2, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
v1, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
if v1.kind == v2.kind {
switch v1.kind {
case STRING:
err = globalStack.Push(token{BOOL, v1.val.(string) < v2.val.(string), line, fp})
2023-06-01 22:16:17 +00:00
case INT:
err = globalStack.Push(token{BOOL, v1.val.(int) < v2.val.(int), line, fp})
2023-06-01 22:16:17 +00:00
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})
2023-06-01 22:16:17 +00:00
default:
return fmt.Errorf("%s is not a comparable type", kindToString(v1.kind))
2023-06-01 22:16:17 +00:00
}
} else if (v1.kind == INT && v2.kind == FLOAT) {
err = globalStack.Push(token{BOOL, float64(v1.val.(int)) < v2.val.(float64), line, fp})
2023-06-01 22:16:17 +00:00
} else if (v1.kind == FLOAT && v2.kind == INT) {
err = globalStack.Push(token{BOOL, v1.val.(float64) < float64(v2.val.(int)), line, fp})
2023-06-01 22:16:17 +00:00
} else {
return fmt.Errorf("%s and %s cannot be compared", kindToString(v1.kind), kindToString(v2.kind))
2023-06-01 22:16:17 +00:00
}
if err != nil {
return err
}
2023-06-01 22:16:17 +00:00
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 {
2023-06-01 22:16:17 +00:00
typeToken, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
if typeToken.kind != TYPE {
return fmt.Errorf("`cast` expected a TYPE on top of stack")
}
2023-06-01 22:16:17 +00:00
fromToken, err := globalStack.Pop()
if err != nil {
return err
2023-06-01 22:16:17 +00:00
}
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 toKind == TYPE {
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
}
2023-06-05 15:33:11 +00:00
func libFileExists(line int, fp string) error {
2023-06-05 15:33:11 +00:00
p, err := globalStack.Pop()
if err != nil {
return err
2023-06-05 15:33:11 +00:00
}
if p.kind != STRING {
return fmt.Errorf("`file-exists?` expected a STRING path on TOS but got %s", kindToString(p.kind))
2023-06-05 15:33:11 +00:00
}
path := ExpandedAbsFilepath(p.val.(string))
_, err = os.Stat(path)
if err == nil {
err = globalStack.Push(token{BOOL, true, line, fp})
2023-06-05 15:33:11 +00:00
} else {
err = globalStack.Push(token{BOOL, false, line, fp})
2023-06-05 15:33:11 +00:00
}
if err != nil {
return err
2023-06-05 15:33:11 +00:00
}
return nil
}
func libFileRemove(line int, fp string) error {
2023-06-05 15:33:11 +00:00
p, err := globalStack.Pop()
if err != nil {
return err
2023-06-05 15:33:11 +00:00
}
if p.kind != STRING {
return fmt.Errorf("`file-remove` expected a STRING path on TOS but got %s", kindToString(p.kind))
2023-06-05 15:33:11 +00:00
}
path := ExpandedAbsFilepath(p.val.(string))
2023-11-28 23:29:05 +00:00
err = os.RemoveAll(path) // Will remove DIRs as well, be careful
2023-06-05 15:33:11 +00:00
if err != nil {
return fmt.Errorf("`file-remove` could not remove the file(s): %s", path)
2023-06-05 15:33:11 +00:00
}
return nil
}
2023-11-28 05:24:20 +00:00
func libFileAppend(line int, fp string, doAppend bool) error {
2023-06-05 15:33:11 +00:00
p, err := globalStack.Pop()
if err != nil {
return err
2023-06-05 15:33:11 +00:00
}
if p.kind != STRING {
return fmt.Errorf("`file-append` expected a STRING path on TOS but got %s", kindToString(p.kind))
2023-06-05 15:33:11 +00:00
}
data, err := globalStack.Pop()
if err != nil {
return err
2023-06-05 15:33:11 +00:00
}
path := ExpandedAbsFilepath(p.val.(string))
s := toString(data, false)
2023-11-28 05:24:20 +00:00
var caller string
var f *os.File
if doAppend {
f, err = os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
caller = "file-append"
} else {
f, err = os.OpenFile(path, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
caller = "file-write"
}
2023-06-05 15:33:11 +00:00
if err != nil {
2023-11-28 05:24:20 +00:00
return fmt.Errorf("`%s` could not open or create the path: %s", caller, path)
2023-06-05 15:33:11 +00:00
}
defer f.Close()
if err != nil {
2023-11-28 05:24:20 +00:00
return fmt.Errorf("`%s` opened but could not write to the path: %s", caller, path)
2023-06-05 15:33:11 +00:00
}
_, err = f.WriteString(s)
return nil
}
func libFileRead(line int, fp string) error {
2023-06-05 15:33:11 +00:00
p, err := globalStack.Pop()
if err != nil {
return err
2023-06-05 15:33:11 +00:00
}
if p.kind != STRING {
return fmt.Errorf("`file-read` expected a STRING path on TOS but got %s", kindToString(p.kind))
2023-06-05 15:33:11 +00:00
}
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)
2023-06-05 15:33:11 +00:00
}
err = globalStack.Push(token{STRING, string(b), line, fp})
2023-06-05 15:33:11 +00:00
if err != nil {
return err
2023-06-05 15:33:11 +00:00
}
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 < 0 {
t = len(s) + t + 1
}
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 < 0 {
t = len(l) + t + 1
}
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))
}
2023-06-13 20:22:22 +00:00
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
case "paths":
err = lexParseInterpret(pathsImport, en, "paths")
if err != nil {
return err
}
en.imports[t.val.(string)] = true
2023-11-28 23:29:05 +00:00
case "strings":
err = lexParseInterpret(stringsImport, en, "strings")
if err != nil {
return err
}
en.imports[t.val.(string)] = true
2023-06-13 20:22:22 +00:00
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
}
2023-06-14 21:03:05 +00:00
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
}