forked from slope-lang/slope
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2423 lines
67 KiB
2423 lines
67 KiB
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/md5"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"math"
|
|
"math/rand"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"git.rawtext.club/sloum/slope/termios"
|
|
)
|
|
|
|
var stdLibrary = vars{
|
|
"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
|
|
},
|
|
"!": 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"
|
|
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) == 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
|
|
},
|
|
"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) < 2 {
|
|
return exception("'equal?' expected, but did not receive, two values")
|
|
}
|
|
return reflect.DeepEqual(a[0], a[1])
|
|
},
|
|
"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": 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 {
|
|
l[1] = a[2]
|
|
list[x] = l
|
|
return list
|
|
}
|
|
return l[1]
|
|
}
|
|
}
|
|
if len(a) >= 3 {
|
|
list = append(list, []expression{a[1], a[2]})
|
|
return list
|
|
}
|
|
return exception("'assoc' could not find the key value " + 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
|
|
}
|
|
},
|
|
"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
|
|
}
|
|
},
|
|
"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 {
|
|
if len(a) < 2 {
|
|
return exception("'string-index-of' expects a string and a number; insufficient arguments were given")
|
|
}
|
|
haystack, ok := a[0].(string)
|
|
if !ok {
|
|
return exception("'string-ref' expects a string as its first argument; a non-string value was given")
|
|
}
|
|
index, ok := a[1].(number)
|
|
if !ok {
|
|
return exception("'string-ref' expects a number as its first argument; a non-number value was given")
|
|
}
|
|
i := int(index)
|
|
if i >= len(haystack) || i < 0 {
|
|
return exception("'string-ref' was given a reference index that is out of bounds for the given string")
|
|
}
|
|
return string(haystack[int(index)])
|
|
},
|
|
"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)
|
|
},
|
|
"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)
|
|
},
|
|
"append": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
return make([]expression, 0)
|
|
}
|
|
mainList, ok := a[0].([]expression)
|
|
if !ok {
|
|
return exception("'append' expects argument 1 to be a list, a non-list was given")
|
|
}
|
|
for _, v := range a[1:] {
|
|
switch i := v.(type) {
|
|
case []expression:
|
|
mainList = append(mainList, i...)
|
|
default:
|
|
mainList = append(mainList, i)
|
|
}
|
|
}
|
|
return mainList
|
|
},
|
|
"list": eval(Parse("(lambda z z)").([]expression)[0], &globalenv),
|
|
"list-ref": func(a ...expression) expression {
|
|
if len(a) < 2 {
|
|
return exception("'list-ref' expects 2 arguments, a list and a number, insufficient arguments were given")
|
|
}
|
|
list, ok := a[0].([]expression)
|
|
if !ok {
|
|
return exception("'list-ref' expects argument one to be a list, a non-list argument was given")
|
|
}
|
|
index, ok := a[1].(number)
|
|
if !ok {
|
|
return exception("'list-ref' expects argument two to be a whole number, a non-number argument was given")
|
|
}
|
|
if int(index) >= len(list) {
|
|
return exception("the list reference value is larger than the number of items in the list given to 'list-ref'")
|
|
}
|
|
if len(a) >= 3 {
|
|
list[int(index)] = a[2]
|
|
return list
|
|
}
|
|
return list[int(index)]
|
|
},
|
|
"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
|
|
}
|
|
},
|
|
"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)))
|
|
}
|
|
return out
|
|
default:
|
|
return exception("map expects a procedure followed by at least one list")
|
|
}
|
|
},
|
|
"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))
|
|
}
|
|
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))
|
|
}
|
|
fmt.Print(out.String())
|
|
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')
|
|
}
|
|
}
|
|
fmt.Print(out.String())
|
|
return make([]expression, 0)
|
|
},
|
|
"write": func(a ...expression) expression {
|
|
if len(a) == 1 {
|
|
fmt.Print(String(a[0], false))
|
|
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-raw": func(a ...expression) expression {
|
|
if len(a) == 1 {
|
|
fmt.Print(String(a[0], true))
|
|
} 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 {
|
|
reader := bufio.NewReader(os.Stdin)
|
|
text, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return text[:len(text)-1]
|
|
} 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-lines": func(a ...expression) expression {
|
|
if len(a) == 0 {
|
|
reader := bufio.NewReader(os.Stdin)
|
|
text, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
return exception("'read-all-lines' could not read from the given IOHandle (Stdin)")
|
|
}
|
|
return strings.SplitN(text[:len(text)-1], "\n", -1)
|
|
} else {
|
|
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' 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 symbol("EOF")
|
|
}
|
|
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 symbol("EOF")
|
|
}
|
|
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 symbol("EOF")
|
|
}
|
|
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 {
|
|
reader := bufio.NewReader(os.Stdin)
|
|
text, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return text[:len(text)-1]
|
|
} else {
|
|
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")
|
|
}
|
|
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] == '\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 symbol("EOF")
|
|
} 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 symbol("EOF")
|
|
} else if err != nil {
|
|
break
|
|
}
|
|
}
|
|
return o.String()
|
|
case *tls.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 symbol("EOF")
|
|
} 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 {
|
|
fmt.Print("\r\n")
|
|
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
|
|
},
|
|
"substring": func(a ...expression) expression {
|
|
if len(a) < 3 {
|
|
return exception("insufficient number of arguments given to 'substring'")
|
|
}
|
|
var s string
|
|
var n1, n2 number
|
|
var ok bool
|
|
if s, ok = a[0].(string); !ok {
|
|
return exception("'substring' expects a string as its first argument")
|
|
}
|
|
if n1, ok = a[1].(number); !ok {
|
|
return exception("'substring' expects a number as its second argument")
|
|
}
|
|
|
|
if n2, ok = a[2].(number); !ok {
|
|
return exception("'substring' expects a number as its second argument")
|
|
}
|
|
|
|
if n1 < 0 || n1 >= n2 {
|
|
return exception("start position of 'substring' is out of range")
|
|
}
|
|
|
|
if int(n2) > len(s) {
|
|
return exception("end position of 'substring' is out of range")
|
|
}
|
|
|
|
return s[int(n1):int(n2)]
|
|
},
|
|
"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 {
|
|
if len(a) == 0 {
|
|
return exception("insufficient number of arguments given to 'string-append'")
|
|
}
|
|
var out strings.Builder
|
|
for i := range a {
|
|
out.WriteString(String(a[i], false))
|
|
}
|
|
return out.String()
|
|
},
|
|
"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-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")
|
|
}
|
|
info, err := os.Stat(ExpandedAbsFilepath(fp))
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
return exception("'file-stat' could not stat file: " + err.Error())
|
|
}
|
|
|
|
out := make([]expression, 7)
|
|
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}
|
|
|
|
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[0].(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+1] + 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 := filepath.Glob(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-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-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.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-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.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.Query()
|
|
},
|
|
"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 {
|
|
fmt.Println(licenseText)
|
|
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([]exception, 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([]exception, 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 *IOHandle
|
|
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")
|
|
}
|
|
}
|
|
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 {
|
|
fmt.Println(a)
|
|
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")
|
|
}
|
|
fmt.Println(layout1)
|
|
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": func(a ...expression) expression {
|
|
rand.Seed(time.Now().UnixNano())
|
|
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)))
|
|
},
|
|
}
|
|
|
|
/*
|
|
TODO:
|
|
|
|
Number based:
|
|
[ ] Adjust >, >=, <, <= to accept any number args and require all to be sequenced correctly for the given operator
|
|
[ ] modulo
|
|
[ ] remainder
|
|
[ ] quotient
|
|
[ ] expt
|
|
[ ] sin
|
|
[ ] cos
|
|
[ ] tan
|
|
[ ] atan
|
|
[ ] sqrt
|
|
|
|
IO Based:
|
|
[ ] file-seek
|
|
|
|
Net Based:
|
|
[ ] http-get
|
|
[ ] http-post
|
|
|
|
System Based:
|
|
[ ] eval
|
|
|
|
*/
|