3542 lines
101 KiB
Go
3542 lines
101 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/md5"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"math"
|
|
"math/rand"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"git.rawtext.club/sloum/slope/termios"
|
|
)
|
|
|
|
var Sysout *IOHandle = &IOHandle{os.Stdout, true, "file (write-only)"}
|
|
|
|
var stdLibrary = vars{
|
|
"sysout": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return Sysout
|
|
}
|
|
switch io := a[0].(type) {
|
|
case *IOHandle:
|
|
if !io.Open {
|
|
return exception("'sysout' expected an open io-handle, but a closed io-handle was given")
|
|
}
|
|
Sysout = io
|
|
return make([]expression, 0)
|
|
default:
|
|
return exception("'sysout' expected an open io-handle, or nothing, but a non-io-handle was given")
|
|
}
|
|
},
|
|
"stdin": &IOHandle{os.Stdin, true, "file (read-only)"},
|
|
"stdout": &IOHandle{os.Stdout, true, "file (write-only)"},
|
|
"stderr": &IOHandle{os.Stderr, true, "file (write-only)"},
|
|
"devnull": func() *IOHandle {
|
|
devnull, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0755)
|
|
dn := &IOHandle{devnull, true, "file (write-only)"}
|
|
openFiles = append(openFiles, dn)
|
|
return dn
|
|
}(),
|
|
"mod-path": func(a ...expression) expression {
|
|
return getModBaseDir()
|
|
},
|
|
"__SIGINT": func(a ...expression) expression {
|
|
SafeExit(1)
|
|
return false
|
|
},
|
|
"signal-catch-sigint": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("'signal-catch-sigint' expected a procedure but no value was given")
|
|
}
|
|
switch p := a[0].(type) {
|
|
case proc, func(...expression) expression:
|
|
globalenv.vars["__SIGINT"] = p
|
|
return true
|
|
default:
|
|
return exception("'signal-catch-sigint' expected a procedure, but a non-procedure value was given")
|
|
}
|
|
},
|
|
"PHI": number(math.Phi),
|
|
"PI": number(math.Pi),
|
|
"E": number(math.E),
|
|
"sys-args": func(a ...expression) expression {
|
|
args := os.Args
|
|
if len(args) > 0 && args[0] == "slope" || args[0] == "./slope" || args[0] == "../slope" {
|
|
args = args[1:]
|
|
}
|
|
return StringSliceToExpressionSlice(args)
|
|
}(),
|
|
"exception-mode-panic": func(a ...expression) expression {
|
|
panicOnException = true
|
|
return make([]expression, 0)
|
|
},
|
|
"exception-mode-pass": func(a ...expression) expression {
|
|
panicOnException = false
|
|
return make([]expression, 0)
|
|
},
|
|
"exception-mode-pass?": func(a ...expression) expression {
|
|
return panicOnException == false
|
|
},
|
|
"exception-mode-panic?": func(a ...expression) expression {
|
|
return panicOnException == true
|
|
},
|
|
"slope-version": func(a ...expression) expression {
|
|
out := make([]expression, 0, 4)
|
|
s := strings.SplitN(version, ".", -1)
|
|
for i := range s {
|
|
integer, err := strconv.ParseInt(s[i], 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
out = append(out, number(integer))
|
|
}
|
|
var maj, min, patch number
|
|
var ok bool
|
|
if len(a) == 0 {
|
|
return out
|
|
}
|
|
maj, ok = a[0].(number)
|
|
if !ok {
|
|
return exception("'slope-version' expected its first argument to be a number representing a major version, an invalid value was given")
|
|
}
|
|
if len(a) > 1 {
|
|
min, ok = a[1].(number)
|
|
if !ok {
|
|
return exception("'slope-version' expected its second argument to be a number representing a minor version, an invalid value was given")
|
|
}
|
|
}
|
|
|
|
if len(a) > 2 {
|
|
patch, ok = a[2].(number)
|
|
if !ok {
|
|
return exception("'slope-version' expected its third argument to be a number representing a patch version, an invalid value was given")
|
|
}
|
|
}
|
|
if out[0].(number) > maj || (out[0].(number) == maj && out[1].(number) > min) || (out[0].(number) == maj && out[1].(number) == min && out[2].(number) >= patch) {
|
|
return true
|
|
}
|
|
return exception(fmt.Sprintf("This program requires a slope interpreter version >= %d.%d.%d\nThe currently installed version is %s\nPlease update your slope interpreter to run this program", int(maj), int(min), int(patch), version))
|
|
},
|
|
"!": func(a ...expression) expression {
|
|
if len(a) < 0 {
|
|
return exception("'!' expects a value, no value was given")
|
|
}
|
|
if panicOnException {
|
|
panic(a[0])
|
|
}
|
|
return exception(String(a[0], false))
|
|
},
|
|
"positive?": func(a ...expression) expression {
|
|
if len(a) > 0 {
|
|
if n, ok := a[0].(number); ok {
|
|
return n > 0
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
"negative?": func(a ...expression) expression {
|
|
if len(a) > 0 {
|
|
if n, ok := a[0].(number); ok {
|
|
return n < 0
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
"zero?": func(a ...expression) expression {
|
|
if len(a) > 0 {
|
|
if n, ok := a[0].(number); ok {
|
|
return n == 0
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
"exception?": func(a ...expression) expression {
|
|
if len(a) > 0 {
|
|
if _, ok := a[0].(exception); ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
"abs": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("'abs' expected a value, but no value was given")
|
|
}
|
|
i, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'abs' expected a number, but a non-number value was received")
|
|
}
|
|
if i < 0 {
|
|
i = i * -1
|
|
}
|
|
return i
|
|
},
|
|
"floor": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("'floor' expected a value, but no value was given")
|
|
}
|
|
i, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'floor' expected a number, but a non-number value was received")
|
|
}
|
|
return number(math.Floor(float64(i)))
|
|
},
|
|
"ceil": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("'ceil' expected a value, but no value was given")
|
|
}
|
|
i, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'ceil' expected a number, but a non-number value was received")
|
|
}
|
|
return number(math.Ceil(float64(i)))
|
|
},
|
|
"type": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'type' expected a value, but no value was given")
|
|
}
|
|
t := reflect.TypeOf(a[0]).String()
|
|
switch t {
|
|
case "main.number":
|
|
return "number"
|
|
case "[]main.expression":
|
|
return "list"
|
|
case "*main.IOHandle":
|
|
return "IOHandle"
|
|
case "main.symbol":
|
|
return "symbol"
|
|
case "func(...main.expression) main.expression":
|
|
return "compiled-procedure"
|
|
case "main.proc":
|
|
return "procedure"
|
|
case "main.exception":
|
|
return "exception"
|
|
case "main.macro":
|
|
return "macro"
|
|
case "main.GUI":
|
|
return "GUI"
|
|
case "main.guiWidget":
|
|
return "Widget"
|
|
case "fyne.Widget":
|
|
return "Widget"
|
|
case "fyne.CanvasObject":
|
|
return "Widget"
|
|
case "fyne.Container":
|
|
return "Container"
|
|
case "main.guiContainer":
|
|
return "Container"
|
|
default:
|
|
return t
|
|
}
|
|
},
|
|
"round": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("'round' expected a value, but no value was given")
|
|
}
|
|
decimals := number(0.0)
|
|
if len(a) >= 2 {
|
|
if dec, ok := a[1].(number); ok {
|
|
decimals = dec
|
|
}
|
|
}
|
|
num, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'round' expected a number, but a non-number value was received")
|
|
}
|
|
return number(math.Round(float64(num)*math.Pow(10, float64(decimals))) / math.Pow(10, float64(decimals)))
|
|
},
|
|
"&": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'&' expected two numbers, insufficient arguments were given")
|
|
}
|
|
n1, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'&' expected argument one to be a number, a non-number value was given")
|
|
}
|
|
n2, ok2 := a[1].(number)
|
|
if !ok2 {
|
|
return exception("'&' expected argument two to be a number, a non-number value was given")
|
|
}
|
|
return number(int(n1) & int(n2))
|
|
},
|
|
"|": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'|' expected two numbers, insufficient arguments were given")
|
|
}
|
|
n1, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'|' expected argument one to be a number, a non-number value was given")
|
|
}
|
|
n2, ok2 := a[1].(number)
|
|
if !ok2 {
|
|
return exception("'|' expected argument two to be a number, a non-number value was given")
|
|
}
|
|
return number(int(n1) | int(n2))
|
|
},
|
|
"<<": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'<<' expected two numbers, insufficient arguments were given")
|
|
}
|
|
n1, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'<<' expected argument one to be a number, a non-number value was given")
|
|
}
|
|
n2, ok2 := a[1].(number)
|
|
if !ok2 {
|
|
return exception("'<<' expected argument two to be a number, a non-number value was given")
|
|
}
|
|
return number(int(n1) << int(n2))
|
|
},
|
|
">>": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'>>' expected two numbers, insufficient arguments were given")
|
|
}
|
|
n1, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'>>' expected argument one to be a number, a non-number value was given")
|
|
}
|
|
n2, ok2 := a[1].(number)
|
|
if !ok2 {
|
|
return exception("'>>' expected argument two to be a number, a non-number value was given")
|
|
}
|
|
return number(int(n1) >> int(n2))
|
|
},
|
|
"^": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("'^' expected two numbers, insufficient arguments were given")
|
|
}
|
|
n1, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'^' expected argument one to be a number, a non-number value was given")
|
|
}
|
|
if len(a) == 1 {
|
|
return number(^int(n1))
|
|
}
|
|
n2, ok2 := a[1].(number)
|
|
if !ok2 {
|
|
return exception("'^' expected argument two to be a number, a non-number value was given")
|
|
}
|
|
return number(int(n1) ^ int(n2))
|
|
},
|
|
"+": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return number(0)
|
|
}
|
|
v, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'+' expected a number, but a non-number value was received")
|
|
}
|
|
for _, i := range a[1:] {
|
|
x, ok := i.(number)
|
|
if !ok {
|
|
return exception("'+' expected a number, but a non-number value was received")
|
|
}
|
|
v += x
|
|
}
|
|
return v
|
|
},
|
|
"-": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'-' expected a value, but no value was given")
|
|
}
|
|
v, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'-' expected a number, but a non-number value was received")
|
|
}
|
|
if len(a) == 1 {
|
|
return v * -1
|
|
}
|
|
for _, i := range a[1:] {
|
|
x, ok := i.(number)
|
|
if !ok {
|
|
return exception("'-' expected a number, but a non-number value was received")
|
|
}
|
|
v -= x
|
|
}
|
|
return v
|
|
},
|
|
"*": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return number(1)
|
|
}
|
|
v, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'*' expected a number, but a non-number value was received")
|
|
}
|
|
if len(a) == 1 {
|
|
return 1 / v
|
|
}
|
|
for _, i := range a[1:] {
|
|
x, ok := i.(number)
|
|
if !ok {
|
|
return exception("'*' expected a number, but a non-number value was received")
|
|
}
|
|
v *= x
|
|
}
|
|
return v
|
|
},
|
|
"/": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'/' expected a value, but no value was given")
|
|
}
|
|
v, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'/' expected a number, but a non-number value was received")
|
|
}
|
|
for _, i := range a[1:] {
|
|
x, ok := i.(number)
|
|
if !ok {
|
|
return exception("'/' expected a number, but a non-number value was received")
|
|
}
|
|
v /= x
|
|
}
|
|
return v
|
|
},
|
|
"%": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'%' expected two numbers, but too few values were given")
|
|
}
|
|
v, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'%' expected a number, but a non-number value was received")
|
|
}
|
|
v2, ok2 := a[1].(number)
|
|
if !ok2 {
|
|
return exception("'%' expected a number, but a non-number value was received")
|
|
}
|
|
out := math.Mod(float64(v), float64(v2))
|
|
if math.IsNaN(out) {
|
|
return number(0)
|
|
}
|
|
|
|
return number(out)
|
|
},
|
|
"exp": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'exp' expected two numbers, but too few values were given")
|
|
}
|
|
base, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'exp' expected a number as its first argument, a non-number value was given")
|
|
}
|
|
exp, ok2 := a[1].(number)
|
|
if !ok2 {
|
|
return exception("'exp' expected a number as its second argument, a non-number value was given")
|
|
}
|
|
return number(math.Pow(float64(base), float64(exp)))
|
|
},
|
|
"sqrt": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'sqrt' expected a number, but no value was given")
|
|
}
|
|
v, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'sqrt' expected a number, but a non-number value was received")
|
|
}
|
|
return number(math.Sqrt(float64(v)))
|
|
},
|
|
"log": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'log' expected a number, but no value was given")
|
|
}
|
|
v, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'log' expected a number, but a non-number value was received")
|
|
}
|
|
return number(math.Log(float64(v)))
|
|
},
|
|
"cos": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'cos' expected a number, but no value was given")
|
|
}
|
|
v, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'cos' expected a number, but a non-number value was received")
|
|
}
|
|
return number(math.Cos(float64(v)))
|
|
},
|
|
"sin": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'sin' expected a number, but no value was given")
|
|
}
|
|
v, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'sin' expected a number, but a non-number value was received")
|
|
}
|
|
return number(math.Sin(float64(v)))
|
|
},
|
|
"tan": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'tan' expected a number, but no value was given")
|
|
}
|
|
v, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'tan' expected a number, but a non-number value was received")
|
|
}
|
|
return number(math.Tan(float64(v)))
|
|
},
|
|
"atan": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'atan' expected at least one number, but no value was given")
|
|
}
|
|
v, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'atan' expected a number, but a non-number value was received")
|
|
}
|
|
if len(a) == 1 {
|
|
return number(math.Atan(float64(v)))
|
|
}
|
|
v2, ok2 := a[1].(number)
|
|
if !ok2 {
|
|
return exception("'atan' expected a number as its second argument, but a non-number value was received")
|
|
}
|
|
return number(math.Atan2(float64(v), float64(v2)))
|
|
},
|
|
"min": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'min' expected a value, but no value was given")
|
|
}
|
|
val := number(math.MaxFloat64)
|
|
for _, v := range a {
|
|
value, ok := v.(number)
|
|
if !ok {
|
|
return exception("'min' expected a number, but a non-number value was received")
|
|
}
|
|
if value < val {
|
|
val = value
|
|
}
|
|
}
|
|
return val
|
|
},
|
|
"max": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'max' expected a value, but no value was given")
|
|
}
|
|
val := number(math.SmallestNonzeroFloat64)
|
|
for _, v := range a {
|
|
value, ok := v.(number)
|
|
if !ok {
|
|
return exception("'max' expected a number, but a non-number value was received")
|
|
}
|
|
if value > val {
|
|
val = value
|
|
}
|
|
}
|
|
return val
|
|
},
|
|
"<=": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'<=' expected, but did not receive, two values")
|
|
}
|
|
n1, ok1 := a[0].(number)
|
|
n2, ok2 := a[1].(number)
|
|
if !ok1 || !ok2 {
|
|
return exception("'<=' expected numbers, but a non-number value was received")
|
|
}
|
|
return n1 <= n2
|
|
},
|
|
"<": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'<' expected, but did not receive, two values")
|
|
}
|
|
n1, ok1 := a[0].(number)
|
|
n2, ok2 := a[1].(number)
|
|
if !ok1 || !ok2 {
|
|
return exception("'<' expected numbers, but a non-number value was received")
|
|
}
|
|
return n1 < n2
|
|
},
|
|
">=": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'>=' expected, but did not receive, two values")
|
|
}
|
|
n1, ok1 := a[0].(number)
|
|
n2, ok2 := a[1].(number)
|
|
if !ok1 || !ok2 {
|
|
return exception("'>=' expected numbers, but a non-number value was received")
|
|
}
|
|
return n1 >= n2
|
|
},
|
|
">": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'>' expected, but did not receive, two values")
|
|
}
|
|
n1, ok1 := a[0].(number)
|
|
n2, ok2 := a[1].(number)
|
|
if !ok1 || !ok2 {
|
|
return exception("'>' expected numbers, but a non-number value was received")
|
|
}
|
|
return n1 > n2
|
|
},
|
|
"equal?": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("'equal?' expected, but did not receive, at least one value")
|
|
} else if len(a) == 1 {
|
|
return true
|
|
}
|
|
eq := false
|
|
for i := 0; i < len(a)-1; i++ {
|
|
eq = reflect.DeepEqual(a[i], a[i+1])
|
|
if !eq {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
},
|
|
"atom?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'atom?' expected a value, but no value was given")
|
|
}
|
|
switch a[0].(type) {
|
|
case []expression:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
},
|
|
"assoc?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'assoc?' expected a value, but no value was given")
|
|
}
|
|
list, ok := a[0].([]expression)
|
|
if !ok {
|
|
return false
|
|
}
|
|
for x := range list {
|
|
l, ok := list[x].([]expression)
|
|
if !ok || len(l) != 2 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
},
|
|
"assoc-has-key?": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'assoc-has-key' expected at least two values, an associative list and a key value; insufficient arguments were given")
|
|
}
|
|
list, ok := a[0].([]expression)
|
|
if !ok {
|
|
return exception("'assoc' expected argument 1 to be an association list, a non-list value was given")
|
|
}
|
|
for x := range list {
|
|
l, ok := list[x].([]expression)
|
|
if !ok || len(l) != 2 {
|
|
return exception("'assoc' expected argument 1 to be an association list, a list was given, but it is not an association list")
|
|
}
|
|
if reflect.DeepEqual(l[0], a[1]) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
"assoc": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'assoc' expected at least two values, a list and a value; insufficient arguments were given")
|
|
}
|
|
list, ok := a[0].([]expression)
|
|
if !ok {
|
|
return exception("'assoc' expected argument 1 to be an association list, a non-list value was given")
|
|
}
|
|
for x := range list {
|
|
l, ok := list[x].([]expression)
|
|
if !ok || len(l) != 2 {
|
|
return exception("'assoc' expected argument 1 to be an association list, a list was given, but it is not an association list")
|
|
}
|
|
if reflect.DeepEqual(l[0], a[1]) {
|
|
if len(a) >= 3 {
|
|
if len(a) > 3 && AnythingToBool(a[3]).(bool) {
|
|
l[1] = a[2]
|
|
list[x] = l
|
|
return list
|
|
} else {
|
|
cpy := make([]expression, len(list))
|
|
copy(cpy, list)
|
|
cpy[x] = []expression{l[0], a[2]}
|
|
return cpy
|
|
}
|
|
}
|
|
return l[1]
|
|
}
|
|
}
|
|
if len(a) >= 3 {
|
|
if len(a) > 3 && AnythingToBool(a[3]).(bool) {
|
|
// This will almost certainly create a new assoc
|
|
// and return it rather than adding to the existing
|
|
// assoc, and is thus similar to the else branch,
|
|
// but in theory has a chance that a copy will not
|
|
// be needed, thus potentially executing faster.
|
|
list = append(list, []expression{a[1], a[2]})
|
|
return list
|
|
} else {
|
|
cpy := make([]expression, len(list))
|
|
copy(cpy, list)
|
|
cpy = append(cpy, []expression{a[1], a[2]})
|
|
return cpy
|
|
}
|
|
}
|
|
return exception("'assoc' could not find the key: " + String(a[1], true))
|
|
},
|
|
"number?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'number?' expected a value, but no value was given")
|
|
}
|
|
switch a[0].(type) {
|
|
case float64, number:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
},
|
|
"byte?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'byte?' expected a value, but no value was given")
|
|
}
|
|
num, ok := a[0].(number)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return int(num) >= 0 && int(num) <= 255
|
|
},
|
|
"string?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'string?' expected a value, but no value was given")
|
|
}
|
|
switch a[0].(type) {
|
|
case string:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
},
|
|
"io-handle?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'io-handle?' expected a value, but no value was given")
|
|
}
|
|
switch a[0].(type) {
|
|
case *IOHandle:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
},
|
|
"string-buf?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'string-buf?' expected a value, but no value was given")
|
|
}
|
|
switch v := a[0].(type) {
|
|
case *IOHandle:
|
|
_, ok := v.Obj.(*strings.Builder)
|
|
return ok
|
|
default:
|
|
return false
|
|
}
|
|
},
|
|
"bool?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'bool?' expected a value, but no value was given")
|
|
}
|
|
switch a[0].(type) {
|
|
case bool:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
},
|
|
"symbol?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'symbol?' expected a value, but no value was given")
|
|
}
|
|
switch a[0].(type) {
|
|
case symbol:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
},
|
|
"null?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'null?' expected a list, but no value was given")
|
|
}
|
|
switch i := a[0].(type) {
|
|
case []expression:
|
|
return len(i) == 0
|
|
default:
|
|
return false
|
|
}
|
|
|
|
},
|
|
"pair?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'pair?' expected a value, but no value was given")
|
|
}
|
|
switch i := a[0].(type) {
|
|
case []expression:
|
|
return len(i) > 0
|
|
default:
|
|
return false
|
|
}
|
|
},
|
|
"list?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'list?' expected a value, but no value was given")
|
|
}
|
|
switch a[0].(type) {
|
|
case []expression:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
},
|
|
"macro?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'macro?' expected a value, but no value was given")
|
|
}
|
|
switch a[0].(type) {
|
|
case macro:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
},
|
|
"procedure?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'proc?' expected a value, but no value was given")
|
|
}
|
|
switch a[0].(type) {
|
|
case proc, func(a ...expression) expression:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
},
|
|
"length": func(a ...expression) expression {
|
|
switch i := a[0].(type) {
|
|
case []expression:
|
|
return number(len(i))
|
|
case string:
|
|
return number(len([]rune(String(i, false))))
|
|
case exception:
|
|
return number(len([]rune(String(string(i), false))))
|
|
case *IOHandle:
|
|
switch h := i.Obj.(type) {
|
|
case *strings.Builder:
|
|
return number(len([]rune(String(h.String(), false))))
|
|
default:
|
|
return exception("'length' is not defined for a non-string-buf IOHandle")
|
|
}
|
|
default:
|
|
return exception("'length' expected a list or a string, but the given value was not a list or a string")
|
|
}
|
|
},
|
|
"string-make-buf": func(a ...expression) expression {
|
|
var b strings.Builder
|
|
if len(a) > 0 {
|
|
b.WriteString(String(a[0], false))
|
|
}
|
|
obj := &IOHandle{&b, true, "string-buf"}
|
|
openFiles = append(openFiles, obj)
|
|
return obj
|
|
},
|
|
"string-buf-clear": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'string-buf-clear' expects an IOHandle for a string-buf as an argument, no argument was given")
|
|
}
|
|
h, ok := a[0].(*IOHandle)
|
|
if !ok {
|
|
return exception("'string-buf-clear' expects an IOHandle as its argument, a non-IOHandle argument was given")
|
|
}
|
|
if !h.Open {
|
|
return exception("a closed IOHandle was given to 'string-buf-clear'")
|
|
}
|
|
switch buf := h.Obj.(type) {
|
|
case *strings.Builder:
|
|
buf.Reset()
|
|
default:
|
|
return exception("'string-buf-clear' expects an IOHandle representing a string-buf as its argument, a non-string-buf-IOHandle argument was given")
|
|
}
|
|
return make([]expression, 0)
|
|
},
|
|
"string-upper": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("no value was given to 'string-upper'")
|
|
}
|
|
s, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'string-upper' expected a string but received a non-string value")
|
|
}
|
|
return strings.ToUpper(s)
|
|
},
|
|
"string-lower": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("no value was given to 'string-lower'")
|
|
}
|
|
s, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'string-lower' expected a string but received a non-string value")
|
|
}
|
|
return strings.ToLower(s)
|
|
},
|
|
"string-format": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("no value was given to 'string-format'")
|
|
}
|
|
format, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'string-format' expected a format string and was given a non-string value as its first argument")
|
|
}
|
|
if len(a) == 1 {
|
|
return format
|
|
}
|
|
var outputBuf, percentBuf strings.Builder
|
|
parsePercent := false
|
|
varNum := 1
|
|
for _, c := range format {
|
|
if parsePercent {
|
|
if c == 'v' {
|
|
parsePercent = false
|
|
if varNum < len(a) {
|
|
s, err := formatValue(percentBuf.String(), a[varNum])
|
|
if err != nil {
|
|
return exception("'string-format' was given an invalid formatting specifier")
|
|
}
|
|
outputBuf.WriteString(s)
|
|
percentBuf.Reset()
|
|
varNum++
|
|
} else {
|
|
return exception("'string-format' was not given enough values to format the string")
|
|
}
|
|
} else {
|
|
percentBuf.WriteRune(c)
|
|
}
|
|
} else if c == '%' {
|
|
parsePercent = true
|
|
} else if c == '%' {
|
|
outputBuf.WriteRune(c)
|
|
} else {
|
|
outputBuf.WriteRune(c)
|
|
}
|
|
}
|
|
|
|
return unescapeString(outputBuf.String())
|
|
},
|
|
"string-index-of": func(a ...expression) expression {
|
|
// (string-index-of [haystack] [needle])
|
|
if len(a) < 2 {
|
|
return exception("'string-index-of' expects two strings, a haystack and a needle, insufficient arguments were given")
|
|
}
|
|
haystack, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'string-index-of' expects a string as its first argument; a non-string value was given")
|
|
}
|
|
needle, ok := a[1].(string)
|
|
if !ok {
|
|
return exception("'string-index-of' expects a string as its second argument; a non-string value was given")
|
|
}
|
|
return number(strings.Index(haystack, needle))
|
|
},
|
|
"string-ref": func(a ...expression) expression {
|
|
// TODO remove this proc entirely someday?
|
|
return globalenv.vars["ref"].(func(...expression) expression)(a...)
|
|
},
|
|
"cons": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception(fmt.Sprintf("'cons' expected two arguments, %d given", len(a)))
|
|
}
|
|
switch car := a[0]; cdr := a[1].(type) {
|
|
case []expression:
|
|
return append([]expression{car}, cdr...)
|
|
default:
|
|
return []expression{car, cdr}
|
|
}
|
|
},
|
|
"car": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception(fmt.Sprintf("%q expected a list with at least one item in it, but a nil value was given", "car"))
|
|
}
|
|
switch i := a[0].(type) {
|
|
case []expression:
|
|
return a[0].([]expression)[0]
|
|
default:
|
|
return exception(fmt.Sprintf("%q expected a list, but a %T was given", "car", i))
|
|
}
|
|
},
|
|
"cdr": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("\"cdr\" expected a list with at least one item in it, but a nil value was given")
|
|
}
|
|
switch i := a[0].(type) {
|
|
case []expression:
|
|
return a[0].([]expression)[1:]
|
|
default:
|
|
return exception(fmt.Sprintf("%q expected a list, but a %T was given", "car", i))
|
|
}
|
|
},
|
|
"list->string": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("insufficient number of arguments given to 'list->string', expected a list and a string")
|
|
}
|
|
listIn, ok := a[0].([]expression)
|
|
if !ok {
|
|
return exception("'list->string' expected a list, but was given a non-list value")
|
|
}
|
|
joinOn := ""
|
|
if len(a) >= 2 {
|
|
switch sp := a[1].(type) {
|
|
case string:
|
|
joinOn = sp
|
|
case exception:
|
|
joinOn = string(sp)
|
|
case symbol:
|
|
joinOn = string(sp)
|
|
case number:
|
|
joinOn = strconv.Itoa(int(sp))
|
|
case []expression:
|
|
joinOn = String(sp, false)
|
|
default:
|
|
return exception("'list->string' could not convert the given join point to a string upon which to join")
|
|
}
|
|
}
|
|
sSlice := make([]string, len(listIn))
|
|
for i := range listIn {
|
|
sSlice[i] = String(listIn[i], false)
|
|
}
|
|
return strings.Join(sSlice, joinOn)
|
|
},
|
|
"range": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return make([]expression, 0)
|
|
}
|
|
start := number(0)
|
|
step := number(1)
|
|
|
|
count, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'range' expected a number representing a count, a non-number value was given")
|
|
}
|
|
if int(count) < 1 {
|
|
return exception("'range' expected a number greater than zero as its first argument, an invalid value was given")
|
|
}
|
|
if len(a) > 1 {
|
|
start, ok = a[1].(number)
|
|
if !ok {
|
|
return exception("'range' expected a number representing a starting point as its second argument, a non-number value was given")
|
|
}
|
|
}
|
|
if len(a) > 2 {
|
|
step, ok = a[2].(number)
|
|
if !ok {
|
|
return exception("'range' expected a number representing a step count as its third argument, a non-number value was given")
|
|
}
|
|
}
|
|
out := make([]expression, 0, int(count))
|
|
for ; count > 0; count-- {
|
|
out = append(out, start)
|
|
start += step
|
|
}
|
|
return out
|
|
},
|
|
"list-seed": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("insufficient number of arguments given to 'list-seed', expected number and value")
|
|
}
|
|
count, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'list-seed' expected a number as its first argument, but was given a non-number value")
|
|
}
|
|
if count < 1 {
|
|
return exception("'list-seed' expects a positive non-zero number as its first argument, a number less than 1 was given")
|
|
}
|
|
l := make([]expression, int(count))
|
|
switch t := a[1].(type) {
|
|
case []expression:
|
|
for i := range l {
|
|
s := make([]expression, len(t))
|
|
copy(s, t)
|
|
l[i] = s
|
|
}
|
|
default:
|
|
for i := range l {
|
|
l[i] = t
|
|
}
|
|
}
|
|
return l
|
|
},
|
|
"list-sort": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("insufficient number of arguments given to 'list-sort', expected a list")
|
|
}
|
|
listIn, ok := a[0].([]expression)
|
|
if !ok {
|
|
return exception("'list-sort' expected a list, but was given a non-list value")
|
|
}
|
|
if len(a) > 1 {
|
|
n, ok := a[1].(number)
|
|
if !ok {
|
|
return exception("'list-sort' expected a number as its optional second argument, but a non-number was given")
|
|
}
|
|
return MergeSort(listIn, int(n))
|
|
}
|
|
return MergeSort(listIn, -1)
|
|
},
|
|
"list-join": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return make([]expression, 0)
|
|
}
|
|
base, ok := a[0].([]expression)
|
|
if !ok {
|
|
return exception("'list-join' expected a list but was given a non-list value")
|
|
}
|
|
out := DeepCopySlice(base).([]expression)
|
|
for _, v := range a[1:] {
|
|
val, ok2 := v.([]expression)
|
|
if !ok2 {
|
|
return exception("'list-join' expected a list but was given a non-list value")
|
|
}
|
|
out = append(out, val...)
|
|
}
|
|
return out
|
|
},
|
|
"append": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return make([]expression, 0)
|
|
}
|
|
switch base := a[0].(type) {
|
|
case []expression:
|
|
base = append(base, a[1:]...)
|
|
return base
|
|
default:
|
|
var out strings.Builder
|
|
for i := range a {
|
|
out.WriteString(String(a[i], false))
|
|
}
|
|
return out.String()
|
|
}
|
|
},
|
|
"ref": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'ref' expects at least two arguments, a string|list and an index number, insufficient arguments were given")
|
|
}
|
|
|
|
switch t := a[1].(type) {
|
|
case number:
|
|
i := int(t)
|
|
switch v := a[0].(type) {
|
|
case []expression:
|
|
if i >= len(v) {
|
|
return exception("'ref' was given an index number that is out of bounds for the given list")
|
|
} else if i < 0 {
|
|
i = len(v) + i
|
|
if i < 0 {
|
|
return exception("'ref' was given an index number that is out of bounds for the given list")
|
|
}
|
|
}
|
|
if len(a) >= 3 {
|
|
if len(a) > 3 && AnythingToBool(a[3]).(bool) {
|
|
v[int(i)] = a[2]
|
|
return v
|
|
} else {
|
|
cpy := DeepCopySlice(v)
|
|
cpy.([]expression)[int(i)] = a[2]
|
|
return cpy
|
|
}
|
|
}
|
|
return v[int(i)]
|
|
case string:
|
|
if i >= len(v) {
|
|
return exception("'ref' was given an index number that is out of bounds for the given list")
|
|
} else if i < 0 {
|
|
i = len(v) + i
|
|
if i < 0 {
|
|
return exception("'ref' was given an index number that is out of bounds for the given list")
|
|
}
|
|
}
|
|
if len(a) >= 3 {
|
|
s := String(a[2], false)
|
|
if len(s) > 0 {
|
|
return v[:i] + s + v[i+1:]
|
|
} else {
|
|
return v[:i] + v[i+1:]
|
|
}
|
|
}
|
|
return string(v[int(i)])
|
|
default:
|
|
return exception("'ref' was given a non-string non-list value")
|
|
}
|
|
case []expression:
|
|
switch v := a[0].(type) {
|
|
case []expression:
|
|
if len(t) == 0 {
|
|
return exception("'ref' was given an index list that is empty")
|
|
}
|
|
var val expression
|
|
var copyList bool = !(len(a) > 3 && AnythingToBool(a[3]).(bool))
|
|
var newList expression
|
|
if copyList {
|
|
newList = DeepCopySlice(v)
|
|
val = newList
|
|
} else {
|
|
val = v
|
|
}
|
|
for count, i := range t {
|
|
if list, ok := val.([]expression); ok {
|
|
if num, ok := i.(number); ok {
|
|
if int(num) >= len(list) {
|
|
return exception("'ref' was given a list of indexes containing an out of bounds reference")
|
|
} else if int(num) < 0 {
|
|
num = number(len(list) + int(num))
|
|
if int(num) < 0 {
|
|
return exception("'ref' was given a list of indexes containing an out of bounds reference")
|
|
}
|
|
}
|
|
val = list[int(num)]
|
|
|
|
if len(a) >= 3 && count == len(t)-1 {
|
|
list[int(num)] = a[2]
|
|
if !copyList {
|
|
return v
|
|
} else {
|
|
return newList
|
|
}
|
|
}
|
|
} else {
|
|
return exception("'ref' was given a list of indexes containing a non-number value")
|
|
}
|
|
} else {
|
|
return exception("'ref' was given a list of indexes that does not correspond to the depth of the given list")
|
|
}
|
|
}
|
|
return val
|
|
case string:
|
|
if len(t) == 0 {
|
|
return exception("'ref' was given an empty string, which cannot be indexed")
|
|
}
|
|
i := int(t[0].(number))
|
|
if i >= len(v) {
|
|
return exception("'ref' was given an index number that is out of bounds for the given list")
|
|
} else if i < 0 {
|
|
i = len(v) + i
|
|
if i < 0 {
|
|
return exception("'ref' was given an index number that is out of bounds for the given list")
|
|
}
|
|
}
|
|
if len(a) >= 3 {
|
|
s := String(a[2], false)
|
|
if len(s) > 0 {
|
|
return v[:i] + s + v[i+1:]
|
|
} else {
|
|
return v[:i] + v[i+1:]
|
|
}
|
|
}
|
|
return string(v[int(i)])
|
|
default:
|
|
return exception("'ref' was given a non-string non-list value")
|
|
}
|
|
default:
|
|
return exception("'ref' expects argument two to be an index number or a list of index numbers (for sublists), a non-number/non-list argument was given")
|
|
}
|
|
|
|
},
|
|
"list": func(a ...expression) expression {
|
|
return a
|
|
},
|
|
"list-ref": func(a ...expression) expression {
|
|
// TODO remove this proc entirely someday?
|
|
return globalenv.vars["ref"].(func(...expression) expression)(a...)
|
|
},
|
|
"member?": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'member?' expects a list and a value to search for, insufficient arguments were given")
|
|
}
|
|
list, ok := a[0].([]expression)
|
|
if !ok {
|
|
return exception("'member?' expects argument one to be a list, a non-list argument was given")
|
|
}
|
|
for i := range list {
|
|
if reflect.DeepEqual(list[i], a[1]) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
"reverse": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return make([]expression, 0)
|
|
}
|
|
switch l := a[0].(type) {
|
|
case []expression:
|
|
out := make([]expression, len(l))
|
|
for i, n := range l {
|
|
j := len(l) - i - 1
|
|
out[j] = n
|
|
}
|
|
return out
|
|
case string:
|
|
var b strings.Builder
|
|
r := []rune(l)
|
|
for i := len(r) - 1; i >= 0; i-- {
|
|
b.WriteRune(r[i])
|
|
}
|
|
return b.String()
|
|
default:
|
|
return exception("'reverse' expected a list but was given a non-list value")
|
|
}
|
|
|
|
},
|
|
"not": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'not' expects a value but no value was given")
|
|
}
|
|
if i, ok := a[0].(bool); ok && !i {
|
|
return true
|
|
}
|
|
return false
|
|
},
|
|
"~bool": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("`~bool` expects a value, but n value was given")
|
|
}
|
|
switch v := a[0].(type) {
|
|
case string:
|
|
if v == "" {
|
|
return false
|
|
}
|
|
return true
|
|
case number:
|
|
if v == 0 {
|
|
return false
|
|
}
|
|
return true
|
|
case *IOHandle:
|
|
return v.Open
|
|
case []expression:
|
|
if len(v) > 0 {
|
|
return true
|
|
}
|
|
return false
|
|
case bool:
|
|
return v
|
|
default:
|
|
return true
|
|
}
|
|
},
|
|
"filter": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'filter' expects two arguments: a procedure and an argument list, too few arguments were given")
|
|
}
|
|
|
|
switch p := a[0].(type) {
|
|
case proc, func(...expression) expression, macro:
|
|
list, ok := a[1].([]expression)
|
|
if !ok {
|
|
return exception("'filter' expects a list as its second argument, a non-list value was given")
|
|
}
|
|
out := make([]expression, 0, len(list))
|
|
for i := range list {
|
|
if AnythingToBool(apply(p, []expression{list[i]}, "Filter routine")).(bool) {
|
|
out = append(out, list[i])
|
|
}
|
|
}
|
|
return out
|
|
default:
|
|
return exception("filter expects a procedure followed by a list")
|
|
}
|
|
|
|
},
|
|
"map": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'map' expects a procedure followed by at least one list")
|
|
}
|
|
ml, err := MergeLists(a[1:])
|
|
if err != nil {
|
|
return exception("map error: " + err.Error())
|
|
}
|
|
mergedLists := ml.([]expression)
|
|
switch i := a[0].(type) {
|
|
case proc, func(...expression) expression:
|
|
out := make([]expression, 0, len(mergedLists))
|
|
for merge := range mergedLists {
|
|
out = append(out, apply(i, mergedLists[merge].([]expression), "Map routine"))
|
|
}
|
|
return out
|
|
default:
|
|
return exception("map expects a procedure followed by at least one list")
|
|
}
|
|
},
|
|
"reduce": func(a ...expression) expression {
|
|
if len(a) < 3 {
|
|
return exception("'reduce' expects a procedure followed by an initializer value and a list, too few arguments given")
|
|
}
|
|
switch p := a[0].(type) {
|
|
case proc, func(...expression) expression:
|
|
init := a[1]
|
|
list, ok := a[2].([]expression)
|
|
if !ok {
|
|
return exception("'reduce' expects a list as its third argument, a non-list value was given")
|
|
}
|
|
for i := range list {
|
|
init = apply(p, []expression{list[i], init}, "Reduce routine")
|
|
}
|
|
return init
|
|
default:
|
|
return exception("'reduce' expects a procedure as its first argument, a non-procedure value was given")
|
|
}
|
|
},
|
|
"for-each": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("for-each expects a procedure followed by at least one list")
|
|
}
|
|
ml, err := MergeLists(a[1:])
|
|
if err != nil {
|
|
return exception("for-each error: " + err.Error())
|
|
}
|
|
mergedLists := ml.([]expression)
|
|
switch i := a[0].(type) {
|
|
case proc, func(...expression) expression:
|
|
for merge := range mergedLists {
|
|
apply(i, mergedLists[merge].([]expression), "For-each routine")
|
|
}
|
|
return make([]expression, 0)
|
|
default:
|
|
return exception("for-each expects a procedure followed by at least one list")
|
|
}
|
|
},
|
|
"display": func(a ...expression) expression {
|
|
var out strings.Builder
|
|
for i := range a {
|
|
out.WriteString(String(a[i], false))
|
|
}
|
|
SysoutPrint(out.String(), Sysout)
|
|
return make([]expression, 0)
|
|
},
|
|
"display-lines": func(a ...expression) expression {
|
|
var out strings.Builder
|
|
for i := range a {
|
|
switch val := a[i].(type) {
|
|
case *IOHandle:
|
|
buf, ok := val.Obj.(*strings.Builder)
|
|
if ok {
|
|
out.WriteString(buf.String())
|
|
} else {
|
|
out.WriteString(String(val, true))
|
|
}
|
|
out.WriteRune('\n')
|
|
default:
|
|
out.WriteString(String(val, false))
|
|
out.WriteRune('\n')
|
|
}
|
|
}
|
|
SysoutPrint(out.String(), Sysout)
|
|
return make([]expression, 0)
|
|
},
|
|
"write": func(a ...expression) expression {
|
|
if len(a) == 1 {
|
|
SysoutPrint(String(a[0], false), Sysout)
|
|
return make([]expression, 0)
|
|
} else if len(a) > 1 {
|
|
stringOut := String(a[0], false)
|
|
obj, ok := a[1].(*IOHandle)
|
|
if !ok {
|
|
return exception("'write' expected an IO handle as its second argument, but was not given an IO handle")
|
|
}
|
|
if !obj.Open {
|
|
return exception("'write' was given an IO handle that is already closed")
|
|
}
|
|
switch ft := obj.Obj.(type) {
|
|
case *os.File:
|
|
ft.WriteString(stringOut)
|
|
case *net.Conn:
|
|
(*ft).Write([]byte(stringOut))
|
|
case *tls.Conn:
|
|
ft.Write([]byte(stringOut))
|
|
case *strings.Builder:
|
|
ft.WriteString(stringOut)
|
|
default:
|
|
return exception("'write' was given an IO handle that is not supported")
|
|
}
|
|
return a[1].(*IOHandle)
|
|
}
|
|
return exception("'write' received insufficient arguments")
|
|
},
|
|
"write-bytes": func(a ...expression) expression {
|
|
if len(a) == 1 {
|
|
a = append(a, &IOHandle{os.Stdout, true, "file (write-only)"})
|
|
}
|
|
obj, ok := a[1].(*IOHandle)
|
|
if !ok {
|
|
return exception("'write-bytes' expected an IO handle as its argument, but was not given an IO handle")
|
|
}
|
|
if !obj.Open {
|
|
return exception("'write-bytes' was given an IO handle that is already closed")
|
|
}
|
|
switch val := a[0].(type) {
|
|
case number:
|
|
n := byte(val)
|
|
if n < 0 || n > 255 {
|
|
return exception("'write-bytes' was given a number that is not a byte")
|
|
}
|
|
switch ft := obj.Obj.(type) {
|
|
case *os.File:
|
|
ft.Write([]byte{n})
|
|
case *net.Conn:
|
|
(*ft).Write([]byte{n})
|
|
case *tls.Conn:
|
|
ft.Write([]byte{n})
|
|
case *strings.Builder:
|
|
ft.Write([]byte{n})
|
|
default:
|
|
return false
|
|
}
|
|
return a[1].(*IOHandle)
|
|
case []expression:
|
|
bys := make([]byte, 0, len(val))
|
|
for _, v := range val {
|
|
n, ok := v.(number)
|
|
if !ok {
|
|
return exception("'write-bytes' was given a non-number value (inside of a list)")
|
|
}
|
|
if n < 0 || n > 255 {
|
|
return exception("'write-bytes' was given a number that is not a byte")
|
|
}
|
|
bys = append(bys, byte(n))
|
|
}
|
|
switch ft := obj.Obj.(type) {
|
|
case *os.File:
|
|
ft.Write(bys)
|
|
case *net.Conn:
|
|
(*ft).Write(bys)
|
|
case *tls.Conn:
|
|
ft.Write(bys)
|
|
case *strings.Builder:
|
|
ft.Write(bys)
|
|
default:
|
|
return false
|
|
}
|
|
return a[1].(*IOHandle)
|
|
}
|
|
return make([]expression, 0)
|
|
|
|
},
|
|
"write-raw": func(a ...expression) expression {
|
|
if len(a) == 1 {
|
|
SysoutPrint(String(a[0], true), Sysout)
|
|
} else if len(a) >= 2 {
|
|
obj, ok := a[1].(*IOHandle)
|
|
if !ok {
|
|
return exception("'write-raw' expected an IO handle as its argument, but was not given an IO handle")
|
|
}
|
|
if !obj.Open {
|
|
return exception("'write-raw' was given an IO handle that is already closed")
|
|
}
|
|
switch ft := obj.Obj.(type) {
|
|
case *os.File:
|
|
ft.WriteString(String(a[0], true))
|
|
case *net.Conn:
|
|
(*ft).Write([]byte(String(a[0], true)))
|
|
case *tls.Conn:
|
|
ft.Write([]byte([]byte(String(a[0], true))))
|
|
case *strings.Builder:
|
|
ft.WriteString(String(a[0], true))
|
|
default:
|
|
return false
|
|
}
|
|
return a[1].(*IOHandle)
|
|
}
|
|
return make([]expression, 0)
|
|
},
|
|
"read-all": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
text, err := ioutil.ReadAll(os.Stdin)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return string(text)
|
|
} else {
|
|
obj, ok := a[0].(*IOHandle)
|
|
if !ok {
|
|
return exception("'read-all' expected an IO handle as its argument, but was not given an IO handle")
|
|
}
|
|
if !obj.Open {
|
|
return exception("'read-all' was given an IO handle that is already closed")
|
|
}
|
|
switch f := obj.Obj.(type) {
|
|
case *os.File:
|
|
b, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
return exception("'read-all' could not read from the given IOHandle")
|
|
}
|
|
return string(b)
|
|
case *net.Conn:
|
|
b, err := ioutil.ReadAll(*f)
|
|
if err != nil {
|
|
return exception("'read-all' could not read from the given IOHandle")
|
|
}
|
|
return string(b)
|
|
case *tls.Conn:
|
|
b, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
return exception("'read-all' could not read from the given IOHandle")
|
|
}
|
|
return string(b)
|
|
case *strings.Builder:
|
|
return f.String()
|
|
default:
|
|
return exception("'read-all' has not been implemented for this object type")
|
|
}
|
|
}
|
|
},
|
|
"read-all-bytes": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
a = append(a, &IOHandle{os.Stdin, true, "file (read-only)"})
|
|
}
|
|
obj, ok := a[0].(*IOHandle)
|
|
if !ok {
|
|
return exception("'read-all-bytes' expected an IO handle as its argument, but was not given an IO handle")
|
|
}
|
|
if !obj.Open {
|
|
return exception("'read-all-bytes' was given an IO handle that is already closed")
|
|
}
|
|
switch f := obj.Obj.(type) {
|
|
case *os.File:
|
|
b, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
return exception("'read-all-bytes' could not read from the given IOHandle")
|
|
}
|
|
return ByteSliceToExpressionSlice(b)
|
|
case *net.Conn:
|
|
b, err := ioutil.ReadAll(*f)
|
|
if err != nil {
|
|
return exception("'read-all-bytes' could not read from the given IOHandle")
|
|
}
|
|
return ByteSliceToExpressionSlice(b)
|
|
case *tls.Conn:
|
|
b, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
return exception("'read-all-bytes' could not read from the given IOHandle")
|
|
}
|
|
return ByteSliceToExpressionSlice(b)
|
|
case *strings.Builder:
|
|
return ByteSliceToExpressionSlice([]byte(f.String()))
|
|
default:
|
|
return exception("'read-all-bytes' has not been implemented for this object type")
|
|
}
|
|
|
|
},
|
|
"read-all-lines": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
a = append(a, &IOHandle{os.Stdin, true, "file (read-only)"})
|
|
}
|
|
obj, ok := a[0].(*IOHandle)
|
|
if !ok {
|
|
return exception("'read-all-lines' expected an IO handle as its argument, but was not given an IO handle")
|
|
}
|
|
if !obj.Open {
|
|
return exception("'read-all-lines' was given an IO handle that is already closed")
|
|
}
|
|
switch f := obj.Obj.(type) {
|
|
case *os.File:
|
|
b, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
return exception("'read-all-lines' could not read from the given IOHandle")
|
|
}
|
|
o := strings.SplitN(string(b), "\n", -1)
|
|
return StringSliceToExpressionSlice(o).([]expression)
|
|
case *net.Conn:
|
|
b, err := ioutil.ReadAll(*f)
|
|
if err != nil {
|
|
return exception("'read-all-lines' could not read from the given IOHandle")
|
|
}
|
|
o := strings.SplitN(string(b), "\n", -1)
|
|
return StringSliceToExpressionSlice(o).([]expression)
|
|
case *tls.Conn:
|
|
b, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
return exception("'read-all-lines' could not read from the given IOHandle")
|
|
}
|
|
o := strings.SplitN(string(b), "\n", -1)
|
|
return StringSliceToExpressionSlice(o).([]expression)
|
|
case *strings.Builder:
|
|
s := f.String()
|
|
o := strings.SplitN(s, "\n", -1)
|
|
return StringSliceToExpressionSlice(o).([]expression)
|
|
default:
|
|
return exception("'read-all-lines' has not been implemented for this object type")
|
|
}
|
|
},
|
|
"read-bytes": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
reader := bufio.NewReader(os.Stdin)
|
|
b, err := reader.ReadByte()
|
|
if err != nil {
|
|
return exception("'read-bytes' could not read from stdin")
|
|
}
|
|
return number(b)
|
|
} else {
|
|
obj, ok := a[0].(*IOHandle)
|
|
if !ok {
|
|
return exception("'read-char' expected an IO handle as its argument, but was not given an IO handle")
|
|
}
|
|
if !obj.Open {
|
|
return exception("'read-char' was given an IO handle that is already closed")
|
|
}
|
|
|
|
count := 1
|
|
if len(a) > 1 {
|
|
c, ok := a[1].(number)
|
|
if !ok {
|
|
return exception("'read-bytes' expected its optional second argument to be a number")
|
|
}
|
|
count = int(c)
|
|
}
|
|
if count < 1 {
|
|
return exception("'read-bytes' expected its second argument to be a number greater than 0")
|
|
}
|
|
|
|
b := make([]byte, count)
|
|
|
|
switch f := obj.Obj.(type) {
|
|
case *os.File:
|
|
n, err := f.Read(b)
|
|
if err != nil && err != io.EOF {
|
|
return exception("'read-byte' could not read from the given IOHandle")
|
|
} else if err != nil || n == 0 {
|
|
return false
|
|
}
|
|
if count > 1 {
|
|
return ByteSliceToExpressionSlice(b)
|
|
}
|
|
return number(b[0])
|
|
case *net.Conn:
|
|
n, err := (*f).Read(b)
|
|
if err != nil && err != io.EOF {
|
|
return exception("'read-byte' could not read from the given IOHandle")
|
|
} else if err != nil || n == 0 {
|
|
return false
|
|
}
|
|
if count > 1 {
|
|
return ByteSliceToExpressionSlice(b)
|
|
}
|
|
return number(b[0])
|
|
case *tls.Conn:
|
|
n, err := f.Read(b)
|
|
if err != nil && err != io.EOF {
|
|
return exception("'read-byte' could not read from the given IOHandle")
|
|
} else if err != nil || n == 0 {
|
|
return false
|
|
}
|
|
if count > 1 {
|
|
return ByteSliceToExpressionSlice(b)
|
|
}
|
|
return number(b[0])
|
|
default:
|
|
return exception("'read-byte' has not been implemented for this object type")
|
|
}
|
|
}
|
|
|
|
},
|
|
"read-char": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
reader := bufio.NewReader(os.Stdin)
|
|
char, _, err := reader.ReadRune()
|
|
if err != nil {
|
|
return exception("'read-char' could not read from stdin")
|
|
}
|
|
return fmt.Sprintf("%c", char)
|
|
} else {
|
|
obj, ok := a[0].(*IOHandle)
|
|
if !ok {
|
|
return exception("'read-char' expected an IO handle as its argument, but was not given an IO handle")
|
|
}
|
|
if !obj.Open {
|
|
return exception("'read-char' was given an IO handle that is already closed")
|
|
}
|
|
// TODO add a strings.Builder to build output
|
|
// take a second arg for how many chars to read
|
|
// loop over readrune to read that many chars and
|
|
// build the buffer, then return the output. If an
|
|
// error is encountered while reading return the
|
|
// output if the len(buffer) > 0, otherwise an
|
|
// exception
|
|
|
|
byteBuf := make([]byte, 0, 3)
|
|
b := make([]byte, 1)
|
|
|
|
switch f := obj.Obj.(type) {
|
|
case *os.File:
|
|
for {
|
|
n, err := f.Read(b)
|
|
if err != nil && err != io.EOF {
|
|
return exception("'read-char' could not read from the given IOHandle")
|
|
} else if err != nil || n == 0 {
|
|
return false
|
|
}
|
|
byteBuf = append(byteBuf, b[0])
|
|
if utf8.FullRune(byteBuf) {
|
|
break
|
|
}
|
|
}
|
|
return string(byteBuf)
|
|
case *net.Conn:
|
|
for {
|
|
n, err := (*f).Read(b)
|
|
if err != nil && err != io.EOF {
|
|
return exception("'read-char' could not read from the given IOHandle")
|
|
} else if err != nil || n == 0 {
|
|
return false
|
|
}
|
|
byteBuf = append(byteBuf, b[0])
|
|
if utf8.FullRune(byteBuf) {
|
|
break
|
|
}
|
|
}
|
|
return string(byteBuf)
|
|
case *tls.Conn:
|
|
for {
|
|
n, err := f.Read(b)
|
|
if err != nil && err != io.EOF {
|
|
return exception("'read-char' could not read from the given IOHandle")
|
|
} else if err != nil || n == 0 {
|
|
return false
|
|
}
|
|
byteBuf = append(byteBuf, b[0])
|
|
if utf8.FullRune(byteBuf) {
|
|
break
|
|
}
|
|
}
|
|
return string(byteBuf)
|
|
default:
|
|
return exception("'read-char' has not been implemented for this object type")
|
|
}
|
|
}
|
|
},
|
|
"read-line": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
a = append(a, &IOHandle{os.Stdin, true, "file (read-only)"})
|
|
}
|
|
obj, ok := a[0].(*IOHandle)
|
|
if !ok {
|
|
return exception("'read-line' expected an IO handle as its argument, but was not given an IO handle")
|
|
}
|
|
if !obj.Open {
|
|
return exception("'read-line' was given an IO handle that is already closed")
|
|
}
|
|
split := byte('\n')
|
|
if len(a) > 1 {
|
|
s := String(a[1], false)
|
|
if len(s) > 0 {
|
|
split = s[0]
|
|
}
|
|
}
|
|
switch f := obj.Obj.(type) {
|
|
case *os.File:
|
|
var o strings.Builder
|
|
for {
|
|
b := make([]byte, 1)
|
|
n, err := f.Read(b)
|
|
if n > 0 {
|
|
if b[0] == split {
|
|
break
|
|
}
|
|
o.Write(b)
|
|
}
|
|
if err != nil && err != io.EOF && o.Len() == 0 {
|
|
return exception("'read-line' could not read from the given IOHandle")
|
|
} else if err != nil && err == io.EOF {
|
|
return false
|
|
} else if err != nil {
|
|
break
|
|
}
|
|
}
|
|
return o.String()
|
|
case *net.Conn:
|
|
var o strings.Builder
|
|
for {
|
|
b := make([]byte, 1)
|
|
n, err := (*f).Read(b)
|
|
if n > 0 {
|
|
if b[0] == '\n' {
|
|
break
|
|
}
|
|
o.Write(b)
|
|
}
|
|
if err != nil && err != io.EOF && o.Len() == 0 {
|
|
return exception("'read-line' could not read from the given IOHandle")
|
|
} else if err != nil && err == io.EOF {
|
|
return false
|
|
} else if err != nil {
|
|
break
|
|
}
|
|
}
|
|
return o.String()
|
|
default:
|
|
return exception("'read-line' has not been implemented for this object type")
|
|
}
|
|
},
|
|
"newline": func(a ...expression) expression {
|
|
SysoutPrint("\r\n", Sysout)
|
|
return make([]expression, 0)
|
|
},
|
|
"exit": func(a ...expression) expression {
|
|
code := 0
|
|
if len(a) > 0 {
|
|
if v, ok := a[0].(number); ok {
|
|
code = int(v)
|
|
}
|
|
}
|
|
SafeExit(code)
|
|
return 0
|
|
},
|
|
"slice": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("insufficient number of arguments given to 'slice', expected at least a list or string and at least one index number")
|
|
}
|
|
var n1, n2 number
|
|
var ok bool
|
|
switch val := a[0].(type) {
|
|
case []expression:
|
|
if n1, ok = a[1].(number); !ok {
|
|
return exception("'slice' expects a number as its second argument")
|
|
}
|
|
|
|
n2 = number(len(val))
|
|
if len(a) > 2 {
|
|
if n2, ok = a[2].(number); !ok {
|
|
return exception("'slice' expects a number as its third argument")
|
|
}
|
|
}
|
|
|
|
if int(n2) > len(val) {
|
|
n2 = number(len(val))
|
|
}
|
|
|
|
if n1 < 0 {
|
|
n1 = number(0)
|
|
}
|
|
|
|
if n1 >= n2 {
|
|
return make([]expression, 0)
|
|
}
|
|
|
|
return val[int(n1):int(n2)]
|
|
case string:
|
|
if n1, ok = a[1].(number); !ok {
|
|
return exception("'slice' expects a number as its second argument")
|
|
}
|
|
|
|
n2 = number(len(val))
|
|
if len(a) > 2 {
|
|
if n2, ok = a[2].(number); !ok {
|
|
return exception("'slice' expects a number as its third argument")
|
|
}
|
|
}
|
|
|
|
if int(n2) > len(val) {
|
|
n2 = number(len(val))
|
|
}
|
|
|
|
if n1 < 0 {
|
|
n1 = number(0)
|
|
}
|
|
|
|
if n1 >= n2 {
|
|
return ""
|
|
}
|
|
|
|
return val[int(n1):int(n2)]
|
|
default:
|
|
return exception("'slice' expects a string or list as its first argument, a non-string non-list value was given")
|
|
}
|
|
},
|
|
"substring": func(a ...expression) expression {
|
|
return globalenv.vars["slice"].(func(a ...expression) expression)(a...)
|
|
},
|
|
"string-trim-space": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("insufficient number of arguments given to 'string-trim-space'")
|
|
}
|
|
if s, ok := a[0].(string); ok {
|
|
return strings.TrimSpace(s)
|
|
}
|
|
return exception("'string-trim-space' expected a string but was given a non-string value")
|
|
|
|
},
|
|
"string-append": func(a ...expression) expression {
|
|
return globalenv.vars["append"].(func(...expression) expression)(a...)
|
|
},
|
|
"rune->string": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'rune->string' expectes a number, no value was given")
|
|
}
|
|
n, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'rune->string' expectes a number, a non-number value was given")
|
|
}
|
|
return string(rune(n))
|
|
|
|
},
|
|
"string->rune": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'string->rune' expectes a string, no value was given")
|
|
}
|
|
s := String(a[0], false)
|
|
r := []rune(s)
|
|
if len(r) == 0 {
|
|
return number(0)
|
|
}
|
|
return number(r[0])
|
|
},
|
|
"rune->bytes": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("insufficient number of arguments given to 'rune->bytes'")
|
|
}
|
|
r, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'rune->bytes' expectes a number, a non-number was given")
|
|
}
|
|
return ByteSliceToExpressionSlice([]byte(string(rune(r))))
|
|
},
|
|
"string->bytes": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("insufficient number of arguments given to 'string->bytes'")
|
|
}
|
|
return ByteSliceToExpressionSlice([]byte(String(a[0], false)))
|
|
},
|
|
"bytes->string": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("insufficient number of arguments given to 'bytes->string'")
|
|
}
|
|
switch b := a[0].(type) {
|
|
case number:
|
|
return string([]byte{byte(b)})
|
|
case []expression:
|
|
by := make([]byte, len(b))
|
|
for i := range b {
|
|
n, ok := b[i].(number)
|
|
if !ok {
|
|
return exception("'byte->string' encountered a non-number value in the given list")
|
|
}
|
|
by[i] = byte(n)
|
|
}
|
|
return string(by)
|
|
default:
|
|
return exception("'byte->string' expected a number or a list of numbers, an invalid item type was given")
|
|
}
|
|
},
|
|
"string->number": func(a ...expression) expression {
|
|
// (string->number [string] [[base: number]])
|
|
if len(a) == 0 {
|
|
return exception("insufficient number of arguments given to 'string->number'")
|
|
}
|
|
s, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'string->number' expects a string as its first argument, a non-string value was given")
|
|
}
|
|
|
|
base := 10
|
|
if len(a) > 1 {
|
|
b, ok2 := a[1].(number)
|
|
if !ok2 {
|
|
return exception("'string->number' expects a number representing the base being used for number parsing as its second argument, a non-number value was given")
|
|
}
|
|
base = int(b)
|
|
}
|
|
|
|
if len(a) == 1 || base == 10 {
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
if err == nil {
|
|
return number(f)
|
|
}
|
|
}
|
|
i, err := strconv.ParseInt(s, base, 64)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return number(i)
|
|
},
|
|
"string-replace": func(a ...expression) expression {
|
|
if len(a) < 3 {
|
|
return exception("'string-replace' expected three values: a string, a thing to replace in that string, and a thing to replace it with. Insufficient values were given")
|
|
}
|
|
count := -1
|
|
if len(a) > 3 {
|
|
c, ok := a[3].(number)
|
|
if !ok {
|
|
return exception("'string-replace' expected its fourth argument to be a number representing a count of replacements to be made, a non-number value was given")
|
|
}
|
|
count = int(c)
|
|
}
|
|
return strings.Replace(String(a[0], false), String(a[1], false), String(a[2], false), count)
|
|
},
|
|
"string-fields": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("insufficient number of arguments given to 'string-fields'")
|
|
}
|
|
stringIn, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'string-fields' expected a string, but was given a non-string value")
|
|
}
|
|
sf := strings.Fields(stringIn)
|
|
return StringSliceToExpressionSlice(sf).([]expression)
|
|
},
|
|
"string->list": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("insufficient number of arguments given to 'string->list'")
|
|
}
|
|
stringIn, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'string->list' expected a string, but was given a non-string value")
|
|
}
|
|
splitPoint := ""
|
|
if len(a) >= 2 {
|
|
switch sp := a[1].(type) {
|
|
case string:
|
|
splitPoint = sp
|
|
case symbol:
|
|
splitPoint = string(sp)
|
|
case number:
|
|
splitPoint = strconv.Itoa(int(sp))
|
|
case []expression:
|
|
splitPoint = String(sp, true)
|
|
default:
|
|
return exception("'string->list' could not convert the given split point to a string upon which to split")
|
|
}
|
|
}
|
|
count := -1
|
|
if len(a) > 2 {
|
|
num, ok := a[2].(number)
|
|
if !ok {
|
|
return exception("'string->list' expected a number as its third argument, but a non-number value was given")
|
|
}
|
|
if num < 1 {
|
|
count = -1
|
|
} else {
|
|
count = int(num)
|
|
}
|
|
}
|
|
sSlice := strings.SplitN(stringIn, splitPoint, count)
|
|
return StringSliceToExpressionSlice(sSlice).([]expression)
|
|
},
|
|
"regex-match?": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'regex-match?' expects two arguments, a pattern string and an input string, insufficient arguments were given")
|
|
}
|
|
pattern, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'regex-match?' expects a string representing a regular expression pattern as its first argument, but was given a non-string value")
|
|
}
|
|
str, ok := a[1].(string)
|
|
if !ok {
|
|
return exception("'regex-match?' expects a string as its second argument, but was given a non-string value")
|
|
}
|
|
match, err := regexp.MatchString(pattern, str)
|
|
if err != nil {
|
|
return exception("'regex-match?' encountered a regular expression error while matching")
|
|
}
|
|
return match
|
|
},
|
|
"regex-find": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'regex-find' expects two arguments, a pattern string and an input string, insufficient arguments were given")
|
|
}
|
|
pattern, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'regex-find' expects a string representing a regular expression pattern as its first argument, but was given a non-string value")
|
|
}
|
|
str, ok := a[1].(string)
|
|
if !ok {
|
|
return exception("'regex-find' expects a string as its second argument, but was given a non-string value")
|
|
}
|
|
re := regexp.MustCompile(pattern)
|
|
found := re.FindAllString(str, -1)
|
|
if found == nil {
|
|
return make([]expression, 0)
|
|
}
|
|
return StringSliceToExpressionSlice(found).([]expression)
|
|
},
|
|
"regex-replace": func(a ...expression) expression {
|
|
if len(a) < 3 {
|
|
return exception("'regex-replace' expects three arguments: a pattern string, an input string, and a replacement string, insufficient arguments were given")
|
|
}
|
|
pattern, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'regex-replace' expects a string representing a regular expression pattern as its first argument, but was given a non-string value")
|
|
}
|
|
str, ok := a[1].(string)
|
|
if !ok {
|
|
return exception("'regex-find' expects a string as its second argument, but was given a non-string value")
|
|
}
|
|
replace, ok := a[2].(string)
|
|
if !ok {
|
|
return exception("'regex-find' expects a replacement string as its third argument, but was given a non-string value")
|
|
}
|
|
|
|
re := regexp.MustCompile(pattern)
|
|
return re.ReplaceAllString(str, replace)
|
|
},
|
|
"number->string": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("insufficient number of arguments given to 'string->number'")
|
|
}
|
|
n, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'number->string' expected a number as its first argument, a non-number value was given")
|
|
}
|
|
if len(a) == 2 {
|
|
base, ok2 := a[1].(number)
|
|
if !ok2 {
|
|
return exception("'number->string' expected a number as its second argument, a non-number value was given")
|
|
}
|
|
if base != 10 {
|
|
return strconv.FormatInt(int64(n), int(base))
|
|
}
|
|
}
|
|
return strconv.FormatFloat(float64(n), 'f', -1, 64)
|
|
},
|
|
"file-create-temp": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'file-create-temp' expects a filename pattern as a string, no value was given")
|
|
}
|
|
fp, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'file-create-temp' expects a string representing a filename pattern, a non-string value was given")
|
|
}
|
|
f, err := os.CreateTemp("", fp)
|
|
if err != nil {
|
|
return exception("'file-create-temp' was unable to create the requested temporary file: " + err.Error())
|
|
}
|
|
obj := &IOHandle{f, true, "file (write-only)"}
|
|
openFiles = append(openFiles, obj)
|
|
return obj
|
|
},
|
|
"file-name": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'file-path' expects an IOHandle, no value was given")
|
|
}
|
|
ioh, ok := a[0].(*IOHandle)
|
|
if !ok {
|
|
return exception("'file-path' expects an IOHandle, a non-IOHandle value was given")
|
|
}
|
|
switch f := ioh.Obj.(type) {
|
|
case *os.File:
|
|
return f.Name()
|
|
default:
|
|
return exception("'file-path' encountered an IOHandle type that it cannot process")
|
|
}
|
|
},
|
|
"file-stat": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'file-stat' expects a filepath, no filepath was given")
|
|
}
|
|
fp, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'file-stat' expects a filepath as a string, a non-string value was given")
|
|
}
|
|
fp = ExpandedAbsFilepath(fp)
|
|
info, err := os.Stat(fp)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
return exception("'file-stat' could not stat file: " + err.Error())
|
|
}
|
|
stat := info.Sys().(*syscall.Stat_t)
|
|
u := strconv.FormatUint(uint64(stat.Uid), 10)
|
|
g := strconv.FormatUint(uint64(stat.Gid), 10)
|
|
var userString string
|
|
usr, err := user.LookupId(u)
|
|
if err != nil {
|
|
userString = ""
|
|
} else {
|
|
userString = usr.Username
|
|
}
|
|
|
|
var groupString string
|
|
group, err := user.LookupGroupId(g)
|
|
if err != nil {
|
|
groupString = ""
|
|
} else {
|
|
groupString = group.Name
|
|
}
|
|
|
|
out := make([]expression, 10)
|
|
out[0] = []expression{"name", info.Name()}
|
|
out[1] = []expression{"size", number(info.Size())}
|
|
out[2] = []expression{"mode", number(info.Mode())}
|
|
out[3] = []expression{"mod-time", number(info.ModTime().Unix())}
|
|
out[4] = []expression{"is-dir?", info.IsDir()}
|
|
out[5] = []expression{"is-symlink?", false}
|
|
out[6] = []expression{"path", fp}
|
|
out[7] = []expression{"owner", userString}
|
|
out[8] = []expression{"group", groupString}
|
|
out[9] = []expression{"modestring", strconv.FormatUint(uint64(info.Mode()), 8)}
|
|
|
|
ln, err := os.Readlink(fp)
|
|
if err == nil {
|
|
out[5] = []expression{"is-symlink?", true}
|
|
if !filepath.IsAbs(ln) {
|
|
root := fp
|
|
if !info.IsDir() {
|
|
root = filepath.Dir(fp)
|
|
}
|
|
ln = ExpandedAbsFilepath(filepath.Join(root, ln))
|
|
}
|
|
out[6] = []expression{"path", ln}
|
|
}
|
|
|
|
return out
|
|
},
|
|
"file-create": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("'file-create' expects a filepath, no filepath was given")
|
|
}
|
|
fp, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'file-create' expects a filepath as a string, a non-string value was given")
|
|
}
|
|
f, err := os.Create(fp)
|
|
if err != nil {
|
|
return exception("'file-create' encountered an error: " + err.Error())
|
|
}
|
|
obj := &IOHandle{f, true, "file (write-only)"}
|
|
openFiles = append(openFiles, obj)
|
|
return obj
|
|
},
|
|
"file-open-read": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'file-open-read' expects a filepath, no filepath was given")
|
|
}
|
|
if fp, ok := a[0].(string); ok {
|
|
f, err := os.Open(fp)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
obj := &IOHandle{f, true, "file (read-only)"}
|
|
openFiles = append(openFiles, obj)
|
|
return obj
|
|
}
|
|
return exception("'file-open-read' expects a filepath as a string, a non-string value was given")
|
|
},
|
|
"file-open-write": func(a ...expression) expression {
|
|
// (file-open-write [filepath: string] [[truncate: bool]])
|
|
if len(a) == 0 {
|
|
return exception("'file-open-write' expects a filepath, no filepath was given")
|
|
}
|
|
if fp, ok := a[0].(string); ok {
|
|
flags := os.O_CREATE | os.O_WRONLY
|
|
if len(a) >= 2 {
|
|
if b, ok := a[1].(bool); ok && b {
|
|
flags = flags | os.O_TRUNC
|
|
}
|
|
}
|
|
f, err := os.OpenFile(fp, flags, 0664)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
obj := &IOHandle{f, true, "file (write-only)"}
|
|
openFiles = append(openFiles, obj)
|
|
return obj
|
|
}
|
|
return exception("'file-write' expects a filepath as a string, a non-string value was given")
|
|
},
|
|
"file-append-to": func(a ...expression) expression {
|
|
// (file-append-to [filepath: string] [[string...]])
|
|
if len(a) < 2 {
|
|
return exception("'file-append-to' requires a filepath and at least one string")
|
|
}
|
|
if fp, ok := a[0].(string); ok {
|
|
f, err := os.OpenFile(fp, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer f.Close()
|
|
for i := range a[1:] {
|
|
f.WriteString(String(a[i+1], false))
|
|
}
|
|
return make([]expression, 0)
|
|
}
|
|
return exception("'file-append-to' expects a filepath, as a string, as its first argument")
|
|
},
|
|
"open?": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("'open?' expects an IO handle, no argument was given")
|
|
}
|
|
if h, ok := a[0].(*IOHandle); ok {
|
|
return h.Open
|
|
}
|
|
return exception("'open?' expects an IO handle")
|
|
},
|
|
"close": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("'close' expects an IO handle, no argument was given")
|
|
}
|
|
if f, ok := a[0].(*IOHandle); ok {
|
|
f.Open = false
|
|
switch o := f.Obj.(type) {
|
|
case *os.File:
|
|
o.Close()
|
|
case *net.Conn:
|
|
(*o).Close()
|
|
case *tls.Conn:
|
|
o.Close()
|
|
case *strings.Builder:
|
|
f.Open = false // duplicate here to not have a blank line
|
|
default:
|
|
return exception("'close' encountered an unsupported IO handle type")
|
|
}
|
|
} else {
|
|
return exception("'close' expects an IO handle")
|
|
}
|
|
return make([]expression, 0)
|
|
},
|
|
"path-exists?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'path-exists' expects a filepath as a string, no value was supplied")
|
|
}
|
|
switch p := a[0].(type) {
|
|
case string:
|
|
_, err := os.Stat(p)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
case symbol:
|
|
_, err := os.Stat(string(p))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
default:
|
|
return exception("'path-exists' expected a string, but a non-string value was given")
|
|
}
|
|
},
|
|
"path-abs": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'path-abs' expects a filepath as a string, no value was given")
|
|
}
|
|
p, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'path-abs' expected a filepath as a string, a non-string value was given")
|
|
}
|
|
return ExpandedAbsFilepath(p)
|
|
},
|
|
"path-is-dir?": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'path-is-dir?' expects a filepath as a string, no value was given")
|
|
}
|
|
switch p := a[0].(type) {
|
|
case string:
|
|
f, err := os.Stat(ExpandedAbsFilepath(p))
|
|
if err != nil {
|
|
return exception(strings.Replace(err.Error(), "stat ", "", -1))
|
|
}
|
|
return f.IsDir()
|
|
case symbol:
|
|
f, err := os.Stat(ExpandedAbsFilepath(string(p)))
|
|
if err != nil {
|
|
return exception(strings.Replace(err.Error(), "stat ", "", -1))
|
|
}
|
|
return f.IsDir()
|
|
default:
|
|
return exception("'path-is-dir?' expected a string, but a non-string value was given")
|
|
}
|
|
},
|
|
"path-join": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return "/"
|
|
}
|
|
strs := make([]string, len(a))
|
|
for i := range a {
|
|
v, ok := a[i].(string)
|
|
if !ok {
|
|
return exception(fmt.Sprintf("Argument %d to 'path-join' is not a string", i+1))
|
|
}
|
|
strs[i] = v
|
|
}
|
|
return filepath.Join(strs...)
|
|
},
|
|
"path-extension": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'path-extension' expects a string and, optionally, a second string; no value was given")
|
|
}
|
|
p, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'path-extension' expects a path string as its first argument, a non-string value was given")
|
|
}
|
|
if len(a) > 1 {
|
|
e, ok := a[1].(string)
|
|
if !ok {
|
|
return exception("'path-extension' expects an extension as a string as its second argument, a non-string value was given")
|
|
}
|
|
ex := filepath.Ext(p)
|
|
if ex == "" {
|
|
return p + e
|
|
}
|
|
li := strings.LastIndex(p, ex)
|
|
if li < 0 {
|
|
return p
|
|
}
|
|
return p[:li] + e
|
|
}
|
|
return filepath.Ext(p)
|
|
},
|
|
"path-base": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'path-base' expects a string, no value was given")
|
|
}
|
|
p, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'path-base' expects a path string as its first argument, a non-string value was given")
|
|
}
|
|
return filepath.Base(p)
|
|
},
|
|
"path-dir": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'path-dir' expects a string, no value was given")
|
|
}
|
|
p, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'path-dir' expects a path string as its first argument, a non-string value was given")
|
|
}
|
|
return filepath.Dir(p)
|
|
},
|
|
"path-glob": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'path-glob' expects a filepath as a string, too few values given")
|
|
}
|
|
p, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'path-glob' expects a filepath as a string, a non-string value was given")
|
|
}
|
|
g, err := BetterGlob(p)
|
|
if err != nil {
|
|
return exception("'path-glob' received an invalid filepath glob pattern as a string")
|
|
}
|
|
return StringSliceToExpressionSlice(g)
|
|
},
|
|
"hostname": func(a ...expression) expression {
|
|
hostname, err := os.Hostname()
|
|
if err != nil {
|
|
return exception("'hostname' could not retrieve the hostname: " + err.Error())
|
|
}
|
|
return hostname
|
|
},
|
|
"net-post": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return []expression{false, "'net-post' expects a url string and a string or an open io-handle containing the post body/data, too few values were given"}
|
|
}
|
|
u, err := url.Parse(String(a[0], false))
|
|
if err != nil {
|
|
return []expression{false, "'net-post' was given a url string that could not be parsed as a url"}
|
|
}
|
|
switch u.Scheme {
|
|
case "http", "https":
|
|
mime := "text/plain"
|
|
if len(a) > 2 {
|
|
mime = String(a[2], false)
|
|
}
|
|
var resp *http.Response
|
|
switch data := a[1].(type) {
|
|
case *IOHandle:
|
|
if !data.Open {
|
|
return []expression{false, "'net-post' was given a closed io-handle"}
|
|
}
|
|
switch handleObj := data.Obj.(type) {
|
|
case *os.File:
|
|
resp, err = http.Post(u.String(), mime, handleObj)
|
|
case *net.Conn:
|
|
resp, err = http.Post(u.String(), mime, *handleObj)
|
|
case net.Conn:
|
|
resp, err = http.Post(u.String(), mime, handleObj)
|
|
case *tls.Conn:
|
|
resp, err = http.Post(u.String(), mime, handleObj)
|
|
case *strings.Builder:
|
|
resp, err = http.Post(u.String(), mime, strings.NewReader(handleObj.String()))
|
|
default:
|
|
return []expression{false, "'net-post' was given an unsupported io-handle type"}
|
|
}
|
|
default:
|
|
resp, err = http.Post(u.String(), mime, strings.NewReader(String(data, false)))
|
|
}
|
|
if err != nil {
|
|
return []expression{false, fmt.Sprintf("'net-post' error: %s", err.Error())}
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return []expression{false, fmt.Sprintf("'net-get' error: %s", err.Error())}
|
|
}
|
|
return []expression{true, string(body)}
|
|
case "gemini":
|
|
u.RawQuery = url.QueryEscape(String(a[1], false))
|
|
resp, mime, err := GeminiRequest(u, 0)
|
|
if err != nil {
|
|
return []expression{false, fmt.Sprintf("'net-post' error: %s", err.Error())}
|
|
}
|
|
return []expression{true, resp, mime}
|
|
case "gopher":
|
|
if u.Port() == "" {
|
|
u.Host = u.Host + ":70"
|
|
}
|
|
conn, err := net.Dial("tcp", u.Host)
|
|
if err != nil {
|
|
return []expression{false, fmt.Sprintf("'net-get' error: %s", err.Error())}
|
|
}
|
|
defer conn.Close()
|
|
p := u.Path
|
|
if len(u.Path) < 2 {
|
|
p = fmt.Sprintf("/\t%s\r\n", a[1])
|
|
} else {
|
|
p = fmt.Sprintf("%s\t%s\r\n", p[2:], a[1])
|
|
}
|
|
_, err = conn.Write([]byte(p))
|
|
if err != nil {
|
|
return []expression{false, fmt.Sprintf("'net-get' error: %s", err.Error())}
|
|
}
|
|
resp, err := io.ReadAll(conn)
|
|
if err != nil {
|
|
return []expression{false, fmt.Sprintf("'net-get' error: %s", err.Error())}
|
|
}
|
|
return []expression{true, string(resp)}
|
|
default:
|
|
return []expression{false, "'net-post' was given a url for an unsupported scheme: " + u.Scheme + ". Consider using 'net-conn' instead"}
|
|
}
|
|
},
|
|
"net-get": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return []expression{false, "'net-get' expects a url string, too few values were given"}
|
|
}
|
|
u, err := url.Parse(String(a[0], false))
|
|
if err != nil {
|
|
return []expression{false, "'net-get' was given a url string that could not be parsed as a url"}
|
|
}
|
|
switch u.Scheme {
|
|
case "http", "https":
|
|
resp, err := http.Get(u.String())
|
|
if err != nil {
|
|
return []expression{false, fmt.Sprintf("'net-get' error: %s", err.Error())}
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return []expression{false, fmt.Sprintf("'net-get' error: %s", err.Error())}
|
|
}
|
|
return []expression{true, string(body)}
|
|
case "gemini":
|
|
resp, mime, err := GeminiRequest(u, 0)
|
|
if err != nil {
|
|
return []expression{false, fmt.Sprintf("'net-get' error: %s", err.Error())}
|
|
}
|
|
return []expression{true, resp, mime}
|
|
case "gopher":
|
|
if u.Port() == "" {
|
|
u.Host = u.Host + ":70"
|
|
}
|
|
conn, err := net.Dial("tcp", u.Host)
|
|
if err != nil {
|
|
return []expression{false, fmt.Sprintf("'net-get' error: %s", err.Error())}
|
|
}
|
|
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 {
|
|
return []expression{false, fmt.Sprintf("'net-get' error: %s", err.Error())}
|
|
}
|
|
resp, err := io.ReadAll(conn)
|
|
if err != nil {
|
|
return []expression{false, fmt.Sprintf("'net-get' error: %s", err.Error())}
|
|
}
|
|
return []expression{true, string(resp)}
|
|
default:
|
|
return []expression{false, "'net-get' was given a URL for an unsupported scheme: " + u.Scheme + ". Consider using 'net-conn' instead"}
|
|
}
|
|
},
|
|
"net-listen": func(a ...expression) expression {
|
|
if len(a) < 3 {
|
|
return exception(`'net-listen' expects a string or symbol ("tcp", "tcp4", "tcp6", "unix", or "unixpacket"), an address string, and a procedure or macro that accepts an io-handle. Too few values were given`)
|
|
}
|
|
network := strings.ToLower(String(a[0], false))
|
|
switch network {
|
|
case "tcp", "tcp4", "tcp6", "unix", "unixpacket":
|
|
break
|
|
default:
|
|
return exception(`'net-listen' expected the first value given to be a string equal to "tcp", "tcp4", "tcp6", "unix", or "unixpacket". An invalid value was given`)
|
|
}
|
|
address := String(a[1], false)
|
|
server, err := net.Listen(network, address)
|
|
if err != nil {
|
|
return exception(`'net-listen' could not listen at the given address: ` + address)
|
|
}
|
|
defer server.Close()
|
|
var callback func(conn *IOHandle)
|
|
switch f := a[2].(type) {
|
|
case func(...expression) expression:
|
|
callback = func(conn *IOHandle) {
|
|
f(IOHandle{&conn, true, "net-conn"})
|
|
}
|
|
case proc:
|
|
callback = func(conn *IOHandle) {
|
|
apply(f, []expression{conn}, "net-listen callback")
|
|
}
|
|
case macro:
|
|
callback = func(conn *IOHandle) {
|
|
apply(f, []expression{conn}, "net-listen callback")
|
|
}
|
|
default:
|
|
return exception(`'net-listen' expected a built-in, proc, or macro as its third value; an invalid value was given`)
|
|
}
|
|
if len(a) > 3 {
|
|
switch f := a[3].(type) {
|
|
case func(...expression) expression:
|
|
f(network, address)
|
|
case proc:
|
|
apply(f, []expression{network, address}, "net-listen init callback")
|
|
case macro:
|
|
apply(f, []expression{network, address}, "net-listen init callback")
|
|
}
|
|
}
|
|
for {
|
|
conn, err := server.Accept()
|
|
if err != nil {
|
|
return exception("'net-listen' encountered an error with a connection: " + err.Error())
|
|
}
|
|
go callback(&IOHandle{&conn, true, "net-conn"})
|
|
}
|
|
},
|
|
"net-conn": func(a ...expression) expression {
|
|
// (net-conn host port use-tls timeout-seconds)
|
|
// (net-conn string string bool number)
|
|
if len(a) < 2 {
|
|
return exception("'net-conn' expects a host and a port as a string, too few values given")
|
|
}
|
|
host, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'net-conn' expects a host as a string, a non-string value was given")
|
|
}
|
|
var port string
|
|
switch p := a[1].(type) {
|
|
case string:
|
|
port = p
|
|
case number:
|
|
port = strconv.Itoa(int(p))
|
|
default:
|
|
return exception("'net-conn' expects a port as a string, a non-string value was given")
|
|
}
|
|
|
|
usetls := false
|
|
if len(a) >= 3 {
|
|
t, ok := a[2].(bool)
|
|
if !ok {
|
|
return exception("'net-conn' expects a boolean value as the third argument (use-tls), a non-bool value was given")
|
|
}
|
|
usetls = t
|
|
}
|
|
|
|
timeout := -1
|
|
if len(a) >= 4 {
|
|
switch to := a[3].(type) {
|
|
case number:
|
|
timeout = int(to)
|
|
case string:
|
|
t, err := strconv.Atoi(to)
|
|
if err != nil {
|
|
return exception("'net-conn' was given a timeout string that does not cast to an integer")
|
|
}
|
|
timeout = t
|
|
default:
|
|
return exception("'net-conn' expects a string or number value representing a timeout, in seconds; a non-string non-number value was given ")
|
|
}
|
|
}
|
|
var conn net.Conn
|
|
var tlsconn *tls.Conn
|
|
var err error
|
|
addr := fmt.Sprintf("%s:%s", host, port)
|
|
if usetls {
|
|
conf := &tls.Config{InsecureSkipVerify: true}
|
|
if timeout > 0 {
|
|
tlsconn, err = tls.DialWithDialer(&net.Dialer{Timeout: time.Duration(timeout) * time.Second}, "tcp", addr, conf)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
} else {
|
|
tlsconn, err = tls.Dial("tcp", addr, conf)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
}
|
|
} else {
|
|
if timeout > 0 {
|
|
conn, err = net.DialTimeout("tcp", addr, time.Duration(timeout)*time.Second)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
} else {
|
|
conn, err = net.Dial("tcp", addr)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
// based on the tls value use tls or not, same with the timeout value for adding a timeout
|
|
var handle *IOHandle
|
|
if usetls {
|
|
handle = &IOHandle{tlsconn, true, "net-conn (tls)"}
|
|
} else {
|
|
handle = &IOHandle{&conn, true, "net-conn"}
|
|
}
|
|
openFiles = append(openFiles, handle)
|
|
return handle
|
|
},
|
|
"url-host": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'url-host' expects a url as a string, no value was given")
|
|
}
|
|
rawURL, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'url-host' expects a url as a string, a non-string value was given")
|
|
}
|
|
u, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if len(a) >= 2 {
|
|
p := ":" + u.Port()
|
|
if p == ":" {
|
|
p = ""
|
|
}
|
|
switch s := a[1].(type) {
|
|
case string:
|
|
u.Host = s + p
|
|
case symbol:
|
|
u.Host = string(s) + p
|
|
default:
|
|
return exception("'url-host' expects either a string or a symbol as its second argument, a non-string non-symbol value was given")
|
|
}
|
|
return u.String()
|
|
}
|
|
return u.Hostname()
|
|
},
|
|
"url-port": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'url-port' expects a url as a string, no value was given")
|
|
}
|
|
rawURL, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'url-port' expects a url as a string, a non-string value was given")
|
|
}
|
|
u, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if len(a) >= 2 {
|
|
switch s := a[1].(type) {
|
|
case string:
|
|
u.Host = u.Hostname() + ":" + s
|
|
case symbol:
|
|
u.Host = u.Hostname() + ":" + string(s)
|
|
case number:
|
|
u.Host = u.Hostname() + ":" + strconv.Itoa(int(s))
|
|
default:
|
|
return exception("'url-port' expects either a string or a symbol as its second argument, a non-string non-symbol value was given")
|
|
}
|
|
return u.String()
|
|
}
|
|
return u.Port()
|
|
},
|
|
"url-query": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'url-query' expects a url as a string, no value was given")
|
|
}
|
|
rawURL, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'url-query' expects a url as a string, a non-string value was given")
|
|
}
|
|
u, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if len(a) >= 2 {
|
|
switch s := a[1].(type) {
|
|
case string:
|
|
u.RawQuery = s
|
|
case symbol:
|
|
u.RawQuery = string(s)
|
|
default:
|
|
return exception("'url-query' expects either a string or a symbol as its second argument, a non-string non-symbol value was given")
|
|
}
|
|
return u.String()
|
|
}
|
|
return u.RawQuery
|
|
},
|
|
"url-path": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'url-path' expects a url as a string, no value was given")
|
|
}
|
|
rawURL, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'url-path' expects a url as a string, a non-string value was given")
|
|
}
|
|
u, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if len(a) >= 2 {
|
|
switch s := a[1].(type) {
|
|
case string:
|
|
u.Path = s
|
|
case symbol:
|
|
u.Path = string(s)
|
|
default:
|
|
return exception("'url-path' expects either a string or a symbol as its second argument, a non-string non-symbol value was given")
|
|
}
|
|
return u.String()
|
|
}
|
|
return u.EscapedPath()
|
|
},
|
|
"url-scheme": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'url-scheme' expects a url as a string, no value was given")
|
|
}
|
|
rawURL, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'url-scheme' expects a url as a string, a non-string value was given")
|
|
}
|
|
u, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if len(a) >= 2 {
|
|
switch s := a[1].(type) {
|
|
case string:
|
|
u.Scheme = s
|
|
case symbol:
|
|
u.Scheme = string(s)
|
|
default:
|
|
return exception("'url-scheme' expects either a string or a symbol as its second argument, a non-string non-symbol value was given")
|
|
}
|
|
return u.String()
|
|
}
|
|
return u.Scheme
|
|
},
|
|
"license": func(a ...expression) expression {
|
|
SysoutPrint(licenseText, Sysout)
|
|
return make([]expression, 0)
|
|
},
|
|
"chdir": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'chdir' expects a path as a string, no value was given")
|
|
}
|
|
p, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'chdir' expects a string; a non-string value was given")
|
|
}
|
|
err := os.Chdir(ExpandedAbsFilepath(p))
|
|
if err != nil {
|
|
return exception(err.Error())
|
|
}
|
|
return make([]expression, 0)
|
|
},
|
|
"chmod": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'chmod' expects a path as a string and a file mode as a number, no value was given")
|
|
}
|
|
p, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'chdir' expects a string; a non-string value was given")
|
|
}
|
|
n, ok := a[1].(number)
|
|
err := os.Chmod(ExpandedAbsFilepath(p), os.FileMode(n))
|
|
if err != nil {
|
|
return exception(err.Error())
|
|
}
|
|
return make([]expression, 0)
|
|
},
|
|
"env": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
e := make([]expression, 0, 20)
|
|
env := os.Environ()
|
|
for _, v := range env {
|
|
sp := strings.Split(v, "=")
|
|
if len(sp) < 2 {
|
|
sp = append(sp, "")
|
|
}
|
|
e = append(e, []expression{sp[0], sp[1]})
|
|
}
|
|
return e
|
|
}
|
|
if len(a) == 1 {
|
|
switch i := a[0].(type) {
|
|
case string:
|
|
return os.Getenv(i)
|
|
default:
|
|
return exception("Invalid argument to single argument 'env' call, was not given a string")
|
|
}
|
|
}
|
|
variable, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("Invalid argument to dual argument 'env' call, was not given a string as the first argument")
|
|
}
|
|
err := os.Setenv(variable, String(a[1], false))
|
|
if err != nil {
|
|
return exception(err.Error())
|
|
}
|
|
return make([]expression, 0)
|
|
},
|
|
"mkdir": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'mkdir' expects a path string and a number representing file permissions and optionally a bool; no value was given")
|
|
}
|
|
path, ok1 := a[0].(string)
|
|
if !ok1 {
|
|
return exception("'mkdir' expects a path string as its first argument, a non-string value was given")
|
|
}
|
|
perms, ok2 := a[1].(number)
|
|
if !ok2 {
|
|
return exception("'mkdir' expects a number representing a file permissions setting as its second argument, a non-number value was given")
|
|
}
|
|
prmInt := os.FileMode(perms)
|
|
|
|
var ok3, mkdirAll bool
|
|
if len(a) > 2 {
|
|
mkdirAll, ok3 = a[2].(bool)
|
|
if !ok3 {
|
|
return exception("'mkdir' expects a bool as its third argument, a non-bool value was given")
|
|
}
|
|
}
|
|
|
|
var err error
|
|
if mkdirAll {
|
|
err = os.MkdirAll(ExpandedAbsFilepath(path), prmInt)
|
|
} else {
|
|
err = os.Mkdir(ExpandedAbsFilepath(path), prmInt)
|
|
}
|
|
if err != nil {
|
|
return exception("'mkdir' could not create directories for the given path with the given permissions: " + err.Error())
|
|
}
|
|
|
|
return make([]expression, 0)
|
|
},
|
|
"rm": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'rm' expects a path string and, optionally, a bool; no value was given")
|
|
}
|
|
path, ok1 := a[0].(string)
|
|
if !ok1 {
|
|
return exception("'rm' expects a path string as its first argument, a non-string value was given")
|
|
}
|
|
|
|
var ok2, mkdirAll bool
|
|
if len(a) > 1 {
|
|
mkdirAll, ok2 = a[1].(bool)
|
|
if !ok2 {
|
|
return exception("'rm' expects a bool as its second argument, a non-bool value was given")
|
|
}
|
|
}
|
|
|
|
var err error
|
|
if mkdirAll {
|
|
err = os.RemoveAll(ExpandedAbsFilepath(path))
|
|
} else {
|
|
err = os.Remove(ExpandedAbsFilepath(path))
|
|
}
|
|
if err != nil {
|
|
return exception("'mkdir' could not create directories for the given path with the given permissions: " + err.Error())
|
|
}
|
|
|
|
return make([]expression, 0)
|
|
},
|
|
"pwd": func(a ...expression) expression {
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
return exception("'pwd' could not retrieve the current working directory: " + err.Error())
|
|
}
|
|
return wd
|
|
},
|
|
"mv": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'mv' expected two path strings (from and to), but was given an insufficient number of arguments")
|
|
}
|
|
from, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'mv' expected a path string as its first argument, a non-string value was given")
|
|
}
|
|
to, ok2 := a[1].(string)
|
|
if !ok2 {
|
|
return exception("'mv' expected a path string as its second argument, a non-string value was given")
|
|
}
|
|
err := os.Rename(from, to)
|
|
if err != nil {
|
|
return exception("'mv' could not perform the requested action: " + err.Error())
|
|
}
|
|
return make([]expression, 0)
|
|
},
|
|
"subprocess": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'subprocess' expects a list containing the process to start and any arguments, as strings. It can also, optionally, take 1 or 2 file handles to write Stdout and Stderr to. No arguments were given.")
|
|
}
|
|
processList, ok := a[0].([]expression)
|
|
if !ok {
|
|
return exception("'subprocess' expects a list containing the process to start and any arguments as separate strings in the list. A non-list was given.")
|
|
}
|
|
process := make([]string, len(processList))
|
|
for i := range processList {
|
|
if s, ok := processList[i].(string); ok {
|
|
process[i] = s
|
|
} else {
|
|
return exception("a list passed to 'subprocess' contained the non-string value: " + String(processList[i], true))
|
|
}
|
|
}
|
|
|
|
var o, e, i *IOHandle
|
|
if len(a) > 3 {
|
|
i1, i1ok := a[3].(*IOHandle)
|
|
if !i1ok {
|
|
if b, ok := a[3].(bool); !ok || (ok && b) {
|
|
return exception("'subprocess' was given an input redirection that is not an IOHandle or the boolean '#f'")
|
|
}
|
|
} else if !i1.Open {
|
|
return exception("'subprocess' was given an input redirection IOHandle that is already closed")
|
|
}
|
|
i = i1
|
|
}
|
|
if len(a) > 2 {
|
|
e1, e1ok := a[2].(*IOHandle)
|
|
if !e1ok {
|
|
if b, ok := a[2].(bool); !ok || (ok && b) {
|
|
return exception("'subprocess' was given an error redirection that is not an IOHandle or the boolean '#f'")
|
|
}
|
|
} else if !e1.Open {
|
|
return exception("'subprocess' was given an error redirection IOHandle that is already closed")
|
|
}
|
|
e = e1
|
|
}
|
|
if len(a) > 1 {
|
|
o1, o1ok := a[1].(*IOHandle)
|
|
if !o1ok {
|
|
if b, ok := a[1].(bool); !ok || (ok && b) {
|
|
return exception("'subprocess' was given an output redirection that is not an IOHandle or the boolean '#f'")
|
|
}
|
|
} else if !o1.Open {
|
|
return exception("'subprocess' was given an output redirection IOHandle that is already closed")
|
|
}
|
|
o = o1
|
|
}
|
|
cmd := exec.Command(process[0], process[1:]...)
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if o != nil {
|
|
switch io := o.Obj.(type) {
|
|
case *os.File:
|
|
cmd.Stdout = io
|
|
case *net.Conn:
|
|
cmd.Stdout = (*io)
|
|
case *tls.Conn:
|
|
cmd.Stdout = io
|
|
case *strings.Builder:
|
|
cmd.Stdout = io
|
|
default:
|
|
return exception("'subprocess' was given an output redirection IOHandle of an unknown type")
|
|
}
|
|
}
|
|
if e != nil {
|
|
switch io := e.Obj.(type) {
|
|
case *os.File:
|
|
cmd.Stderr = io
|
|
case *net.Conn:
|
|
cmd.Stderr = (*io)
|
|
case *tls.Conn:
|
|
cmd.Stderr = io
|
|
case *strings.Builder:
|
|
cmd.Stderr = io
|
|
default:
|
|
return exception("'subprocess' was given an error redirection IOHandle of an unknown type")
|
|
}
|
|
}
|
|
if i != nil {
|
|
switch io := i.Obj.(type) {
|
|
case *os.File:
|
|
cmd.Stdin = io
|
|
case *net.Conn:
|
|
cmd.Stdin = (*io)
|
|
case *tls.Conn:
|
|
cmd.Stdin = io
|
|
case *strings.Builder:
|
|
cmd.Stdin = strings.NewReader(io.String())
|
|
default:
|
|
return exception("'subprocess' was given an input redirection IOHandle of an unknown type")
|
|
}
|
|
}
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
|
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
|
return number(status.ExitStatus())
|
|
}
|
|
} else {
|
|
return exception("'subprocess' encountered an error running the given process and the process could not be run")
|
|
}
|
|
}
|
|
return number(0)
|
|
},
|
|
"term-size": func(a ...expression) expression {
|
|
cols, rows := termios.GetWindowSize()
|
|
return []expression{number(cols), number(rows)}
|
|
},
|
|
"term-restore": func(a ...expression) expression {
|
|
termios.Restore()
|
|
return make([]expression, 0)
|
|
},
|
|
"term-char-mode": func(a ...expression) expression {
|
|
termios.SetCharMode()
|
|
return make([]expression, 0)
|
|
},
|
|
"term-raw-mode": func(a ...expression) expression {
|
|
termios.SetRawMode()
|
|
return make([]expression, 0)
|
|
},
|
|
"term-sane-mode": func(a ...expression) expression {
|
|
termios.SetSaneMode()
|
|
return make([]expression, 0)
|
|
},
|
|
"term-cooked-mode": func(a ...expression) expression {
|
|
termios.SetCookedMode()
|
|
return make([]expression, 0)
|
|
},
|
|
"timestamp": func(a ...expression) expression {
|
|
return number(time.Now().Unix())
|
|
},
|
|
"date": func(a ...expression) expression {
|
|
// (date [[format: string]])
|
|
if len(a) == 0 {
|
|
return time.Now().Format("Mon Jan 2 15:04:05 -0700 MST 2006")
|
|
}
|
|
layout, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'date' was given a non-string layout value")
|
|
}
|
|
return time.Now().Format(createTimeFormatString(layout))
|
|
},
|
|
"timestamp->date": func(a ...expression) expression {
|
|
// (timestamp->date [timestamp: string] [[layout: string]])
|
|
if len(a) >= 1 {
|
|
layout := "Mon Jan 2 15:04:05 -0700 MST 2006"
|
|
|
|
var dateint int64
|
|
switch d := a[0].(type) {
|
|
case string:
|
|
di, err := strconv.ParseInt(d, 10, 64)
|
|
if err != nil {
|
|
return exception("'timestamp->date' was given an invalid timestamp string, it could not be converted to a number")
|
|
}
|
|
dateint = di
|
|
case number:
|
|
dateint = int64(d)
|
|
default:
|
|
return exception("'timestamp->date' was given a non-string non-number value as a timestamp")
|
|
}
|
|
|
|
if len(a) >= 2 {
|
|
var ok1 bool
|
|
layout, ok1 = a[1].(string)
|
|
if !ok1 {
|
|
return exception("'timestamp->date' was given a non-string layout value")
|
|
}
|
|
layout = createTimeFormatString(layout)
|
|
}
|
|
|
|
t := time.Unix(dateint, 0)
|
|
return t.Local().Format(layout)
|
|
}
|
|
return exception("'timestamp->date' received an insufficient argument count, expected 1 or 2 arguments")
|
|
},
|
|
"date-format": func(a ...expression) expression {
|
|
// (date-format [input-format: string] [input-date: string] [output-format: string])
|
|
if len(a) == 3 {
|
|
// datestring->datestring
|
|
layout1, ok1 := a[0].(string)
|
|
datestring, ok2 := a[1].(string)
|
|
layout2, ok3 := a[2].(string)
|
|
if !ok1 || !ok2 || !ok3 {
|
|
return exception("'date-format' was given a non-string argument, it expects strings")
|
|
}
|
|
t, err := time.ParseInLocation(createTimeFormatString(layout1), datestring, time.Now().Location())
|
|
if err != nil {
|
|
return exception("'date-format' could not parse the input date to a time value based on the given layout string")
|
|
}
|
|
return t.Format(createTimeFormatString(layout2))
|
|
}
|
|
return exception("'date-format' received an unexpected number of arguments")
|
|
},
|
|
"date->timestamp": func(a ...expression) expression {
|
|
// (date->timestamp [date: string] [format: string])
|
|
if len(a) == 2 {
|
|
layout1, ok1 := a[1].(string)
|
|
datestring, ok2 := a[0].(string)
|
|
if !ok1 || !ok2 {
|
|
return exception("'date->timestamp' was given a non-string argument, it expects strings")
|
|
}
|
|
t, err := time.ParseInLocation(createTimeFormatString(layout1), datestring, time.Now().Location())
|
|
if err != nil {
|
|
return exception("'date->timestamp' could not parse the input date to a time value based on the given layout string")
|
|
}
|
|
return number(t.Unix())
|
|
}
|
|
return exception("'date' received an unexpected number of arguments")
|
|
},
|
|
"date-default-format": func(a ...expression) expression {
|
|
return "%w %f %d %g:%I:%S %O %Z %Y"
|
|
},
|
|
"sleep": func(a ...expression) expression {
|
|
// (sleep [ms: number])
|
|
if len(a) > 0 {
|
|
ms, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'sleep' was given a non-number value")
|
|
}
|
|
time.Sleep(time.Duration(ms) * time.Millisecond)
|
|
}
|
|
return make([]expression, 0)
|
|
},
|
|
"rand-seed": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'rand-seed' expected a number, no value was given")
|
|
}
|
|
seed, ok := a[0].(number)
|
|
if !ok {
|
|
return exception("'rand-seed' expected a number, a non-number value was given")
|
|
}
|
|
rand.Seed(int64(seed))
|
|
return true
|
|
},
|
|
"rand": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
// equal to: (rand 1 0)
|
|
return number(rand.Float64())
|
|
}
|
|
var max, min number
|
|
var ok bool
|
|
if len(a) >= 1 {
|
|
// equal to: (rand [max] 0)
|
|
max, ok = a[0].(number)
|
|
if !ok {
|
|
return exception("'rand' expected a number as its first argument, a non-number value was given")
|
|
}
|
|
}
|
|
if len(a) == 1 {
|
|
return number(rand.Float64() * float64(max))
|
|
}
|
|
// equal to: (rand [max] [min])
|
|
min, ok = a[1].(number)
|
|
if !ok {
|
|
return exception("'rand' expected a number as its second argument, a non-number value was given")
|
|
}
|
|
return number(rand.Float64()*(float64(max)-float64(min)) + float64(min))
|
|
},
|
|
"string->md5": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'string->md5' expects a string, no value was given")
|
|
}
|
|
s, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'string->md5' expected a string as its first argument, a non-string argument was given")
|
|
}
|
|
return fmt.Sprintf("%x", md5.Sum([]byte(s)))
|
|
},
|
|
"string->sha256": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'string->sha256' expects a string, no value was given")
|
|
}
|
|
s, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'string->sha256' expected a string as its first argument, a non-string argument was given")
|
|
}
|
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(s)))
|
|
},
|
|
"ns-create": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'ns-create' expects a string, no argument was given")
|
|
}
|
|
s := String(a[0], false)
|
|
if strings.ContainsAny(s, " ()\"#':") {
|
|
return exception("'ns-create' cannot create an env with a name containing any of the following characters: \033[1m:'( )\"#\033[0m")
|
|
}
|
|
if _, ok := namespaces[s]; ok {
|
|
return exception("'ns-create' cannot create an env with a name that is already in use")
|
|
}
|
|
|
|
namespaces[s] = env{vars(map[symbol]expression{}), &globalenv}
|
|
// Add magic string (slope custom env)
|
|
namespaces[s].vars[magicCustomEnv] = true
|
|
|
|
return true
|
|
},
|
|
"ns-delete": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return exception("'ns-delete' expects a string representing an env name and, optionally, a string representing a symbol in the given env. No argument was given")
|
|
}
|
|
s := String(a[0], false)
|
|
if _, ok := namespaces[s]; ok {
|
|
if _, ok := namespaces[s].vars[magicCustomEnv]; ok {
|
|
if len(a) > 1 {
|
|
delete(namespaces[s].vars, symbol(String(a[1], false)))
|
|
} else {
|
|
delete(namespaces, s)
|
|
}
|
|
return true
|
|
} else {
|
|
return exception("'ns-delete' cannot delete loaded environments, only custom environments made with 'ns-create'")
|
|
}
|
|
}
|
|
return exception("'ns-delete' could not find the env in question")
|
|
},
|
|
"ns-list": func(a ...expression) expression {
|
|
if len(a) > 0 {
|
|
s := String(a[0], false)
|
|
if ns, ok := namespaces[s]; ok {
|
|
if _, magicPresent := ns.vars[magicCustomEnv]; magicPresent {
|
|
symbols := make([]expression, 0, 5)
|
|
for k, _ := range ns.vars {
|
|
if k == magicCustomEnv {
|
|
continue
|
|
}
|
|
symbols = append(symbols, string(k))
|
|
}
|
|
return symbols
|
|
} else {
|
|
return exception("'ns-list' cannot list loaded environments, only custom environments made with 'ns-create'")
|
|
}
|
|
} else {
|
|
return exception("'ns-list' could not find the requested env")
|
|
}
|
|
} else {
|
|
envs := make([]expression, 0, 5)
|
|
for k, v := range namespaces {
|
|
if _, ok := v.vars[magicCustomEnv]; ok {
|
|
envs = append(envs, k)
|
|
}
|
|
}
|
|
return envs
|
|
}
|
|
},
|
|
"ns-define": func(a ...expression) expression {
|
|
if len(a) < 3 {
|
|
return exception("'ns-define' expects three values: the env name, the symbol name, and a value. Insufficient arguments were given")
|
|
}
|
|
envS := String(a[0], false)
|
|
symS := String(a[1], false) // FIXME this should ensure string or symbol and not coerce
|
|
if _, ok := namespaces[envS]; ok {
|
|
if _, ok := namespaces[envS].vars[magicCustomEnv]; ok {
|
|
namespaces[envS].vars[symbol(symS)] = a[2]
|
|
return a[2]
|
|
} else {
|
|
return exception("'ns-define' can only define new variables for custom environments made with 'ns-create'")
|
|
}
|
|
}
|
|
return exception("'ns-define' could not find the env in question")
|
|
},
|
|
"ns->string": func(a ...expression) expression {
|
|
if len(a) < 1 {
|
|
return exception("'ns->string' requires the name of an env, no arguments were given")
|
|
}
|
|
s := String(a[0], false)
|
|
var buf strings.Builder
|
|
ns, ok := namespaces[s]
|
|
if !ok {
|
|
return exception("'ns->string' could not find the requested env")
|
|
}
|
|
if _, magicPresent := ns.vars[magicCustomEnv]; magicPresent {
|
|
buf.WriteString(";;; ")
|
|
buf.WriteString(s)
|
|
buf.WriteString("\n;; Exported via ns->string:\n\n")
|
|
buf.WriteString(strings.Join(ExpressionSliceToStringSlice(a[1:]), "\n\n"))
|
|
if len(a[1:]) > 0 {
|
|
buf.WriteString("\n\n")
|
|
}
|
|
for k, v := range ns.vars {
|
|
if k == magicCustomEnv {
|
|
continue
|
|
}
|
|
buf.WriteString("(define ")
|
|
buf.WriteString(string(k))
|
|
buf.WriteRune(' ')
|
|
buf.WriteString(strings.ReplaceAll(String(v, true), fmt.Sprintf("%s::", s), ""))
|
|
buf.WriteString(")\n\n")
|
|
}
|
|
return buf.String()
|
|
}
|
|
return exception("'ns->string' cannot export loaded environments, only custom environments made with 'ns-create'")
|
|
},
|
|
"coeval": func(a ...expression) expression {
|
|
// (coeval [proc [args]]...)
|
|
// Takes a variable number of args. All args should
|
|
// be a list containing a proc and a list of args to
|
|
// that proc
|
|
//
|
|
// Use apply instead of eval (which was used in the special form)
|
|
// Pass 'coeval - arg (#)' as the name for apply
|
|
if len(a) == 0 {
|
|
return exception("'coeval' expects at least one argument (a list containing a procedure and a list of arguments to the procedure)")
|
|
}
|
|
for i := range a {
|
|
if list, ok := a[i].([]expression); ok {
|
|
if len(list) == 0 {
|
|
return exception("'coeval' expects lists with a proc and a list of arguments, an empty list was given")
|
|
}
|
|
_, procOk := list[0].(proc)
|
|
_, funcOk := list[0].(func(...expression) expression)
|
|
if !procOk && !funcOk {
|
|
return exception("'The first item in a list given to 'coeval' should be a procedure, a non-procedure value was given")
|
|
}
|
|
} else {
|
|
return exception("'coeval' expects its arguments to be lists, a non-list value was given")
|
|
}
|
|
}
|
|
// Validate args here before moving on
|
|
wg := new(sync.WaitGroup)
|
|
wg.Add(len(a))
|
|
for i := range a {
|
|
input := a[i].([]expression)
|
|
var list []expression
|
|
if len(input) == 1 {
|
|
list = make([]expression, 0)
|
|
} else {
|
|
switch item := input[1].(type) {
|
|
case []expression:
|
|
list = item
|
|
default:
|
|
list = []expression{item}
|
|
}
|
|
}
|
|
go func(w *sync.WaitGroup, p expression, args []expression, name expression) {
|
|
defer w.Done()
|
|
apply(p, args, name)
|
|
}(wg, input[0], list, fmt.Sprintf("Coeval procedure argument %d", i))
|
|
}
|
|
wg.Wait()
|
|
return make([]expression, 0)
|
|
},
|
|
}
|