2023-06-02 05:42:09 +00:00
/ *
Copyright ( C ) 2023 Brian Evans ( aka sloum ) . All rights reserved .
This source code is available under the terms of the ffsl , or ,
Floodgap Free Software License . A copy of the license has been
provided as the file ' LICENSE ' in the same folder as this source
code file . If for some reason it is not present , you can find the
terms of version 1 of the FFSL at the following URL :
https : //www.floodgap.com/software/ffsl/license.html
* /
2023-05-31 23:18:16 +00:00
package main
import (
2024-05-13 20:52:47 +00:00
"bufio"
"fmt"
"io"
"math"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"syscall"
"time"
2023-05-31 23:18:16 +00:00
)
func callKeyword ( kw token , r * tokenReader , en * env ) error {
2024-05-13 20:52:47 +00:00
switch kw . val . ( string ) {
case "+" :
return libAdd ( kw . line , kw . file )
case "-" :
return libSubtract ( kw . line , kw . file )
case "*" :
return libMultiply ( kw . line , kw . file )
case "/" :
return libDivide ( kw . line , kw . file )
case "var!" :
return libVar ( kw . line , r , en , kw . file )
case "scoped-var!" :
return libVarPlus ( kw . line , r , en , kw . file )
case "set!" :
return libSet ( kw . line , r , en , kw . file )
case "scoped-set!" :
return libSetPlus ( kw . line , r , en , kw . file )
case "dup" :
return libDuplicate ( kw . line , kw . file )
case "swap" :
return libSwap ( kw . line , kw . file )
case "over" :
return libOver ( kw . line , kw . file )
case "rot" :
return libRot ( kw . line , kw . file )
case "drop" :
return libDrop ( kw . line , kw . file )
case "cast" :
return libConvertType ( kw . line , kw . file )
case "and" :
return libLogicalAnd ( kw . line , kw . file )
case "or" :
return libLogicalOr ( kw . line , kw . file )
case "=" :
return libEqual ( kw . line , kw . file )
case ">" :
return libGreaterThan ( kw . line , kw . file )
case "<" :
return libLessThan ( kw . line , kw . file )
case "stackdump" :
return libStackDump ( )
case "clearstack" :
return libClearStack ( )
case "stackdepth" :
return libStackDepth ( kw . line , kw . file )
case "length" :
return libLength ( kw . line , kw . file )
case "append!" , "=>!" :
return libAppendSpecial ( kw . line , r , en , kw . file )
case "append" , "=>" :
return libAppend ( kw . line , kw . file )
case "list-get" , "<-" :
return libGetListItem ( kw . line , r , en , kw . file )
case "list-set!" , "->!" :
return libSetListItemSpecial ( kw . line , r , en , kw . file )
case "list-set" , "->" :
return libSetListItem ( kw . line , kw . file )
case "file-append" :
return libFileAppend ( kw . line , kw . file , true )
case "file-exists?" :
return libFileExists ( kw . line , kw . file )
case "file-read" :
return libFileRead ( kw . line , kw . file )
case "file-write" :
return libFileAppend ( kw . line , kw . file , false )
case "docstring!" :
return libDocstring ( kw . line , r , en , kw . file )
case "input" :
return libInput ( kw . line , kw . file )
case "re-match?" :
return libReMatch ( kw . line , kw . file )
case "re-find" :
return libReFind ( kw . line , kw . file , false )
case "re-find-all" :
return libReFind ( kw . line , kw . file , true )
case "re-replace" :
return libReReplace ( kw . line , kw . file )
case "slice" :
return libSlice ( kw . line , kw . file )
case "type" :
return libType ( kw . line , kw . file )
case "net-get" :
return libNetGet ( kw . line , kw . file )
case "throw" :
return libThrow ( kw . line , kw . file )
case "import" :
return libImport ( kw . line , kw . file , en )
case "each!" :
return libEach ( kw . line , r , en , kw . file )
case "filter!" :
return libFilter ( kw . line , r , en , kw . file )
case "words" :
return libWords ( kw . line , kw . file )
case "time" :
return libTime ( kw . line , kw . file )
case "char-conv" :
return libToChar ( kw . line , kw . file )
case "env-get" :
return libEnvGet ( kw . line , kw . file )
case "env-set" :
return libEnvSet ( kw . line , kw . file )
case "exec" :
return libSubprocess ( kw . line , kw . file , false , false )
case "<-exec" :
return libSubprocess ( kw . line , kw . file , true , false )
case "exec->" :
return libSubprocess ( kw . line , kw . file , false , true )
case "<-exec->" :
return libSubprocess ( kw . line , kw . file , true , true )
case "cd" :
return libChangeDir ( kw . line , kw . file )
case "pwd" :
return libPresentDir ( kw . line , kw . file )
case "mkdir" :
return libMkdir ( kw . line , kw . file )
case "keys" :
return libKeys ( kw . line , kw . file )
default :
return fmt . Errorf ( "Unknown keyword: `%s`" , kw . line , kw . val . ( string ) )
}
2023-05-31 23:18:16 +00:00
}
2023-11-29 00:14:38 +00:00
func libWords ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
return globalStack . Push ( token { STRING , strings . Join ( completions , " " ) , line , fp } )
2023-11-29 00:14:38 +00:00
}
2023-06-12 05:51:39 +00:00
func libAdd ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
if v1 . kind == STRING && v2 . kind == STRING {
// String join
err = globalStack . Push ( token { STRING , v1 . val . ( string ) + v2 . val . ( string ) , line , fp } )
} else if v1 . kind == LIST && v2 . kind == LIST {
// List join
l1 := v1 . val . ( list )
l2 := v2 . val . ( list )
nl := make ( [ ] token , len ( l1 . body ) + len ( l2 . body ) )
for i , lt := range l1 . body {
nl [ i ] = lt
}
for i , lt := range l2 . body {
nl [ i + len ( l1 . body ) ] = lt
}
err = globalStack . Push ( token { LIST , list { nl } , line , fp } )
} else if v1 . kind == STRING && v2 . kind == INT {
i := v2 . val . ( int )
if i < 0 {
return fmt . Errorf ( "Adding a rune to a string is only valid with positive integers, got %d" , i )
}
s := v1 . val . ( string )
s += string ( rune ( i ) )
err = globalStack . Push ( token { STRING , s , line , fp } )
} else if v1 . kind == INT && v2 . kind == INT {
err = globalStack . Push ( token { INT , v1 . val . ( int ) + v2 . val . ( int ) , line , fp } )
} else if v1 . kind == FLOAT && v2 . kind == FLOAT {
err = globalStack . Push ( token { FLOAT , v1 . val . ( float64 ) + v2 . val . ( float64 ) , line , fp } )
} else if v1 . kind == FLOAT && v2 . kind == INT {
err = globalStack . Push ( token { FLOAT , v1 . val . ( float64 ) + float64 ( v2 . val . ( int ) ) , line , fp } )
} else if v1 . kind == INT && v2 . kind == FLOAT {
err = globalStack . Push ( token { FLOAT , v2 . val . ( float64 ) + float64 ( v1 . val . ( int ) ) , line , fp } )
} else if v1 . kind == STRING {
err = globalStack . Push ( token { STRING , v1 . val . ( string ) + toString ( v2 , false ) , line , fp } )
} else {
return fmt . Errorf ( "Cannot `+` %s and %s" , kindToString ( v1 . kind ) , kindToString ( v2 . kind ) )
}
if err != nil {
return err
}
return nil
2023-05-31 23:18:16 +00:00
}
2023-06-12 05:51:39 +00:00
func libSubtract ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
if v1 . kind == STRING {
s := strings . Replace ( v1 . val . ( string ) , toString ( v2 , false ) , "" , - 1 )
err = globalStack . Push ( token { STRING , s , line , fp } )
} else if v1 . kind == INT && v2 . kind == INT {
err = globalStack . Push ( token { INT , v1 . val . ( int ) - v2 . val . ( int ) , line , fp } )
} else if v1 . kind == FLOAT && v2 . kind == FLOAT {
err = globalStack . Push ( token { FLOAT , v1 . val . ( float64 ) - v2 . val . ( float64 ) , line , fp } )
} else if v1 . kind == FLOAT && v2 . kind == INT {
err = globalStack . Push ( token { FLOAT , v1 . val . ( float64 ) - float64 ( v2 . val . ( int ) ) , line , fp } )
} else if v1 . kind == INT && v2 . kind == FLOAT {
err = globalStack . Push ( token { FLOAT , float64 ( v1 . val . ( int ) ) - v2 . val . ( float64 ) , line , fp } )
} else if v1 . kind == DICT && v2 . kind == STRING {
// NOTE This works on the memory location, so acts as -!,
// which isn't exactly what we want I dont think? We'd
// want a separate way to do that, right? Compare to
// how this works with a list
delete ( v1 . val . ( dict ) . body , v2 . val . ( string ) )
err = globalStack . Push ( v1 )
} else if v1 . kind == LIST {
l := v1 . val . ( list ) . body
nl := make ( [ ] token , 0 , len ( l ) )
for i := range l {
if l [ i ] . kind != v2 . kind {
nl = append ( nl , l [ i ] )
} else if v2 . kind == LIST {
l1 := l [ i ] . val . ( list )
l2 := v2 . val . ( list )
if l1 . String ( ) != l2 . String ( ) {
nl = append ( nl , l [ i ] )
}
} else if v2 != l [ i ] {
nl = append ( nl , l [ i ] )
}
}
err = globalStack . Push ( token { LIST , list { nl } , line , fp } )
} else {
return fmt . Errorf ( "Cannot `-` %s and %s" , kindToString ( v1 . kind ) , kindToString ( v2 . kind ) )
}
if err != nil {
return err
}
return nil
2023-05-31 23:18:16 +00:00
}
2023-06-12 05:51:39 +00:00
func libMultiply ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
if v1 . kind == STRING && v2 . kind == INT {
if v2 . val . ( int ) < 0 {
err = globalStack . Push ( token { STRING , "" , line , fp } )
} else {
s := strings . Repeat ( v1 . val . ( string ) , v2 . val . ( int ) )
err = globalStack . Push ( token { STRING , s , line , fp } )
}
} else if v1 . kind == STRING && v2 . kind == FLOAT {
if v2 . val . ( float64 ) < 0.0 {
err = globalStack . Push ( token { STRING , "" , line , fp } )
} else {
s := strings . Repeat ( v1 . val . ( string ) , int ( v2 . val . ( float64 ) ) )
err = globalStack . Push ( token { STRING , s , line , fp } )
}
} else if v1 . kind == LIST && v2 . kind == STRING {
l := v1 . val . ( list )
s := l . Join ( v2 . val . ( string ) )
err = globalStack . Push ( token { STRING , s , line , fp } )
} else if v1 . kind == INT && v2 . kind == INT {
err = globalStack . Push ( token { INT , v1 . val . ( int ) * v2 . val . ( int ) , line , fp } )
} else if v1 . kind == FLOAT && v2 . kind == FLOAT {
err = globalStack . Push ( token { FLOAT , v1 . val . ( float64 ) * v2 . val . ( float64 ) , line , fp } )
} else if v1 . kind == FLOAT && v2 . kind == INT {
err = globalStack . Push ( token { FLOAT , v1 . val . ( float64 ) * float64 ( v2 . val . ( int ) ) , line , fp } )
} else if v1 . kind == INT && v2 . kind == FLOAT {
err = globalStack . Push ( token { FLOAT , v2 . val . ( float64 ) * float64 ( v1 . val . ( int ) ) , line , fp } )
} else {
return fmt . Errorf ( "Cannot `*` %s and %s" , kindToString ( v1 . kind ) , kindToString ( v2 . kind ) )
}
if err != nil {
return err
}
return nil
2023-05-31 23:18:16 +00:00
}
2023-06-12 05:51:39 +00:00
func libDivide ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
if v1 . kind == STRING && v2 . kind == STRING {
l := strings . SplitN ( v1 . val . ( string ) , v2 . val . ( string ) , - 1 )
ts := make ( [ ] token , len ( l ) )
for i := range l {
ts [ i ] = token { STRING , l [ i ] , line , fp }
}
err = globalStack . Push ( token { LIST , list { ts } , line , fp } )
} else if v1 . kind == INT && v2 . kind == INT {
if v2 . val . ( int ) == 0 {
return fmt . Errorf ( "Division by zero" )
}
err = globalStack . Push ( token { INT , v1 . val . ( int ) / v2 . val . ( int ) , line , fp } )
} else if v1 . kind == FLOAT && v2 . kind == FLOAT {
if v2 . val . ( float64 ) == 0.0 {
return fmt . Errorf ( "Division by zero" )
}
err = globalStack . Push ( token { FLOAT , v1 . val . ( float64 ) / v2 . val . ( float64 ) , line , fp } )
} else if v1 . kind == FLOAT && v2 . kind == INT {
if v2 . val . ( int ) == 0 {
return fmt . Errorf ( "Division by zero" )
}
err = globalStack . Push ( token { FLOAT , v1 . val . ( float64 ) / float64 ( v2 . val . ( int ) ) , line , fp } )
} else if v1 . kind == INT && v2 . kind == FLOAT {
if v2 . val . ( float64 ) == 0.0 {
return fmt . Errorf ( "Division by zero" )
}
err = globalStack . Push ( token { FLOAT , float64 ( v1 . val . ( int ) ) / v2 . val . ( float64 ) , line , fp } )
} else {
return fmt . Errorf ( "Cannot `/` %s and %s" , kindToString ( v1 . kind ) , kindToString ( v2 . kind ) )
}
if err != nil {
return err
}
return nil
2023-05-31 23:18:16 +00:00
}
2023-06-12 05:51:39 +00:00
func libAppend ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
valueToken , err := globalStack . Pop ( )
if err != nil {
return err
}
targetToken , err := globalStack . Pop ( )
if err != nil {
return err
}
switch targetToken . kind {
case LIST :
l := targetToken . val . ( list )
err = l . Append ( valueToken )
if err != nil {
return err
}
err = globalStack . Push ( token { LIST , l , line , fp } )
if err != nil {
return err
}
case STRING :
s := targetToken . val . ( string )
s = s + toString ( valueToken , false )
err = globalStack . Push ( token { STRING , s , line , fp } )
if err != nil {
return err
}
default :
return fmt . Errorf ( "Cannot `append` %s to %s" , kindToString ( valueToken . kind ) , kindToString ( targetToken . kind ) )
}
return nil
2023-06-04 22:04:58 +00:00
}
2023-06-03 16:33:09 +00:00
2023-06-12 05:51:39 +00:00
func libAppendSpecial ( line int , r * tokenReader , en * env , fp string ) error {
2024-05-13 20:52:47 +00:00
valueToken , err := globalStack . Pop ( )
if err != nil {
return err
}
t , err := r . Read ( )
if err != nil {
return fmt . Errorf ( "Unexpectedly reached EOF" )
}
var fromVariable bool
var targetToken token = t
var targetTokenenv * env
if t . kind == SYMBOL {
targetTokenenv , err = en . Find ( t . val . ( string ) )
if err != nil {
return err
}
targetToken = targetTokenenv . vars [ t . val . ( string ) ]
fromVariable = true
}
switch targetToken . kind {
case LIST :
l := targetToken . val . ( list )
err = l . Append ( valueToken )
if err != nil {
return err
}
if fromVariable {
targetTokenenv . vars [ t . val . ( string ) ] = token { LIST , l , line , fp }
} else {
err := globalStack . Push ( token { LIST , l , targetToken . line , targetToken . file } )
if err != nil {
return err
}
}
case STRING :
s := targetToken . val . ( string ) + toString ( valueToken , false )
if fromVariable {
targetTokenenv . vars [ t . val . ( string ) ] = token { STRING , s , targetToken . line , targetToken . file }
} else {
targetToken . val = s
err := globalStack . Push ( token { STRING , s , targetToken . line , targetToken . file } )
if err != nil {
return err
}
}
default :
return fmt . Errorf ( "`=>` cannot append to %s" , kindToString ( targetToken . kind ) )
}
return nil
2023-06-04 22:04:58 +00:00
}
2023-06-12 05:51:39 +00:00
func libSetListItem ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
valueToken , err := globalStack . Pop ( )
if err != nil {
return err
}
indexToken , err := globalStack . Pop ( )
if err != nil {
return err
}
listToken , err := globalStack . Pop ( )
if err != nil {
return err
}
if listToken . kind == LIST && indexToken . kind != INT {
return fmt . Errorf ( "`list-set`/`->` expected a value, INT index, and LIST but found %s instead of INT" , kindToString ( indexToken . kind ) )
} else if listToken . kind == DICT && indexToken . kind != STRING {
return fmt . Errorf ( "`list-set`/`->` expected a value, STRING key, and DICT but found %s instead of STRING" , kindToString ( indexToken . kind ) )
} else if listToken . kind != LIST && listToken . kind != DICT {
return fmt . Errorf ( "`list-set`/`->` expected a value, an index or key, and a LIST or DICT but found %s instead of LIST or DICT" , kindToString ( indexToken . kind ) )
}
if listToken . kind == LIST {
l := listToken . val . ( list )
err = l . Set ( indexToken . val . ( int ) , valueToken )
if err != nil {
return err
}
err = globalStack . Push ( token { LIST , l , line , fp } )
} else {
d := listToken . val . ( dict )
err = d . Update ( indexToken , valueToken )
if err != nil {
return err
}
err = globalStack . Push ( token { DICT , d , line , fp } )
}
return err
2023-06-04 22:04:58 +00:00
}
2023-06-12 05:51:39 +00:00
func libSetListItemSpecial ( line int , r * tokenReader , en * env , fp string ) error {
2024-05-13 20:52:47 +00:00
valueToken , err := globalStack . Pop ( )
if err != nil {
return err
}
indexToken , err := globalStack . Pop ( )
if err != nil {
return err
}
// Get var reference
t , err := r . Read ( )
if err != nil {
return err
}
if t . kind != SYMBOL {
return fmt . Errorf ( "`list-set!`/`->!` expected to read in a SYMBOL but found %s" , kindToString ( t . kind ) )
}
targetTokenEnv , err := en . Find ( t . val . ( string ) )
if err != nil {
return err
}
targetToken := targetTokenEnv . vars [ t . val . ( string ) ]
if targetToken . kind == LIST && indexToken . kind != INT {
return fmt . Errorf ( "`list-set!`/`->!` expected a value, INT index, and LIST but found %s instead of INT" , kindToString ( indexToken . kind ) )
} else if targetToken . kind == DICT && indexToken . kind != STRING {
return fmt . Errorf ( "`list-set!`/`->!` expected a value, STRING key, and DICT but found %s instead of STRING" , kindToString ( indexToken . kind ) )
} else if targetToken . kind != LIST && targetToken . kind != DICT {
return fmt . Errorf ( "`list-set!`/`->!` cannot set a value in type %s" , kindToString ( targetToken . kind ) )
}
if targetToken . kind == LIST {
l := targetToken . val . ( list )
err = l . Set ( indexToken . val . ( int ) , valueToken )
} else {
d := targetToken . val . ( dict )
err = d . Update ( indexToken , valueToken )
}
if err != nil {
return err
}
return nil
2023-06-04 22:04:58 +00:00
}
2023-06-12 05:51:39 +00:00
func libGetListItem ( line int , r * tokenReader , en * env , fp string ) error {
2024-05-13 20:52:47 +00:00
indexToken , err := globalStack . Pop ( )
if err != nil {
return err
}
listToken , err := globalStack . Pop ( )
if err != nil {
return err
}
if listToken . kind == LIST && indexToken . kind != INT {
return fmt . Errorf ( "`<-`/`list-get` expected a LIST underneath an INT on the stack, LIST found but found %s instead of INT" , kindToString ( indexToken . kind ) )
} else if listToken . kind == DICT && indexToken . kind != STRING {
return fmt . Errorf ( "`<-`/`list-get` expected a DICT underneath a STRING on the stack, DICT found but found %s instead of STRING" , kindToString ( indexToken . kind ) )
}
var val token
if listToken . kind == LIST {
l := listToken . val . ( list )
val , err = l . Get ( indexToken . val . ( int ) )
if err != nil {
return err
}
} else if listToken . kind == DICT {
d := listToken . val . ( dict )
val , err = d . Get ( indexToken . val . ( string ) )
if err != nil {
return err
}
} else {
return fmt . Errorf ( "`<-`/`list-get` expected a LIST or DICT underneath an INT or STRING on the stack, but found %s instead of INT or STRING" , kindToString ( indexToken . kind ) )
}
err = globalStack . Push ( val )
if err != nil {
return err
}
return nil
2023-06-03 16:33:09 +00:00
}
2023-06-12 05:51:39 +00:00
func libVar ( line int , r * tokenReader , en * env , fp string ) error {
2024-05-13 20:52:47 +00:00
typeToken , err := globalStack . Pop ( )
if err != nil {
return err
}
if typeToken . kind != TYPE {
return fmt . Errorf ( "`var!` expects TYPE on the top of the stack" )
}
newToken := token { }
newToken . kind = typeToken . val . ( int )
newToken . line = line
newToken . file = fp
t , err := r . Read ( )
if err != nil {
return fmt . Errorf ( "Unexpectedly reached EOF" )
}
if t . kind != SYMBOL {
return fmt . Errorf ( "A non-symbol value was given as an identifier to `var!`" )
2024-05-14 03:49:17 +00:00
} else if strings . HasSuffix ( t . val . ( string ) , "!" ) {
return fmt . Errorf ( "Attempted to create a variable named %s on line %d of %s. Only `proc!` names can end in an exclamation point" , t . val . ( string ) , line , fp )
2024-05-14 15:55:03 +00:00
}
2024-05-13 20:52:47 +00:00
newToken . val = typeInit ( newToken . kind )
en . Add ( t . val . ( string ) , newToken )
return nil
2023-06-02 17:12:23 +00:00
}
2023-06-12 05:51:39 +00:00
func libVarPlus ( line int , r * tokenReader , en * env , fp string ) error {
2024-05-13 20:52:47 +00:00
scope , err := globalStack . Pop ( )
if err != nil {
return err
}
if scope . kind != INT {
return fmt . Errorf ( "`scoped-var!` expected an INT declaring how many scopes to jump up, found %s" , kindToString ( scope . kind ) )
}
for i := scope . val . ( int ) ; i > 0 ; i -- {
if en . parent != nil {
en = en . parent
} else {
break
}
}
typeToken , err := globalStack . Pop ( )
if err != nil {
return err
}
if typeToken . kind != TYPE {
return fmt . Errorf ( "`scoped-var!` expects a TYPE literal on the the stack after an INT, found %s" , kindToString ( typeToken . kind ) )
}
newToken := token { }
newToken . kind = typeToken . val . ( int )
newToken . line = line
newToken . file = fp
t , err := r . Read ( )
if err != nil {
return fmt . Errorf ( "Unexpectedly reached EOF" )
}
if t . kind != SYMBOL {
return fmt . Errorf ( "A non-symbol value was given as an identifier to `scoped-var!`" )
2024-05-14 03:49:17 +00:00
} else if strings . HasSuffix ( t . val . ( string ) , "!" ) {
return fmt . Errorf ( "Attempted to create a scoped-variable named %s on line %d of %s. Only `proc!` names can end in an exclamation point" , t . val . ( string ) , line , fp )
2024-05-14 15:55:03 +00:00
}
2024-05-13 20:52:47 +00:00
newToken . val = typeInit ( newToken . kind )
en . Add ( t . val . ( string ) , newToken )
return nil
2023-05-31 23:18:16 +00:00
}
2023-06-12 05:51:39 +00:00
func libSet ( line int , r * tokenReader , en * env , f string ) error {
2024-05-13 20:52:47 +00:00
t , err := r . Read ( )
if err != nil {
return fmt . Errorf ( "Unexpectedly reached EOF" )
}
if t . kind != SYMBOL {
return fmt . Errorf ( "A non-symbol value was given as an identifier to `set!`" )
}
e , err := en . Find ( t . val . ( string ) )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
current := e . vars [ t . val . ( string ) ]
if v1 . kind != current . kind {
return fmt . Errorf ( "Cannot `set!` %s to %s" , kindToString ( v1 . kind ) , kindToString ( current . kind ) )
}
e . vars [ t . val . ( string ) ] = v1
return nil
2023-06-02 17:12:23 +00:00
}
2023-06-04 22:04:58 +00:00
// ANY INT scoped-set! SYMBOL
2023-06-02 17:12:23 +00:00
// INT represents the number of scopes to jump up
2023-06-12 05:51:39 +00:00
func libSetPlus ( line int , r * tokenReader , en * env , fp string ) error {
2024-05-13 20:52:47 +00:00
t , err := r . Read ( )
if err != nil {
return fmt . Errorf ( "Unexpectedly reached EOF" )
}
if t . kind != SYMBOL {
return fmt . Errorf ( "A non-symbol value was given as an identifier to `scoped-set!`" )
}
scope , err := globalStack . Pop ( )
if err != nil {
return err
}
if scope . kind != INT {
return fmt . Errorf ( "`scoped-set!` expected an INT declaring how many scopes to jump up" )
}
for i := scope . val . ( int ) ; i > 0 ; i -- {
if en . parent != nil {
en = en . parent
} else {
break
}
}
e , err := en . Find ( t . val . ( string ) )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
current := e . vars [ t . val . ( string ) ]
if v1 . kind != current . kind {
return fmt . Errorf ( "Cannot assign %s to %s var" , kindToString ( v1 . kind ) , kindToString ( current . kind ) )
}
e . vars [ t . val . ( string ) ] = v1
return nil
2023-05-31 23:18:16 +00:00
}
2023-06-12 05:51:39 +00:00
func libDuplicate ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
err := globalStack . Push ( globalStack . Peek ( ) )
if err != nil {
return err
}
return nil
2023-05-31 23:18:16 +00:00
}
2023-06-12 05:51:39 +00:00
func libSwap ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
err = globalStack . Push ( v2 )
if err != nil {
return err
}
err = globalStack . Push ( v1 )
if err != nil {
return err
}
return nil
2023-05-31 23:18:16 +00:00
}
2023-06-12 05:51:39 +00:00
func libOver ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
err = globalStack . Push ( v1 )
if err != nil {
return err
}
err = globalStack . Push ( v2 )
if err != nil {
return err
}
err = globalStack . Push ( v1 )
if err != nil {
return err
}
return nil
2023-05-31 23:18:16 +00:00
}
2023-06-13 23:19:18 +00:00
func libRot ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v3 , err := globalStack . Pop ( )
if err != nil {
return err
}
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
err = globalStack . Push ( v2 )
if err != nil {
return err
}
err = globalStack . Push ( v3 )
if err != nil {
return err
}
err = globalStack . Push ( v1 )
if err != nil {
return err
}
return nil
2023-06-13 23:19:18 +00:00
}
2023-06-12 05:51:39 +00:00
func libDrop ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
_ , err := globalStack . Pop ( )
if err != nil {
return err
}
return nil
2023-06-01 22:16:17 +00:00
}
2023-06-12 05:51:39 +00:00
func libLogicalAnd ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
if toBoolRaw ( v1 ) && toBoolRaw ( v2 ) {
err = globalStack . Push ( token { BOOL , true , line , fp } )
if err != nil {
return err
}
} else {
err = globalStack . Push ( token { BOOL , false , line , fp } )
if err != nil {
return err
}
}
return nil
2023-06-01 22:16:17 +00:00
}
2023-06-12 05:51:39 +00:00
func libLogicalOr ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
if toBoolRaw ( v1 ) || toBoolRaw ( v2 ) {
err = globalStack . Push ( token { BOOL , true , line , fp } )
if err != nil {
return err
}
} else {
err = globalStack . Push ( token { BOOL , false , line , fp } )
if err != nil {
return err
}
}
return nil
2023-06-01 22:16:17 +00:00
}
2023-06-08 05:59:53 +00:00
// `=` works at a value level and does not refer to
// the equality of memory adresses. As such, two lists
// with the same contents are equal even if they do
// not occupy the same memory/refer to the same object
2023-06-12 05:51:39 +00:00
func libEqual ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
if v1 . kind != v2 . kind {
err = globalStack . Push ( token { BOOL , false , line , fp } )
} else {
var equal bool
switch v1 . kind {
case BOOL :
equal = v1 . val . ( bool ) == v2 . val . ( bool )
case INT , TYPE :
equal = v1 . val . ( int ) == v2 . val . ( int )
case STRING :
equal = v1 . val . ( string ) == v2 . val . ( string )
case FLOAT :
equal = v1 . val . ( float64 ) == v2 . val . ( float64 )
case LIST :
l1 := v1 . val . ( list )
l2 := v2 . val . ( list )
equal = ( l1 . String ( ) == l2 . String ( ) )
}
err = globalStack . Push ( token { BOOL , equal , line , fp } )
}
if err != nil {
return err
}
return nil
2023-06-01 22:16:17 +00:00
}
2023-06-12 05:51:39 +00:00
func libGreaterThan ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
if v1 . kind == v2 . kind {
switch v1 . kind {
case STRING :
err = globalStack . Push ( token { BOOL , v1 . val . ( string ) > v2 . val . ( string ) , line , fp } )
case INT :
err = globalStack . Push ( token { BOOL , v1 . val . ( int ) > v2 . val . ( int ) , line , fp } )
case FLOAT :
err = globalStack . Push ( token { BOOL , v1 . val . ( float64 ) > v2 . val . ( float64 ) , line , fp } )
default :
return fmt . Errorf ( "%s is not a comparable type" , kindToString ( v1 . kind ) )
}
} else if v1 . kind == INT && v2 . kind == FLOAT {
err = globalStack . Push ( token { BOOL , float64 ( v1 . val . ( int ) ) > v2 . val . ( float64 ) , line , fp } )
} else if v1 . kind == FLOAT && v2 . kind == INT {
err = globalStack . Push ( token { BOOL , v1 . val . ( float64 ) > float64 ( v2 . val . ( int ) ) , line , fp } )
} else {
return fmt . Errorf ( "%s and %s cannot be compared" , kindToString ( v1 . kind ) , kindToString ( v2 . kind ) )
}
if err != nil {
return err
}
return nil
2023-06-01 22:16:17 +00:00
}
2023-06-12 05:51:39 +00:00
func libLessThan ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
v2 , err := globalStack . Pop ( )
if err != nil {
return err
}
v1 , err := globalStack . Pop ( )
if err != nil {
return err
}
if v1 . kind == v2 . kind {
switch v1 . kind {
case STRING :
err = globalStack . Push ( token { BOOL , v1 . val . ( string ) < v2 . val . ( string ) , line , fp } )
case INT :
err = globalStack . Push ( token { BOOL , v1 . val . ( int ) < v2 . val . ( int ) , line , fp } )
case FLOAT :
err = globalStack . Push ( token { BOOL , v1 . val . ( float64 ) < v2 . val . ( float64 ) , line , fp } )
case BOOL :
err = globalStack . Push ( token { BOOL , false , line , fp } )
default :
return fmt . Errorf ( "%s is not a comparable type" , kindToString ( v1 . kind ) )
}
} else if v1 . kind == INT && v2 . kind == FLOAT {
err = globalStack . Push ( token { BOOL , float64 ( v1 . val . ( int ) ) < v2 . val . ( float64 ) , line , fp } )
} else if v1 . kind == FLOAT && v2 . kind == INT {
err = globalStack . Push ( token { BOOL , v1 . val . ( float64 ) < float64 ( v2 . val . ( int ) ) , line , fp } )
} else {
return fmt . Errorf ( "%s and %s cannot be compared" , kindToString ( v1 . kind ) , kindToString ( v2 . kind ) )
}
if err != nil {
return err
}
return nil
2023-06-01 22:16:17 +00:00
}
func libStackDump ( ) error {
2024-05-13 20:52:47 +00:00
fmt . Fprintf ( os . Stderr , "\n%s\n" , globalStack . String ( ) )
return nil
2023-06-01 22:16:17 +00:00
}
func libClearStack ( ) error {
2024-05-13 20:52:47 +00:00
globalStack . sp = 0
return nil
2023-05-31 23:18:16 +00:00
}
2023-06-12 05:51:39 +00:00
func libLength ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
t , err := globalStack . Pop ( )
if err != nil {
return err
}
switch t . kind {
case LIST :
l := t . val . ( list )
err = globalStack . Push ( token { INT , len ( l . body ) , line , fp } )
case STRING :
s := t . val . ( string )
err = globalStack . Push ( token { INT , len ( s ) , line , fp } )
case DICT :
d := t . val . ( dict )
err = globalStack . Push ( token { INT , len ( d . body ) , line , fp } )
default :
err = fmt . Errorf ( "%s does not have a length" , kindToString ( t . kind ) )
}
return err
2023-06-04 22:04:58 +00:00
}
2023-06-12 05:51:39 +00:00
func libConvertType ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
typeToken , err := globalStack . Pop ( )
if err != nil {
return err
}
if typeToken . kind != TYPE {
return fmt . Errorf ( "`cast` expected a TYPE on top of stack" )
}
fromToken , err := globalStack . Pop ( )
if err != nil {
return err
}
toKind , fromKind := typeToken . val . ( int ) , fromToken . kind
if fromKind == toKind {
err = globalStack . Push ( fromToken )
} else if fromKind == INT && toKind == FLOAT {
err = globalStack . Push ( token { FLOAT , float64 ( fromToken . val . ( int ) ) , line , fp } )
} else if fromKind == FLOAT && toKind == INT {
err = globalStack . Push ( token { INT , int ( math . Floor ( fromToken . val . ( float64 ) ) ) , line , fp } )
} else if toKind == STRING {
err = globalStack . Push ( token { STRING , toString ( fromToken , false ) , line , fp } )
} else if fromKind == STRING && toKind == INT {
i , err := tokenStringToInt ( fromToken . val . ( string ) )
if err != nil {
return fmt . Errorf ( "%s (STRING: %q)" , err . Error ( ) , fromToken . val . ( string ) )
}
err = globalStack . Push ( token { INT , i , line , fp } )
} else if fromKind == STRING && toKind == FLOAT {
f , err := strconv . ParseFloat ( fromToken . val . ( string ) , 64 )
if err != nil {
return fmt . Errorf ( "Could not convert STRING to FLOAT (STRING: %q)" , fromToken . val . ( string ) )
}
err = globalStack . Push ( token { FLOAT , f , line , fp } )
} else if toKind == BOOL {
err = globalStack . Push ( toBool ( fromToken ) )
} else if toKind == TYPE {
err = globalStack . Push ( token { TYPE , fromToken . kind , line , fp } )
} else {
return fmt . Errorf ( "Could not convert %s to %s" , kindToString ( fromKind ) , kindToString ( toKind ) )
}
if err != nil {
return err
}
return nil
2023-05-31 23:18:16 +00:00
}
2023-06-05 15:33:11 +00:00
2023-06-12 05:51:39 +00:00
func libFileExists ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
p , err := globalStack . Pop ( )
if err != nil {
return err
}
if p . kind != STRING {
return fmt . Errorf ( "`file-exists?` expected a STRING path on TOS but got %s" , kindToString ( p . kind ) )
}
path := ExpandedAbsFilepath ( p . val . ( string ) )
_ , err = os . Stat ( path )
if err == nil {
err = globalStack . Push ( token { BOOL , true , line , fp } )
} else {
err = globalStack . Push ( token { BOOL , false , line , fp } )
}
if err != nil {
return err
}
return nil
2023-06-05 15:33:11 +00:00
}
2023-06-12 05:51:39 +00:00
func libFileRemove ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
p , err := globalStack . Pop ( )
if err != nil {
return err
}
if p . kind != STRING {
return fmt . Errorf ( "`file-remove` expected a STRING path on TOS but got %s" , kindToString ( p . kind ) )
}
path := ExpandedAbsFilepath ( p . val . ( string ) )
err = os . RemoveAll ( path ) // Will remove DIRs as well, be careful
if err != nil {
return fmt . Errorf ( "`file-remove` could not remove the file(s): %s" , path )
}
return nil
2023-06-05 15:33:11 +00:00
}
2023-11-28 05:24:20 +00:00
func libFileAppend ( line int , fp string , doAppend bool ) error {
2024-05-13 20:52:47 +00:00
p , err := globalStack . Pop ( )
if err != nil {
return err
}
if p . kind != STRING {
return fmt . Errorf ( "`file-append` expected a STRING path on TOS but got %s" , kindToString ( p . kind ) )
}
data , err := globalStack . Pop ( )
if err != nil {
return err
}
path := ExpandedAbsFilepath ( p . val . ( string ) )
s := toString ( data , false )
var caller string
var f * os . File
if doAppend {
f , err = os . OpenFile ( path , os . O_APPEND | os . O_WRONLY | os . O_CREATE , 0644 )
caller = "file-append"
} else {
f , err = os . OpenFile ( path , os . O_TRUNC | os . O_WRONLY | os . O_CREATE , 0644 )
caller = "file-write"
}
if err != nil {
return fmt . Errorf ( "`%s` could not open or create the path: %s" , caller , path )
}
defer f . Close ( )
if err != nil {
return fmt . Errorf ( "`%s` opened but could not write to the path: %s" , caller , path )
}
_ , err = f . WriteString ( s )
return nil
2023-06-05 15:33:11 +00:00
}
2023-06-12 05:51:39 +00:00
func libFileRead ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
p , err := globalStack . Pop ( )
if err != nil {
return err
}
if p . kind != STRING {
return fmt . Errorf ( "`file-read` expected a STRING path on TOS but got %s" , kindToString ( p . kind ) )
}
path := ExpandedAbsFilepath ( p . val . ( string ) )
b , err := os . ReadFile ( path )
if err != nil {
return fmt . Errorf ( "`file-read` could not read from the file at path: %s" , path )
}
err = globalStack . Push ( token { STRING , string ( b ) , line , fp } )
if err != nil {
return err
}
return nil
2023-06-05 15:33:11 +00:00
}
2023-06-12 05:51:39 +00:00
func libDocstring ( line int , r * tokenReader , en * env , fp string ) error {
2024-05-13 20:52:47 +00:00
t , err := r . Read ( )
if err != nil {
return fmt . Errorf ( "Unexpectedly reached EOF" )
}
// There is a situation here where the parser does not expect
// to see a structure (proc, while, if) here and may mangle
// parsing, since there will be no close to the declaration.
// Make the parser smart enough to handle this case.
if t . kind == KEYWORD || t . kind == PROC || t . kind == IF || t . kind == WHILE {
var s string
switch t . kind {
case IF :
s = "if"
case WHILE :
s = "while"
case PROC :
if t . val . ( proc ) . hasArg {
s = "proc!"
} else {
s = "proc"
}
default :
s = t . val . ( string )
}
v , ok := kwDocstrings [ s ]
if ok {
err = globalStack . Push ( token { STRING , fmt . Sprintf ( "`%s`\n%s" , s , v ) , line , fp } )
if err != nil {
return err
}
return nil
}
} else if t . kind != SYMBOL && t . kind != KEYWORD {
return fmt . Errorf ( "`docstring!` expected to read in a SYMBOL or KEYWORD but found %s" , kindToString ( t . kind ) )
}
targetTokenEnv , err := en . Find ( t . val . ( string ) )
if err != nil {
return err
}
targetToken := targetTokenEnv . vars [ t . val . ( string ) ]
if targetToken . kind != PROC {
return fmt . Errorf ( "`docstring!` cannot read a docstring from %s" , kindToString ( targetToken . kind ) )
}
p := targetToken . val . ( proc )
s := fmt . Sprintf ( "`%s`\n%s" , t . val . ( string ) , p . doc )
err = globalStack . Push ( token { STRING , s , line , fp } )
if err != nil {
return err
}
return nil
2023-06-05 22:45:53 +00:00
}
2023-06-12 05:51:39 +00:00
func libInput ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
scanner := bufio . NewScanner ( os . Stdin )
scanner . Scan ( )
text := scanner . Text ( )
err := globalStack . Push ( token { STRING , text , line , fp } )
if err != nil {
return err
}
return nil
2023-06-06 06:04:58 +00:00
}
2023-06-12 05:51:39 +00:00
func libReMatch ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
t2 , err := globalStack . Pop ( )
if err != nil {
return err
}
t1 , err := globalStack . Pop ( )
if err != nil {
return err
}
if ! ( t1 . kind == STRING && t2 . kind == STRING ) {
return fmt . Errorf ( "`re-match?` expected two strings from the stack but found %s and %s" , kindToString ( t1 . kind ) , kindToString ( t2 . kind ) )
}
re , err := regexp . Compile ( t1 . val . ( string ) )
if err != nil {
return err
}
err = globalStack . Push ( token { BOOL , re . MatchString ( t2 . val . ( string ) ) , line , fp } )
if err != nil {
return err
}
return nil
2023-06-06 06:04:58 +00:00
}
2024-05-08 03:48:42 +00:00
func libReFind ( line int , fp string , all bool ) error {
2024-05-13 20:52:47 +00:00
t2 , err := globalStack . Pop ( )
if err != nil {
return err
}
t1 , err := globalStack . Pop ( )
if err != nil {
return err
}
if ! ( t1 . kind == STRING && t2 . kind == STRING ) {
return fmt . Errorf ( "`re-find` expected two strings from the stack but found %s and %s" , kindToString ( t1 . kind ) , kindToString ( t2 . kind ) )
}
re , err := regexp . Compile ( t1 . val . ( string ) )
if err != nil {
return err
}
var out token
if all {
matches := re . FindAllStringSubmatch ( t2 . val . ( string ) , - 1 )
tokens := make ( [ ] token , len ( matches ) )
for i := range matches {
subtokens := make ( [ ] token , len ( matches [ i ] ) )
for ii , ss := range matches [ i ] {
subtokens [ ii ] = token { STRING , ss , line , fp }
}
tokens [ i ] = token { LIST , list { subtokens } , line , fp }
}
out = token { LIST , list { tokens } , line , fp }
} else {
matches := re . FindStringSubmatch ( t2 . val . ( string ) )
tokens := make ( [ ] token , len ( matches ) )
for i , s := range matches {
tokens [ i ] = token { STRING , s , line , fp }
}
out = token { LIST , list { tokens } , line , fp }
}
err = globalStack . Push ( out )
if err != nil {
return err
}
return nil
2023-06-06 06:04:58 +00:00
}
2023-06-12 05:51:39 +00:00
func libReReplace ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
replacement , err := globalStack . Pop ( )
if err != nil {
return err
}
input , err := globalStack . Pop ( )
if err != nil {
return err
}
pattern , err := globalStack . Pop ( )
if err != nil {
return err
}
re , err := regexp . Compile ( toString ( pattern , false ) )
if err != nil {
return err
}
s := re . ReplaceAllString ( toString ( input , false ) , toString ( replacement , false ) )
err = globalStack . Push ( token { STRING , s , line , fp } )
if err != nil {
return err
}
return nil
2023-06-06 06:04:58 +00:00
}
2023-06-12 05:51:39 +00:00
func libSlice ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
to , err := globalStack . Pop ( )
if err != nil {
return err
}
from , err := globalStack . Pop ( )
if err != nil {
return err
}
if to . kind != INT || from . kind != INT {
return fmt . Errorf ( "`slice` expected two integers from the stack but found %s and %s" , kindToString ( from . kind ) , kindToString ( to . kind ) )
}
data , err := globalStack . Pop ( )
if err != nil {
return err
}
t := to . val . ( int )
f := from . val . ( int ) - 1
switch data . kind {
case STRING :
s := data . val . ( string )
if t < 0 {
t = len ( s ) + t + 1
}
if t > len ( s ) {
t = len ( s )
}
if f < 0 {
f = 0
}
if f > t {
f = t
}
err = globalStack . Push ( token { STRING , s [ f : t ] , line , fp } )
case LIST :
li := data . val . ( list )
l := li . body
if t < 0 {
t = len ( l ) + t + 1
}
if t > len ( l ) {
t = len ( l )
}
if f < 0 {
f = 0
}
if f > t {
f = t
}
err = globalStack . Push ( token { LIST , list { l [ f : t ] } , line , fp } )
default :
return fmt . Errorf ( "Cannot `slice` %s\n" , line , kindToString ( data . kind ) )
}
if err != nil {
return err
}
return nil
2023-06-07 06:17:21 +00:00
}
2023-06-12 05:51:39 +00:00
func libStackDepth ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
err := globalStack . Push ( token { INT , globalStack . Depth ( ) , line , fp } )
if err != nil {
return err
}
return nil
2023-06-07 06:17:21 +00:00
}
2023-06-08 05:59:53 +00:00
2023-06-12 05:51:39 +00:00
func libType ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
val , err := globalStack . Pop ( )
if err != nil {
return err
}
err = globalStack . Push ( token { TYPE , val . kind , line , fp } )
if err != nil {
return err
}
return nil
2023-06-08 05:59:53 +00:00
}
2023-06-12 05:51:39 +00:00
func libNetGet ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
val , err := globalStack . Pop ( )
if err != nil {
return err
}
respList := dict { make ( map [ string ] token ) }
respList . body [ "success" ] = token { BOOL , false , line , fp }
respList . body [ "status" ] = token { INT , - 1 , line , fp }
respList . body [ "body" ] = token { STRING , "The given string could not be parsed as a url" , line , fp }
u , err := url . Parse ( toString ( val , false ) )
if err != nil {
err = globalStack . Push ( token { DICT , respList , line , fp } )
if err != nil {
return err
}
return nil
}
switch u . Scheme {
case "http" , "https" :
resp , err := http . Get ( u . String ( ) )
if err != nil {
respList . body [ "status" ] = token { STRING , err . Error ( ) , line , fp }
break
}
defer resp . Body . Close ( )
success := true
status := resp . StatusCode
if status < 200 || status > 299 {
success = false
}
body , err := io . ReadAll ( resp . Body )
if err != nil {
respList . body [ "status" ] = token { INT , status , line , fp }
respList . body [ "body" ] = token { STRING , "Could not read response body, or reading was interuppted" , line , fp }
break
}
respList . body [ "success" ] = token { BOOL , success , line , fp }
respList . body [ "status" ] = token { INT , status , line , fp }
respList . body [ "body" ] = token { STRING , string ( body ) , line , fp }
case "gemini" :
status , resp := GeminiRequest ( u , 0 )
if status == 1 || status == 2 {
respList . body [ "success" ] = token { BOOL , true , line , fp }
} else {
respList . body [ "success" ] = token { BOOL , false , line , fp }
}
respList . body [ "status" ] = token { INT , status , line , fp }
respList . body [ "body" ] = token { STRING , resp , line , fp }
case "gopher" :
if u . Port ( ) == "" {
u . Host = u . Host + ":70"
}
conn , err := net . Dial ( "tcp" , u . Host )
if err != nil {
respList . body [ "body" ] = token { STRING , err . Error ( ) , line , fp }
break
}
defer conn . Close ( )
p := u . Path
if len ( u . Path ) < 2 {
p = "/" + "\n"
} else {
p = p [ 2 : ] + "\n"
}
_ , err = conn . Write ( [ ] byte ( p ) )
if err != nil {
respList . body [ "body" ] = token { STRING , err . Error ( ) , line , fp }
break
}
resp , err := io . ReadAll ( conn )
if err != nil {
respList . body [ "body" ] = token { STRING , err . Error ( ) , line , fp }
break
}
respList . body [ "success" ] = token { BOOL , true , line , fp }
respList . body [ "body" ] = token { STRING , string ( resp ) , line , fp }
default :
return fmt . Errorf ( "Unsupported URL scheme: %s\n" , line , u . Scheme )
}
err = globalStack . Push ( token { DICT , respList , line , fp } )
if err != nil {
return err
}
return nil
2023-06-08 05:59:53 +00:00
}
2023-06-12 05:51:39 +00:00
func libThrow ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
e , err := globalStack . Pop ( )
if err != nil {
return err
}
return fmt . Errorf ( "%s" , toString ( e , false ) )
2023-06-09 17:05:10 +00:00
}
2023-06-13 20:22:22 +00:00
func libImport ( line int , fp string , en * env ) error {
2024-05-13 20:52:47 +00:00
t , err := globalStack . Pop ( )
if err != nil {
return err
}
if t . kind != STRING {
return fmt . Errorf ( "`import` expects a STRING on TOS, not %s" , kindToString ( t . kind ) )
}
if en . Imported ( t . val . ( string ) ) {
return nil
}
switch t . val . ( string ) {
case "std" :
err = lexParseInterpret ( stdImport , en , "std" )
if err != nil {
return err
}
en . imports [ t . val . ( string ) ] = true
case "math" :
err = lexParseInterpret ( mathImport , en , "math" )
if err != nil {
return err
}
en . imports [ t . val . ( string ) ] = true
case "stack" :
err = lexParseInterpret ( stackImport , en , "stack" )
if err != nil {
return err
}
en . imports [ t . val . ( string ) ] = true
case "paths" :
err = lexParseInterpret ( pathsImport , en , "paths" )
if err != nil {
return err
}
en . imports [ t . val . ( string ) ] = true
case "strings" :
err = lexParseInterpret ( stringsImport , en , "strings" )
if err != nil {
return err
}
en . imports [ t . val . ( string ) ] = true
default :
p := ExpandedAbsFilepath ( t . val . ( string ) )
s , err := readFile ( p )
if err != nil {
return err
}
err = lexParseInterpret ( s , en , p )
if err != nil {
return err
}
en . imports [ t . val . ( string ) ] = true
}
return nil
2023-06-13 20:22:22 +00:00
}
2023-06-14 03:32:23 +00:00
func libEach ( line int , r * tokenReader , en * env , fp string ) error {
2024-05-13 20:52:47 +00:00
li , err := globalStack . Pop ( )
if err != nil {
return err
}
if li . kind != LIST {
return fmt . Errorf ( "'each!' expected a LIST on TOS but found %s" , kindToString ( li . kind ) )
}
l := li . val . ( list ) . body
t , err := r . Read ( )
if err != nil {
return fmt . Errorf ( "Unexpectedly reached EOF" )
}
if t . kind == SYMBOL {
e , err := en . Find ( t . val . ( string ) )
if err != nil {
return err
}
val := e . vars [ t . val . ( string ) ]
if val . kind != PROC {
return fmt . Errorf ( "Cannot apply %s to list item in `each!`, expected PROC or KEYWORD" , kindToString ( val . kind ) )
}
} else if t . kind != KEYWORD {
return fmt . Errorf ( "A non-SYMBOL/non-KEYWORD value was given as an identifier to `each!`" )
}
for i := range l {
err = interpret ( [ ] token { l [ i ] , t } , en )
if err != nil {
return err
}
}
return nil
2023-06-14 03:32:23 +00:00
}
2023-06-14 21:03:05 +00:00
func libFilter ( line int , r * tokenReader , en * env , fp string ) error {
2024-05-13 20:52:47 +00:00
li , err := globalStack . Pop ( )
if err != nil {
return err
}
if li . kind != LIST {
return fmt . Errorf ( "'filter!' expected a LIST on TOS but found %s" , kindToString ( li . kind ) )
}
l := li . val . ( list ) . body
t , err := r . Read ( )
if err != nil {
return fmt . Errorf ( "Unexpectedly reached EOF" )
}
if t . kind == SYMBOL {
e , err := en . Find ( t . val . ( string ) )
if err != nil {
return err
}
val := e . vars [ t . val . ( string ) ]
if val . kind != PROC {
return fmt . Errorf ( "Cannot apply %s to list item in `filter!`, expected PROC or KEYWORD" , kindToString ( val . kind ) )
}
} else if t . kind != KEYWORD {
return fmt . Errorf ( "A non-SYMBOL/non-KEYWORD value was given as an identifier to `filter!`" )
}
newList := make ( [ ] token , 0 , len ( l ) )
for i := range l {
err = interpret ( [ ] token { l [ i ] , t } , en )
if err != nil {
return err
}
b , err := globalStack . Pop ( )
if err != nil {
return err
}
if toBoolRaw ( b ) {
newList = append ( newList , l [ i ] )
}
}
err = globalStack . Push ( token { LIST , list { newList } , line , fp } )
if err != nil {
return err
}
return nil
2023-06-14 21:03:05 +00:00
}
2023-11-30 07:18:55 +00:00
func libTime ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
return globalStack . Push ( token { INT , int ( time . Now ( ) . Unix ( ) ) , line , fp } )
2023-11-30 07:18:55 +00:00
}
func libToChar ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
t , err := globalStack . Pop ( )
if err != nil {
return err
}
if t . kind == STRING {
if len ( t . val . ( string ) ) == 0 {
return fmt . Errorf ( "Cannot convert empty STRING to char INT" )
}
return globalStack . Push ( token { INT , int ( rune ( t . val . ( string ) [ 0 ] ) ) , line , fp } )
} else if t . kind == INT {
if t . val . ( int ) < 0 {
return fmt . Errorf ( "Cannot convert negative INT to STRING as a char" )
}
return globalStack . Push ( token { STRING , string ( rune ( t . val . ( int ) ) ) , line , fp } )
}
return fmt . Errorf ( "Cannot convert %s to or from a char" , kindToString ( t . kind ) )
2023-11-30 07:18:55 +00:00
}
2023-12-23 23:50:49 +00:00
func libEnvGet ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
t , err := globalStack . Pop ( )
if err != nil {
return err
}
if t . kind != STRING {
return fmt . Errorf ( "Non-STRING value on TOS" )
}
v := os . Getenv ( t . val . ( string ) )
return globalStack . Push ( token { STRING , v , line , fp } )
2023-12-23 23:50:49 +00:00
}
func libEnvSet ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
t , err := globalStack . Pop ( )
if err != nil {
return err
}
if t . kind != STRING {
return fmt . Errorf ( "Non-STRING value on TOS" )
}
val , err := globalStack . Pop ( )
if err != nil {
return err
}
err = os . Setenv ( t . val . ( string ) , toString ( val , false ) )
if err != nil {
return err
}
return nil
2023-12-25 06:45:40 +00:00
}
2024-05-08 03:48:42 +00:00
func libChangeDir ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
t , err := globalStack . Pop ( )
if err != nil {
return err
}
if t . kind != STRING {
return fmt . Errorf ( "`cd` expected a STRING on TOS but found %s on line %d of %s" , kindToString ( t . kind ) , line , fp )
}
err = os . Chdir ( ExpandedAbsFilepath ( toString ( t , false ) ) )
if err != nil {
return fmt . Errorf ( "Could not change directory to %s, line %d of %s" , toString ( t , true ) , line , fp )
}
return nil
2024-05-08 03:48:42 +00:00
}
func libPresentDir ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
d , err := os . Getwd ( )
if err != nil {
return err
}
return globalStack . Push ( token { STRING , d , line , fp } )
2024-05-08 03:48:42 +00:00
}
2023-12-25 06:45:40 +00:00
func libSubprocess ( line int , fp string , asReturn bool , takeInput bool ) error {
2024-05-13 20:52:47 +00:00
t , err := globalStack . Pop ( )
if err != nil {
return err
}
var in string
if takeInput {
i , err := globalStack . Pop ( )
if err != nil {
return err
}
in = toString ( i , false )
}
path := ""
var cmd * exec . Cmd
var args [ ] string
if t . kind == LIST {
// If the list is empty, just return nil
if len ( t . val . ( list ) . body ) == 0 {
return nil
}
path = toString ( t . val . ( list ) . body [ 0 ] , false )
argSlice := t . val . ( list ) . body [ 1 : ]
args = make ( [ ] string , 0 , 3 )
for _ , v := range argSlice {
args = append ( args , toString ( v , false ) )
}
if len ( args ) == 0 {
cmd = exec . Command ( path )
} else {
cmd = exec . Command ( path , args ... )
}
} else {
path = toString ( t , false )
cmd = exec . Command ( path )
}
if takeInput {
cmd . Stdin = strings . NewReader ( in )
} else {
cmd . Stdin = os . Stdin
}
if asReturn {
var sout strings . Builder
var eout strings . Builder
cmd . Stdout = & sout
cmd . Stderr = & eout
err = cmd . Run ( )
out := token {
LIST ,
list {
[ ] token {
token { STRING , sout . String ( ) , line , fp } ,
token { STRING , eout . String ( ) , line , fp } ,
token { INT , 0 , line , fp } } } ,
line ,
fp }
if err != nil {
if exiterr , ok := err . ( * exec . ExitError ) ; ok {
if status , ok := exiterr . Sys ( ) . ( syscall . WaitStatus ) ; ok {
out . val . ( list ) . body [ 2 ] . val = status . ExitStatus ( )
}
} else {
return err
}
}
return globalStack . Push ( out )
} else {
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
err = cmd . Run ( )
out := token { INT , 0 , line , fp }
if err != nil {
if exiterr , ok := err . ( * exec . ExitError ) ; ok {
if status , ok := exiterr . Sys ( ) . ( syscall . WaitStatus ) ; ok {
out . val = status . ExitStatus ( )
}
} else {
return err
}
}
return globalStack . Push ( out )
}
2023-12-23 23:50:49 +00:00
}
2024-05-12 19:10:25 +00:00
func libMkdir ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
t , err := globalStack . Pop ( )
if err != nil {
return err
}
if t . kind != STRING {
return fmt . Errorf ( "`mkdir` expected a STRING but found %s on line %d of %s" , kindToString ( t . kind ) , line , fp )
}
if os . MkdirAll ( t . val . ( string ) , 0755 ) != nil {
2024-05-14 03:14:35 +00:00
return fmt . Errorf ( "`mkdir` was unable to create %q on line %d of %s" , t . val . ( string ) , line , fp )
2024-05-13 20:52:47 +00:00
}
2024-05-14 03:14:35 +00:00
return nil
2024-05-12 19:10:25 +00:00
}
2024-05-13 05:50:12 +00:00
func libKeys ( line int , fp string ) error {
2024-05-13 20:52:47 +00:00
t , err := globalStack . Pop ( )
if err != nil {
return err
}
if t . kind != DICT {
return fmt . Errorf ( "`keys` expected a DICT but found %s on line %d of %s" , kindToString ( t . kind ) , line , fp )
}
d := t . val . ( dict ) . body
o := make ( [ ] token , 0 , len ( d ) )
for k := range d {
o = append ( o , token { STRING , k , line , fp } )
}
return globalStack . Push ( token { LIST , list { o } , line , fp } )
2024-05-13 05:50:12 +00:00
}