slope/lib.go

982 lines
23 KiB
Go
Raw Normal View History

package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"math"
"net"
"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 {
panic("'abs' expected a value, but no value was given")
}
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 {
panic("'floor' expected a value, but no value was given")
}
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 {
panic("'ceil' expected a value, but no value was given")
}
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 {
panic("'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 {
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 {
panic("'-' expected a value, but no value was given")
}
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 {
panic("'/' expected a value, but no value was given")
}
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 {
panic("'min' expected a value, but no value was given")
}
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 {
panic("'max' expected a value, but no value was given")
}
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)
n2, ok2 := a[1].(number)
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)
n2, ok2 := a[1].(number)
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)
n2, ok2 := a[1].(number)
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)
n2, ok2 := a[1].(number)
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 {
panic("'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 {
panic("'string?' expected a value, but no value was given")
}
switch a[0].(type) {
case string:
return true
default:
return false
}
},
"bool?": func(a ...expression) expression {
if len(a) == 0 {
panic("'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 {
panic("'symbol?' expected a value, but no value was given")
}
switch a[0].(type) {
case symbol:
return true
default:
return false
}
},
"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")
}
},
"pair?": func(a ...expression) expression {
if len(a) == 0 {
panic("'pair?' 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 {
panic("'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))
default:
panic("'length' expected a list, but the given value was not a list")
}
},
"string-length": func(a ...expression) expression {
if len(a) == 0 {
panic("no value was given to 'string-length'")
}
if v, ok := a[0].(string); ok {
return number(len([]rune(v)))
}
panic("a non-string value was given to string-length")
},
"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 {
panic("'not' expects a value but no value was given")
}
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)
},
"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)
},
"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])
}
obj, ok := a[1].(*IOHandle)
if !ok {
panic("'write' expected an IO handle as its second argument, but was not given an IO handle")
}
if !obj.Open {
panic("'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))
return a[1].(*IOHandle)
default:
return false
}
}
return make([]expression, 0)
},
"write-raw": func(a ...expression) expression {
if len(a) == 1 {
fmt.Print(String(a[0]))
} 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) {
case *os.File:
ft.WriteString(String(a[0]))
case *net.Conn:
(*ft).Write([]byte(String(a[0])))
return a[1].(*IOHandle)
default:
return false
}
}
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 {
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")
}
}
},
"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 {
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) {
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()
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()
default:
panic("'read-line' has not been implemented for this object type")
}
}
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 {
panic("insufficient number of arguments given to 'substring'")
}
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 {
panic("insufficient number of arguments given to 'string-append'")
}
var out strings.Builder
for i := range a {
if _, ok := a[i].(string); !ok {
panic("a non-string value was given to 'string-append'")
}
out.WriteString(a[i].(string))
}
return out.String()
},
"string->number": func(a ...expression) expression {
if len(a) == 0 {
panic("insufficient number of arguments given to 'string->number'")
}
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 {
panic("insufficient number of arguments given to 'string->number'")
}
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
}
obj := &IOHandle{f, true}
openFiles = append(openFiles, obj)
return obj
}
panic("'file-create' expects a filepath as a string, a non-string value was given")
},
"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
}
obj := &IOHandle{f, true}
openFiles = append(openFiles, obj)
return obj
}
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
}
obj := &IOHandle{f, true}
openFiles = append(openFiles, obj)
return obj
}
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")
},
"open?": func (a ...expression) expression {
if len(a) < 1 {
panic("'open?' expects an IO handle, no argument was given")
}
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()
case *net.Conn:
(*o).Close()
default:
panic("'close' encountered an unsupported IO handle type")
}
} else {
panic("'close' expects an IO handle")
}
return make([]expression, 0)
},
"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
}
},
"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
},
}
/*
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
[ ] string-make-buf
[ ] string-buf-len
[ ] string-buf-string
[x] string->number
[ ] 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
List based:
[x] append
[x] cons
[x] car
[x] cdr
[x] list
[x] empty?
[x] pair?
[ ] list->string (takes an optional join-string which will be inserted between each element)
Control structure based:
[x] for-each
[x] map
[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
IO Based:
[x] display
[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)
[ ] read-all (takes a file or conn as argument)
[ ] read-bytes (takes a file or conn)
[ ] read-all-lines (takes a file or conn)
[x] file-create (overwrites existing, returns *File || bool)
[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)
[x] file-close
[x] file-append-to
[ ] file-seek
Net Based:
[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
System Based:
[x] exit
[ ] env-get
[ ] env-set
[ ] env-chdir
[ ] subprocess (may require multiple funcs)
[ ] load (loads a scheme file)
[x] equal? (compares any two things)
*/