2021-07-26 05:31:22 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"fmt"
|
2021-07-27 03:19:59 +00:00
|
|
|
"io"
|
2021-07-27 22:35:00 +00:00
|
|
|
"io/ioutil"
|
2021-07-26 05:31:22 +00:00
|
|
|
"math"
|
2021-07-27 22:35:00 +00:00
|
|
|
"net"
|
2021-07-26 05:31:22 +00:00
|
|
|
"os"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
var stdLibrary = vars{
|
|
|
|
"PI": number(math.Pi),
|
|
|
|
"E": number(math.E),
|
|
|
|
"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
|
|
|
|
},
|
|
|
|
"abs": func(a ...expression) expression {
|
|
|
|
if len(a) < 1 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'abs' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
i, ok := a[0].(number)
|
|
|
|
if !ok {
|
|
|
|
panic("'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 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'floor' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
i, ok := a[0].(number)
|
|
|
|
if !ok {
|
|
|
|
panic("'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 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'ceil' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
i, ok := a[0].(number)
|
|
|
|
if !ok {
|
|
|
|
panic("'ceil' expected a number, but a non-number value was received")
|
|
|
|
}
|
|
|
|
return number(math.Ceil(float64(i)))
|
|
|
|
},
|
|
|
|
"round": func(a ...expression) expression {
|
|
|
|
if len(a) < 1 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'round' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
decimals := number(0.0)
|
|
|
|
if len(a) >= 2 {
|
|
|
|
if dec, ok := a[1].(number); ok {
|
|
|
|
decimals = dec
|
|
|
|
}
|
|
|
|
}
|
|
|
|
num, ok := a[0].(number)
|
|
|
|
if !ok {
|
|
|
|
panic("'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 {
|
|
|
|
panic("'+' expected a number, but a non-number value was received")
|
|
|
|
}
|
|
|
|
for _, i := range a[1:] {
|
|
|
|
x, ok := i.(number)
|
|
|
|
if !ok {
|
|
|
|
panic("'+' expected a number, but a non-number value was received")
|
|
|
|
}
|
|
|
|
v += x
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
},
|
|
|
|
"-": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'-' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
v, ok := a[0].(number)
|
|
|
|
if !ok {
|
|
|
|
panic("'-' 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 {
|
|
|
|
panic("'-' 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 {
|
|
|
|
panic("'-' 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 {
|
|
|
|
panic("'*' expected a number, but a non-number value was received")
|
|
|
|
}
|
|
|
|
v *= x
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
},
|
|
|
|
"/": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'/' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
v, ok := a[0].(number)
|
|
|
|
if !ok {
|
|
|
|
panic("'/' expected a number, but a non-number value was received")
|
|
|
|
}
|
|
|
|
for _, i := range a[1:] {
|
|
|
|
x, ok := i.(number)
|
|
|
|
if !ok {
|
|
|
|
panic("'/' expected a number, but a non-number value was received")
|
|
|
|
}
|
|
|
|
v /= x
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
},
|
|
|
|
"min": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'min' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
val := number(math.MaxFloat64)
|
|
|
|
for _, v := range a {
|
|
|
|
value, ok := v.(number)
|
|
|
|
if !ok {
|
|
|
|
panic("'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 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'max' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
val := number(math.SmallestNonzeroFloat64)
|
|
|
|
for _, v := range a {
|
|
|
|
value, ok := v.(number)
|
|
|
|
if !ok {
|
|
|
|
panic("'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 {
|
|
|
|
panic("'<=' expected, but did not receive, two values")
|
|
|
|
}
|
|
|
|
n1, ok1 := a[0].(number)
|
2021-07-27 03:19:59 +00:00
|
|
|
n2, ok2 := a[1].(number)
|
2021-07-26 05:31:22 +00:00
|
|
|
if !ok1 || !ok2 {
|
|
|
|
panic("'<=' expected numbers, but a non-number value was received")
|
|
|
|
}
|
|
|
|
return n1 <= n2
|
|
|
|
},
|
|
|
|
"<": func(a ...expression) expression {
|
|
|
|
if len(a) < 2 {
|
|
|
|
panic("'<' expected, but did not receive, two values")
|
|
|
|
}
|
|
|
|
n1, ok1 := a[0].(number)
|
2021-07-27 03:19:59 +00:00
|
|
|
n2, ok2 := a[1].(number)
|
2021-07-26 05:31:22 +00:00
|
|
|
if !ok1 || !ok2 {
|
|
|
|
panic("'<' expected numbers, but a non-number value was received")
|
|
|
|
}
|
|
|
|
return n1 < n2
|
|
|
|
},
|
|
|
|
">=": func(a ...expression) expression {
|
|
|
|
if len(a) < 2 {
|
|
|
|
panic("'>=' expected, but did not receive, two values")
|
|
|
|
}
|
|
|
|
n1, ok1 := a[0].(number)
|
2021-07-27 03:19:59 +00:00
|
|
|
n2, ok2 := a[1].(number)
|
2021-07-26 05:31:22 +00:00
|
|
|
if !ok1 || !ok2 {
|
|
|
|
panic("'>=' expected numbers, but a non-number value was received")
|
|
|
|
}
|
|
|
|
return n1 >= n2
|
|
|
|
},
|
|
|
|
">": func(a ...expression) expression {
|
|
|
|
if len(a) < 2 {
|
|
|
|
panic("'>' expected, but did not receive, two values")
|
|
|
|
}
|
|
|
|
n1, ok1 := a[0].(number)
|
2021-07-27 03:19:59 +00:00
|
|
|
n2, ok2 := a[1].(number)
|
2021-07-26 05:31:22 +00:00
|
|
|
if !ok1 || !ok2 {
|
|
|
|
panic("'>' expected numbers, but a non-number value was received")
|
|
|
|
}
|
|
|
|
return n1 > n2
|
|
|
|
},
|
|
|
|
"equal?": func(a ...expression) expression {
|
|
|
|
if len(a) < 2 {
|
|
|
|
panic("'equal?' expected, but did not receive, two values")
|
|
|
|
}
|
|
|
|
return reflect.DeepEqual(a[0], a[1])
|
|
|
|
},
|
|
|
|
"number?": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'number?' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
switch a[0].(type) {
|
|
|
|
case float64, number:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"string?": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'string?' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
switch a[0].(type) {
|
|
|
|
case string:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"bool?": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'bool?' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
switch a[0].(type) {
|
|
|
|
case bool:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"symbol?": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'symbol?' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
switch a[0].(type) {
|
|
|
|
case symbol:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
},
|
2021-07-27 03:19:59 +00:00
|
|
|
"empty?": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
|
|
|
panic("'empty?' expected a value, but no value was given")
|
|
|
|
}
|
|
|
|
switch i := a[0].(type) {
|
|
|
|
case []expression:
|
|
|
|
return len(i) == 0
|
|
|
|
default:
|
|
|
|
panic("'empty' expected a pair/list, but a non-pair/list was given")
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
2021-07-26 05:31:22 +00:00
|
|
|
"pair?": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'pair?' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
switch a[0].(type) {
|
|
|
|
case []expression:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"procedure?": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'proc?' expected a value, but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
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))
|
|
|
|
default:
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("'length' expected a list, but the given value was not a list")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
"string-length": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("no value was given to 'string-length'")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
if v, ok := a[0].(string); ok {
|
|
|
|
return number(len([]rune(v)))
|
|
|
|
}
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("a non-string value was given to string-length")
|
2021-07-26 05:31:22 +00:00
|
|
|
},
|
|
|
|
"cons": func(a ...expression) expression {
|
|
|
|
if len(a) < 2 {
|
|
|
|
panic(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 {
|
|
|
|
panic(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:
|
|
|
|
panic(fmt.Sprintf("%q expected a list, but a %T was given", "car", i))
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"cdr": func(a ...expression) expression {
|
|
|
|
if len(a) < 1 {
|
|
|
|
panic("\"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:
|
|
|
|
panic(fmt.Sprintf("%q expected a list, but a %T was given", "car", i))
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"append": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
|
|
|
return make([]expression, 0)
|
|
|
|
}
|
|
|
|
mainList, ok := a[0].([]expression)
|
|
|
|
if !ok {
|
|
|
|
panic("'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)"),&globalenv),
|
|
|
|
"not": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 22:35:00 +00:00
|
|
|
panic("'not' expects a value but no value was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
if i, ok := a[0].(bool); ok && !i {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
"and": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
for _, v := range a {
|
|
|
|
if !AnythingToBool(v).(bool) {
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return a[len(a)-1]
|
|
|
|
},
|
|
|
|
"or": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for _, v := range a {
|
|
|
|
if AnythingToBool(v).(bool) {
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return a[len(a)-1]
|
|
|
|
},
|
|
|
|
"map": func(a ...expression) expression {
|
|
|
|
if len(a) < 2 {
|
|
|
|
panic("'map' expects a procedure followed by at least one list")
|
|
|
|
}
|
|
|
|
ml, err := MergeLists(a[1:])
|
|
|
|
if err != nil {
|
|
|
|
panic("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:
|
|
|
|
panic("map expects a procedure followed by at least one list")
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"for-each": func(a ...expression) expression {
|
|
|
|
if len(a) < 2 {
|
|
|
|
panic("for-each expects a procedure followed by at least one list")
|
|
|
|
}
|
|
|
|
ml, err := MergeLists(a[1:])
|
|
|
|
if err != nil {
|
|
|
|
panic("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:
|
|
|
|
panic("for-each expects a procedure followed by at least one list")
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"display": func(a ...expression) expression {
|
|
|
|
var out strings.Builder
|
|
|
|
for i := range a {
|
|
|
|
switch val := a[i].(type) {
|
|
|
|
case string:
|
|
|
|
out.WriteString(unescapeString(val))
|
|
|
|
default:
|
|
|
|
out.WriteString(String(val))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Print(out.String())
|
|
|
|
return make([]expression, 0)
|
|
|
|
},
|
2021-07-27 16:33:06 +00:00
|
|
|
"display-lines": func(a ...expression) expression {
|
|
|
|
var out strings.Builder
|
|
|
|
for i := range a {
|
|
|
|
switch val := a[i].(type) {
|
|
|
|
case string:
|
|
|
|
out.WriteString(unescapeString(val))
|
|
|
|
out.WriteRune('\n')
|
|
|
|
default:
|
|
|
|
out.WriteString(String(val))
|
|
|
|
out.WriteRune('\n')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Print(out.String())
|
|
|
|
return make([]expression, 0)
|
|
|
|
},
|
2021-07-26 05:31:22 +00:00
|
|
|
"write": func(a ...expression) expression {
|
|
|
|
if len(a) == 1 {
|
|
|
|
fmt.Print(String(a[0]))
|
|
|
|
} else if len(a) == 2 {
|
|
|
|
var stringOut string
|
|
|
|
if s, ok := a[0].(string); ok {
|
|
|
|
stringOut = unescapeString(s)
|
|
|
|
} else {
|
|
|
|
stringOut = String(a[0])
|
|
|
|
}
|
2021-07-27 16:33:06 +00:00
|
|
|
obj, ok := a[1].(*IOHandle)
|
|
|
|
if !ok {
|
2021-07-27 22:35:00 +00:00
|
|
|
panic("'write' expected an IO handle as its second argument, but was not given an IO handle")
|
2021-07-27 16:33:06 +00:00
|
|
|
}
|
|
|
|
if !obj.Open {
|
|
|
|
panic("'write' was given an IO handle that is already closed")
|
|
|
|
}
|
|
|
|
switch ft := obj.Obj.(type) {
|
2021-07-26 05:31:22 +00:00
|
|
|
case *os.File:
|
|
|
|
ft.WriteString(stringOut)
|
2021-07-27 22:35:00 +00:00
|
|
|
case *net.Conn:
|
|
|
|
(*ft).Write([]byte(stringOut))
|
|
|
|
return a[1].(*IOHandle)
|
2021-07-26 05:31:22 +00:00
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return make([]expression, 0)
|
|
|
|
},
|
2021-07-27 03:19:59 +00:00
|
|
|
"write-raw": func(a ...expression) expression {
|
|
|
|
if len(a) == 1 {
|
|
|
|
fmt.Print(String(a[0]))
|
2021-07-27 16:33:06 +00:00
|
|
|
} else if len(a) >= 2 {
|
|
|
|
obj, ok := a[1].(*IOHandle)
|
|
|
|
if !ok {
|
|
|
|
panic("'write-raw' expected an IO handle as its argument, but was not given an IO handle")
|
|
|
|
}
|
|
|
|
if !obj.Open {
|
|
|
|
panic("'write-raw' was given an IO handle that is already closed")
|
|
|
|
}
|
|
|
|
switch ft := obj.Obj.(type) {
|
2021-07-27 03:19:59 +00:00
|
|
|
case *os.File:
|
|
|
|
ft.WriteString(String(a[0]))
|
2021-07-27 22:35:00 +00:00
|
|
|
case *net.Conn:
|
|
|
|
(*ft).Write([]byte(String(a[0])))
|
|
|
|
return a[1].(*IOHandle)
|
2021-07-27 03:19:59 +00:00
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return make([]expression, 0)
|
|
|
|
},
|
2021-07-27 22:35:00 +00:00
|
|
|
"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 {
|
|
|
|
panic("'read-all' expected an IO handle as its argument, but was not given an IO handle")
|
|
|
|
}
|
|
|
|
if !obj.Open {
|
|
|
|
panic("'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 false
|
|
|
|
}
|
|
|
|
return string(b)
|
|
|
|
case *net.Conn:
|
|
|
|
b, err := ioutil.ReadAll(*f)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return string(b)
|
|
|
|
default:
|
|
|
|
panic("'read-line' has not been implemented for this object type")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2021-07-26 05:31:22 +00:00
|
|
|
"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]
|
2021-07-27 03:19:59 +00:00
|
|
|
} else {
|
2021-07-27 16:33:06 +00:00
|
|
|
obj, ok := a[0].(*IOHandle)
|
|
|
|
if !ok {
|
|
|
|
panic("'read-line' expected an IO handle as its argument, but was not given an IO handle")
|
|
|
|
}
|
|
|
|
if !obj.Open {
|
|
|
|
panic("'read-line' was given an IO handle that is already closed")
|
|
|
|
}
|
|
|
|
switch f := obj.Obj.(type) {
|
2021-07-27 03:19:59 +00:00
|
|
|
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 false
|
|
|
|
} else if err != nil && err == io.EOF {
|
|
|
|
return symbol("EOF")
|
|
|
|
} else if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return o.String()
|
2021-07-27 22:35:00 +00:00
|
|
|
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 false
|
|
|
|
} else if err != nil && err == io.EOF {
|
|
|
|
return symbol("EOF")
|
|
|
|
} else if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return o.String()
|
2021-07-27 03:19:59 +00:00
|
|
|
default:
|
|
|
|
panic("'read-line' has not been implemented for this object type")
|
|
|
|
}
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
"newline": func(a ...expression) expression {
|
|
|
|
fmt.Print("\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 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("insufficient number of arguments given to 'substring'")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
var s string
|
|
|
|
var n1, n2 number
|
|
|
|
var ok bool
|
|
|
|
if s, ok = a[0].(string); !ok {
|
|
|
|
panic("'substring' expects a string as its first argument")
|
|
|
|
}
|
|
|
|
if n1, ok = a[1].(number); !ok {
|
|
|
|
panic("'substring' expects a number as its second argument")
|
|
|
|
}
|
|
|
|
|
|
|
|
if n2, ok = a[2].(number); !ok {
|
|
|
|
panic("'substring' expects a number as its second argument")
|
|
|
|
}
|
|
|
|
|
|
|
|
if n1 < 0 || n1 >= n2{
|
|
|
|
panic("start position of 'substring' is out of range")
|
|
|
|
}
|
|
|
|
|
|
|
|
if int(n2) > len(s) {
|
|
|
|
panic("end position of 'substring' is out of range")
|
|
|
|
}
|
|
|
|
|
|
|
|
return s[int(n1):int(n2)]
|
|
|
|
},
|
|
|
|
"string-append": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("insufficient number of arguments given to 'string-append'")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
var out strings.Builder
|
|
|
|
for i := range a {
|
|
|
|
if _, ok := a[i].(string); !ok {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("a non-string value was given to 'string-append'")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
out.WriteString(a[i].(string))
|
|
|
|
}
|
|
|
|
return out.String()
|
|
|
|
},
|
|
|
|
"string->number": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("insufficient number of arguments given to 'string->number'")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
if s, ok := a[0].(string); ok {
|
|
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
|
|
if err == nil {
|
|
|
|
return number(f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
"number->string": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
2021-07-27 03:19:59 +00:00
|
|
|
panic("insufficient number of arguments given to 'string->number'")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
if n, ok := a[0].(number); ok {
|
|
|
|
return strconv.FormatFloat(float64(n), 'f', -1, 64)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
"file-create": func(a ...expression) expression {
|
|
|
|
if len(a) < 1 {
|
|
|
|
panic("'file-create' expects a filepath, no filepath was given")
|
|
|
|
}
|
|
|
|
if fp, ok := a[0].(string); ok {
|
|
|
|
f, err := os.Create(fp)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
2021-07-27 16:33:06 +00:00
|
|
|
obj := &IOHandle{f, true}
|
|
|
|
openFiles = append(openFiles, obj)
|
|
|
|
return obj
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
panic("'file-create' expects a filepath as a string, a non-string value was given")
|
|
|
|
},
|
2021-07-27 03:19:59 +00:00
|
|
|
"file-open-read": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
|
|
|
panic("'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
|
|
|
|
}
|
2021-07-27 16:33:06 +00:00
|
|
|
obj := &IOHandle{f, true}
|
|
|
|
openFiles = append(openFiles, obj)
|
|
|
|
return obj
|
2021-07-27 03:19:59 +00:00
|
|
|
}
|
|
|
|
panic("'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_ [_truncate bool_])
|
|
|
|
//
|
|
|
|
if len(a) == 0 {
|
|
|
|
panic("'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
|
|
|
|
}
|
2021-07-27 16:33:06 +00:00
|
|
|
obj := &IOHandle{f, true}
|
|
|
|
openFiles = append(openFiles, obj)
|
|
|
|
return obj
|
2021-07-27 03:19:59 +00:00
|
|
|
}
|
|
|
|
panic("'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"...)
|
|
|
|
//
|
|
|
|
if len(a) < 2 {
|
|
|
|
panic("'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:] {
|
|
|
|
switch val := a[i+1].(type) {
|
|
|
|
case string:
|
|
|
|
f.WriteString(unescapeString(val))
|
|
|
|
default:
|
|
|
|
f.WriteString(String(val))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
panic("'file-append-to' expects a filepath, as a string, as its first argument")
|
|
|
|
},
|
2021-07-27 16:33:06 +00:00
|
|
|
"open?": func (a ...expression) expression {
|
2021-07-26 05:31:22 +00:00
|
|
|
if len(a) < 1 {
|
2021-07-27 16:33:06 +00:00
|
|
|
panic("'open?' expects an IO handle, no argument was given")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
2021-07-27 16:33:06 +00:00
|
|
|
if h, ok := a[0].(*IOHandle); ok {
|
|
|
|
return h.Open
|
|
|
|
}
|
|
|
|
panic("'close' expects an IO handle")
|
|
|
|
},
|
|
|
|
"close": func(a ...expression) expression {
|
|
|
|
if len(a) < 1 {
|
|
|
|
panic("'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()
|
2021-07-27 22:35:00 +00:00
|
|
|
case *net.Conn:
|
|
|
|
(*o).Close()
|
2021-07-27 16:33:06 +00:00
|
|
|
default:
|
|
|
|
panic("'close' encountered an unsupported IO handle type")
|
|
|
|
}
|
2021-07-26 05:31:22 +00:00
|
|
|
} else {
|
2021-07-27 16:33:06 +00:00
|
|
|
panic("'close' expects an IO handle")
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
return make([]expression, 0)
|
|
|
|
},
|
2021-07-27 03:19:59 +00:00
|
|
|
"path-exists?": func(a ...expression) expression {
|
|
|
|
if len(a) == 0 {
|
|
|
|
panic("'path-exists' expects a filepath as a string, no value was supplied")
|
|
|
|
}
|
|
|
|
switch p := a[0].(type) {
|
|
|
|
case string:
|
|
|
|
_, err := os.Stat(unescapeString(p))
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
case symbol:
|
|
|
|
_, err := os.Stat(unescapeString(string(p)))
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
},
|
2021-07-27 22:35:00 +00:00
|
|
|
"net-conn": func(a ...expression) expression {
|
|
|
|
if len(a) < 2 {
|
|
|
|
panic("'net-conn' expects a host and a port as a string, too few values given")
|
|
|
|
}
|
|
|
|
host, ok := a[0].(string)
|
|
|
|
if !ok {
|
|
|
|
panic("'net-conn' expects a host as a string, a non-string value was given")
|
|
|
|
}
|
|
|
|
port, ok := a[1].(string)
|
|
|
|
if !ok {
|
|
|
|
panic("'net-conn' expects a port as a string, a non-string value was given")
|
|
|
|
}
|
|
|
|
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%s", host, port))
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
handle := &IOHandle{&conn, true}
|
|
|
|
openFiles = append(openFiles, handle)
|
|
|
|
return handle
|
|
|
|
},
|
2021-07-26 05:31:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
TODO:
|
|
|
|
|
|
|
|
Number based:
|
|
|
|
[ ] Adjust >, >=, <, <= to accept any number args and require all to be sequenced correctly for the given operator
|
|
|
|
[x] zero?
|
|
|
|
[x] positive?
|
|
|
|
[x] negative?
|
|
|
|
[ ] odd? - may not make sense in a float only language
|
|
|
|
[ ] even? - may not make sense in a float only language
|
|
|
|
[x] max
|
|
|
|
[x] min
|
|
|
|
[x] abs
|
|
|
|
[ ] modulo
|
|
|
|
[ ] remainder
|
|
|
|
[ ] quotient
|
|
|
|
[x] floor
|
|
|
|
[x] ceiling
|
|
|
|
[x] round (take an optional second input for num digits, default to 0)
|
|
|
|
[ ] expt
|
|
|
|
[ ] sin
|
|
|
|
[ ] cos
|
|
|
|
[ ] tan
|
|
|
|
[ ] atan
|
|
|
|
[ ] sqrt
|
|
|
|
[x] PI
|
|
|
|
[x] E
|
|
|
|
[x] number->string
|
|
|
|
|
|
|
|
String based:
|
|
|
|
[x] string-length (use runes)
|
|
|
|
[x] substring
|
|
|
|
[x] string-append
|
2021-07-27 03:19:59 +00:00
|
|
|
[ ] string-make-buf
|
|
|
|
[ ] string-buf-len
|
|
|
|
[ ] string-buf-string
|
2021-07-26 05:31:22 +00:00
|
|
|
[x] string->number
|
2021-07-27 03:19:59 +00:00
|
|
|
[ ] string->list (takes a string and an optional split-string, with no split-string it will split on every char)
|
|
|
|
[ ] string-upper
|
|
|
|
[ ] string-lower
|
|
|
|
[ ] string-has-prefix?
|
|
|
|
[ ] string-has-suffix?
|
|
|
|
[ ] string-index-of
|
|
|
|
[ ] string-format
|
2021-07-26 05:31:22 +00:00
|
|
|
|
|
|
|
List based:
|
|
|
|
[x] append
|
|
|
|
[x] cons
|
|
|
|
[x] car
|
|
|
|
[x] cdr
|
|
|
|
[x] list
|
2021-07-27 03:19:59 +00:00
|
|
|
[x] empty?
|
|
|
|
[x] pair?
|
|
|
|
[ ] list->string (takes an optional join-string which will be inserted between each element)
|
2021-07-26 05:31:22 +00:00
|
|
|
|
|
|
|
Control structure based:
|
|
|
|
[x] for-each
|
|
|
|
[x] map
|
2021-07-27 03:19:59 +00:00
|
|
|
[x] cond (done as a builtin special form)
|
|
|
|
|
|
|
|
FS Based:
|
|
|
|
[x] path-exists?
|
|
|
|
[ ] path-is-dir?
|
|
|
|
[ ] chown
|
|
|
|
[ ] chmod
|
|
|
|
[ ] path-dir-contents (return a list of strings)
|
|
|
|
[ ] path-join
|
|
|
|
[ ] path-base
|
|
|
|
[ ] path-extension
|
|
|
|
[ ] path-abs
|
|
|
|
[ ] path-dir
|
|
|
|
[ ] path-glob
|
|
|
|
|
2021-07-26 05:31:22 +00:00
|
|
|
IO Based:
|
|
|
|
[x] display
|
2021-07-27 03:19:59 +00:00
|
|
|
[x] write (should do stdout by default, or accept a file, buf, or conn)
|
|
|
|
[x] write-raw (same as write, but does not unescape strings)
|
|
|
|
[x] read-line (defaults to stdin, or takes a file or conn)
|
2021-07-26 05:31:22 +00:00
|
|
|
[ ] read-all (takes a file or conn as argument)
|
2021-07-27 05:47:24 +00:00
|
|
|
[ ] read-bytes (takes a file or conn)
|
2021-07-26 05:31:22 +00:00
|
|
|
[ ] read-all-lines (takes a file or conn)
|
|
|
|
[x] file-create (overwrites existing, returns *File || bool)
|
2021-07-27 03:19:59 +00:00
|
|
|
[x] file-open-read (returns *file || bool, file is set to read only)
|
|
|
|
[x] file-open-write (returns *file || bool, file is set to read only)
|
2021-07-26 05:31:22 +00:00
|
|
|
[x] file-close
|
2021-07-27 03:19:59 +00:00
|
|
|
[x] file-append-to
|
2021-07-27 05:47:24 +00:00
|
|
|
[ ] file-seek
|
2021-07-26 05:31:22 +00:00
|
|
|
|
2021-07-27 03:19:59 +00:00
|
|
|
Net Based:
|
2021-07-27 22:35:00 +00:00
|
|
|
[x] net-conn
|
|
|
|
- url-host
|
|
|
|
- url-port
|
|
|
|
- url-scheme
|
|
|
|
- url-path
|
|
|
|
- url-query
|
|
|
|
- url-set-host
|
|
|
|
- url-set-port
|
|
|
|
- url-set-query
|
|
|
|
- url-set-path
|
2021-07-26 05:31:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
System Based:
|
|
|
|
[x] exit
|
|
|
|
[ ] env-get
|
|
|
|
[ ] env-set
|
2021-07-27 03:19:59 +00:00
|
|
|
[ ] env-chdir
|
2021-07-26 05:31:22 +00:00
|
|
|
[ ] subprocess (may require multiple funcs)
|
|
|
|
[ ] load (loads a scheme file)
|
|
|
|
[x] equal? (compares any two things)
|
|
|
|
|
|
|
|
*/
|
2021-07-27 22:35:00 +00:00
|
|
|
|