2021-07-26 05:31:22 +00:00
package main
import (
"fmt"
2022-04-04 22:13:29 +00:00
"regexp"
2021-09-04 21:14:30 +00:00
"sort"
"strings"
2022-04-03 23:52:53 +00:00
"git.rawtext.club/sloum/slope/termios"
2021-07-26 05:31:22 +00:00
)
type proc struct {
2021-07-31 20:25:20 +00:00
params expression
body expression
en * env
2021-07-26 05:31:22 +00:00
}
func eval ( exp expression , en * env ) ( value expression ) {
switch e := exp . ( type ) {
case symbol :
2022-05-01 02:48:37 +00:00
ex , err := en . Find ( e )
if err != nil {
value = exception ( err . Error ( ) )
} else {
value = ex . vars [ e ]
}
2021-09-05 21:47:47 +00:00
case string :
value = unescapeString ( e )
case bool , number :
2021-08-17 20:44:24 +00:00
value = e
case exception :
if panicOnException {
panic ( string ( e ) )
}
2021-08-09 22:53:27 +00:00
value = e
2021-07-26 05:31:22 +00:00
case [ ] expression :
switch car , _ := e [ 0 ] . ( symbol ) ; car {
case "quote" :
2021-07-27 03:19:59 +00:00
if len ( e ) < 2 {
2021-09-07 05:44:27 +00:00
value = exception ( "Invalid 'quote' syntax - too few arguments" )
break
2021-07-27 03:19:59 +00:00
}
2021-07-26 05:31:22 +00:00
value = e [ 1 ]
case "if" :
2021-07-30 23:03:25 +00:00
if len ( e ) < 3 {
2021-09-07 05:44:27 +00:00
value = exception ( "Invalid 'if' syntax - too few arguments" )
break
2021-07-27 03:19:59 +00:00
}
2021-07-26 05:31:22 +00:00
if AnythingToBool ( eval ( e [ 1 ] , en ) ) . ( bool ) {
value = eval ( e [ 2 ] , en )
2021-07-30 23:03:25 +00:00
} else if len ( e ) > 3 {
2021-07-26 05:31:22 +00:00
value = eval ( e [ 3 ] , en )
2021-07-30 23:03:25 +00:00
} else {
value = make ( [ ] expression , 0 )
2021-07-26 05:31:22 +00:00
}
2021-08-01 22:13:25 +00:00
case "and" :
if len ( e ) < 2 {
2021-09-07 05:44:27 +00:00
value = exception ( "Invalid 'and' syntax - too few arguments" )
break
2021-08-01 22:13:25 +00:00
}
2021-09-04 14:45:35 +00:00
OuterAnd :
2021-08-01 22:13:25 +00:00
for i := range e [ 1 : ] {
switch item := e [ i + 1 ] . ( type ) {
case [ ] expression :
value = eval ( item , en )
if ! AnythingToBool ( value ) . ( bool ) {
break OuterAnd
}
default :
2021-08-25 22:16:13 +00:00
value = eval ( item , en )
2021-08-01 22:13:25 +00:00
if ! AnythingToBool ( value ) . ( bool ) {
break OuterAnd
}
}
}
case "or" :
if len ( e ) < 2 {
2021-09-07 05:44:27 +00:00
value = exception ( "Invalid 'or' syntax - too few arguments" )
break
2021-08-01 22:13:25 +00:00
}
2021-09-04 14:45:35 +00:00
OuterOr :
2021-08-01 22:13:25 +00:00
for i := range e [ 1 : ] {
switch item := e [ i + 1 ] . ( type ) {
case [ ] expression :
value = eval ( item , en )
if AnythingToBool ( value ) . ( bool ) {
break OuterOr
}
default :
2021-08-25 22:16:13 +00:00
value = eval ( item , en )
2021-08-01 22:13:25 +00:00
if AnythingToBool ( value ) . ( bool ) {
break OuterOr
}
}
}
2021-07-27 03:19:59 +00:00
case "cond" :
if len ( e ) < 2 {
2021-09-07 05:44:27 +00:00
value = exception ( "Invalid 'cond' syntax - too few arguments" )
break
2021-07-27 03:19:59 +00:00
}
2021-09-19 23:05:11 +00:00
CondLoop :
2021-07-27 03:19:59 +00:00
for _ , exp := range e [ 1 : ] {
switch i := exp . ( type ) {
case [ ] expression :
if len ( i ) < 2 {
2021-09-07 05:44:27 +00:00
value = exception ( "Invalid 'cond' case, cases must take the form: `(<test> <expression>)" )
2021-09-08 03:35:08 +00:00
break CondLoop
2021-07-27 03:19:59 +00:00
}
2021-08-02 06:06:52 +00:00
if i [ 0 ] == "else" || i [ 0 ] == symbol ( "else" ) {
2021-09-07 05:44:27 +00:00
value = eval ( i [ 1 ] , en )
2021-09-08 03:35:08 +00:00
break CondLoop
2021-08-02 06:06:52 +00:00
}
if AnythingToBool ( eval ( i [ 0 ] , en ) ) . ( bool ) {
2021-09-07 05:44:27 +00:00
value = eval ( i [ 1 ] , en )
2021-09-08 03:35:08 +00:00
break CondLoop
2021-07-27 03:19:59 +00:00
}
default :
2021-09-07 05:44:27 +00:00
value = exception ( "Invalid 'cond' case, cases must take the form: `(<test> <expression>)" )
2021-09-08 03:35:08 +00:00
break CondLoop
2021-07-27 03:19:59 +00:00
}
}
2022-05-03 23:35:29 +00:00
if value == nil {
value = make ( [ ] expression , 0 )
}
2021-07-26 05:31:22 +00:00
case "set!" :
2021-07-27 03:19:59 +00:00
if len ( e ) < 3 {
2021-09-07 05:44:27 +00:00
value = exception ( "Invalid 'set!' syntax - too few arguments" )
break
2021-07-27 03:19:59 +00:00
}
2021-08-23 22:39:48 +00:00
v , ok := e [ 1 ] . ( symbol )
if ! ok {
2021-09-07 05:44:27 +00:00
value = exception ( "'set!' expected a symbol as its first argument, a non-symbol was provided" )
break
2021-08-23 22:39:48 +00:00
}
2021-08-16 21:53:52 +00:00
val := eval ( e [ 2 ] , en )
2022-05-01 02:48:37 +00:00
ex , err := en . Find ( v )
if err != nil {
value = exception ( err . Error ( ) )
} else {
ex . vars [ v ] = val
value = val
}
2021-07-26 05:31:22 +00:00
case "define" :
2021-07-27 03:19:59 +00:00
if len ( e ) < 3 {
2021-09-07 05:44:27 +00:00
value = exception ( "Invalid 'define' syntax - too few arguments" )
break
2021-07-27 03:19:59 +00:00
}
if _ , ok := e [ 1 ] . ( symbol ) ; ! ok {
2021-09-07 05:44:27 +00:00
value = exception ( "'define' expects a symbol as its first argument" )
break
2021-07-27 03:19:59 +00:00
}
2021-08-16 21:53:52 +00:00
val := eval ( e [ 2 ] , en )
en . vars [ e [ 1 ] . ( symbol ) ] = val
value = val
2021-08-02 21:37:57 +00:00
case "lambda" , "λ" :
if len ( e ) < 3 {
2021-09-07 05:44:27 +00:00
value = exception ( "'lambda' expects at least three arguments" )
break
2021-08-02 21:37:57 +00:00
}
2021-08-01 21:17:02 +00:00
b := [ ] expression { symbol ( "begin" ) }
b = append ( b , e [ 2 : ] ... )
value = proc { e [ 1 ] , b , en }
2021-07-26 05:31:22 +00:00
case "begin" :
for _ , i := range e [ 1 : ] {
value = eval ( i , en )
}
2021-07-31 06:31:08 +00:00
case "begin0" :
for ii , i := range e [ 1 : ] {
if ii == 0 {
value = eval ( i , en )
} else {
eval ( i , en )
}
}
2021-08-23 22:39:48 +00:00
case "usage" :
2022-04-04 22:18:01 +00:00
var procSigRE = regexp . MustCompile ( ` (?s)(\()([^() ]+\b)([^)]*)(\))(?:(\s*=>)([^\n]*))?(.*) ` )
var replacer = "\033[40;33;1m$1\033[95m$2\033[92m$3\033[33m$4\033[94m$5\033[36m$6\033[0m$7"
2021-09-04 21:14:30 +00:00
if len ( e ) < 2 {
var out strings . Builder
2022-04-04 22:13:29 +00:00
header := "(usage [[procedure: symbol]])\n\n\033[1;4mKnown procedures\033[0m\n\n"
out . WriteString ( procSigRE . ReplaceAllString ( header , replacer ) )
2022-04-03 23:52:53 +00:00
keys := make ( [ ] string , 0 , len ( usageStrings ) )
2021-09-04 21:14:30 +00:00
for key , _ := range usageStrings {
keys = append ( keys , key )
}
2022-04-03 23:56:50 +00:00
var width int = 60
if globalenv . vars [ symbol ( "slope-interactive?" ) ] != false {
width , _ = termios . GetWindowSize ( )
}
2022-04-03 23:52:53 +00:00
printedWidth := 0
2021-09-04 21:14:30 +00:00
sort . Strings ( keys )
for i := range keys {
2022-04-03 23:52:53 +00:00
if printedWidth + 26 >= width {
out . WriteRune ( '\n' )
printedWidth = 0
}
2021-09-04 21:14:30 +00:00
out . WriteString ( fmt . Sprintf ( "%-26s" , keys [ i ] ) )
2022-04-03 23:52:53 +00:00
printedWidth += 26
}
if len ( moduleUsageStrings ) > 0 {
out . WriteString ( "\n\n\033[1;4mKnown modules\033[0m\n\n" )
for k , _ := range moduleUsageStrings {
out . WriteString ( k )
2021-09-04 21:14:30 +00:00
out . WriteRune ( '\n' )
}
}
2022-04-01 22:21:46 +00:00
2022-04-28 05:59:02 +00:00
SysoutPrint ( out . String ( ) , Sysout )
2021-09-07 05:44:27 +00:00
value = make ( [ ] expression , 0 )
break
2022-04-01 22:21:46 +00:00
} else if len ( e ) == 2 {
proc , ok := e [ 1 ] . ( string )
if ! ok {
p , ok2 := e [ 1 ] . ( symbol )
if ! ok2 {
value = exception ( "'usage' expected a string or symbol as its first argument, a non-string non-symbol value was given" )
break
}
proc = string ( p )
}
v , ok := usageStrings [ proc ]
if ! ok {
2022-04-28 05:59:02 +00:00
SysoutPrint ( fmt . Sprintf ( "%q does not have a usage definition\n" , proc ) , Sysout )
2022-04-01 22:21:46 +00:00
} else {
2022-04-28 05:59:02 +00:00
SysoutPrint ( fmt . Sprintf ( "%v\n\n" , procSigRE . ReplaceAllString ( v , replacer ) ) , Sysout )
2021-08-23 22:39:48 +00:00
}
} else {
2022-04-01 22:21:46 +00:00
module , ok := e [ 1 ] . ( string )
if ! ok {
m , ok2 := e [ 1 ] . ( symbol )
if ! ok2 {
value = exception ( "'usage' expected a string or symbol as its first argument, a non-string non-symbol value was given" )
break
}
module = string ( m )
}
modMap , ok := moduleUsageStrings [ module ]
if ! ok {
value = exception ( "'usage' expected a string or symbol representing the name of a loaded module as its first argument, there is no loaded module with the given name/value" )
break
}
funcName := String ( e [ 2 ] , false )
if funcName == "" || funcName == "#f" {
2022-04-28 05:59:02 +00:00
SysoutPrint ( fmt . Sprintf ( "\033[1;4m%s's Known Procedures\033[0m\n\n" , module ) , Sysout )
2022-04-01 22:21:46 +00:00
for k := range modMap {
2022-04-28 05:59:02 +00:00
SysoutPrint ( fmt . Sprintf ( "%v\n" , k ) , Sysout )
2022-04-01 22:21:46 +00:00
}
} else {
subFunc , ok := modMap [ funcName ]
if ! ok {
value = exception ( "'usage' could not find the requested symbol within the " + module + "module's usage data" )
break
}
2022-04-28 05:59:02 +00:00
SysoutPrint ( fmt . Sprintf ( "%v\n" , procSigRE . ReplaceAllString ( subFunc , replacer ) ) , Sysout )
2022-04-01 22:21:46 +00:00
}
2021-08-23 22:39:48 +00:00
}
value = make ( [ ] expression , 0 )
2021-08-02 21:37:57 +00:00
case "load" :
if en . outer != nil {
2021-09-30 05:57:18 +00:00
value = exception ( "'load' is only callable from the global/top-level" )
2021-09-07 05:44:27 +00:00
break
2021-08-02 21:37:57 +00:00
}
2022-05-25 21:11:44 +00:00
files := make ( [ ] expression , 0 , len ( e ) - 1 )
2021-08-02 21:37:57 +00:00
for _ , fp := range e [ 1 : ] {
2022-05-25 21:15:40 +00:00
var p string
if _ , ok := fp . ( [ ] expression ) ; ok {
p = String ( eval ( fp , en ) , false )
} else {
p = String ( fp , false )
}
2022-05-25 21:11:44 +00:00
files = append ( files , p )
2021-08-17 05:20:12 +00:00
}
2022-05-25 21:11:44 +00:00
loadFiles ( files )
2021-08-17 05:20:12 +00:00
value = symbol ( "ok" )
case "load-mod" :
fullLoadEnv := env { make ( map [ symbol ] expression ) , & globalenv }
for _ , fp := range e [ 1 : ] {
2022-05-25 21:15:40 +00:00
var p string
if _ , ok := fp . ( [ ] expression ) ; ok {
p = String ( eval ( fp , en ) , false )
} else {
p = String ( fp , false )
}
2022-05-25 21:11:44 +00:00
modEnv , err := RunModule ( p , false )
if err != nil {
panic ( fmt . Errorf ( "'load-mod' failed loading module %s: %s" , p , err . Error ( ) ) )
}
for k , v := range modEnv . vars {
if k == "_USAGE" {
// Add helper text if available
helpOut := make ( map [ string ] string )
if val , ok := v . ( [ ] expression ) ; ok {
for _ , helpPair := range val {
switch p := helpPair . ( type ) {
case [ ] expression :
if len ( p ) < 2 {
break
2022-04-01 22:21:46 +00:00
}
2022-05-25 21:11:44 +00:00
helpOut [ String ( p [ 0 ] , false ) ] = String ( p [ 1 ] , false )
2022-04-01 22:21:46 +00:00
}
}
}
2022-05-25 21:11:44 +00:00
if len ( helpOut ) > 0 {
moduleUsageStrings [ p ] = helpOut
}
} else {
// Otherwise add to the global symbol table
fullLoadEnv . vars [ k ] = v
2021-08-17 05:20:12 +00:00
}
2021-08-17 21:59:34 +00:00
}
}
2022-04-01 22:21:46 +00:00
2021-08-17 21:59:34 +00:00
for k , v := range fullLoadEnv . vars {
globalenv . vars [ k ] = v
}
value = symbol ( "ok" )
case "load-mod-file" :
fullLoadEnv := env { make ( map [ symbol ] expression ) , & globalenv }
for _ , fp := range e [ 1 : ] {
2022-05-25 21:15:40 +00:00
var p string
if _ , ok := fp . ( [ ] expression ) ; ok {
p = String ( eval ( fp , en ) , false )
} else {
p = String ( fp , false )
}
2022-05-25 21:11:44 +00:00
modEnv , err := RunModule ( p , true )
if err != nil {
panic ( fmt . Errorf ( "'load-mod-file' failed loading module %s: %s" , p , err . Error ( ) ) )
}
for k , v := range modEnv . vars {
fullLoadEnv . vars [ k ] = v
2021-08-17 05:20:12 +00:00
}
}
for k , v := range fullLoadEnv . vars {
globalenv . vars [ k ] = v
2021-08-02 21:37:57 +00:00
}
value = symbol ( "ok" )
2022-05-07 22:02:38 +00:00
case "exists?" :
if len ( e ) == 0 {
value = exception ( "'exists?' expects at least one symbol or string, no values were given" )
}
value = true
2022-05-25 21:11:44 +00:00
var err error
2022-05-07 22:02:38 +00:00
for i := range e {
if i == 0 {
continue
}
doPanic := panicOnException
if doPanic {
panicOnException = false
}
2022-05-25 21:11:44 +00:00
_ , ok := e [ i ] . ( [ ] expression )
if ok {
_ , err = en . Find ( symbol ( String ( eval ( e [ i ] , en ) , false ) ) )
} else {
_ , err = en . Find ( symbol ( String ( e [ i ] , false ) ) )
}
2022-05-07 22:02:38 +00:00
if doPanic {
2022-05-25 21:11:44 +00:00
panicOnException = true
2022-05-07 22:02:38 +00:00
}
if err != nil {
value = false
break
}
}
2021-08-03 21:08:39 +00:00
case "apply" :
if len ( e ) < 3 {
2021-09-30 05:57:18 +00:00
value = exception ( "'apply' expects two arguments: a procedure and an argument list, too few arguments were given" )
2021-09-07 05:44:27 +00:00
break
2021-08-03 21:08:39 +00:00
}
args := eval ( e [ 2 ] , en )
switch item := args . ( type ) {
case [ ] expression :
2021-09-07 05:44:27 +00:00
value = apply ( eval ( e [ 1 ] , en ) , item )
2021-08-03 21:08:39 +00:00
default :
2021-09-07 05:44:27 +00:00
value = apply ( eval ( e [ 1 ] , en ) , [ ] expression { item } )
2021-08-03 21:08:39 +00:00
}
2021-09-30 05:57:18 +00:00
case "eval" :
if len ( e ) < 1 {
value = exception ( "'eval' expects a string and an optional boolean to indicate that a string should be parsed and evaluated, but was not given any arguments" )
}
sParse := false
if len ( e ) >= 3 {
v , ok := e [ 2 ] . ( bool )
if ok {
sParse = v
}
}
switch item := eval ( e [ 1 ] , en ) . ( type ) {
case [ ] expression :
if _ , ok := item [ 0 ] . ( symbol ) ; ! ok {
value = item
} else {
value = eval ( item , en )
v , ok := value . ( string )
if ok && sParse {
p := Parse ( v )
value = eval ( p . ( [ ] expression ) [ 0 ] , en )
}
}
case string :
if sParse {
p := Parse ( item )
if l , ok := p . ( [ ] expression ) [ 0 ] . ( [ ] expression ) ; ok {
if _ , ok := l [ 0 ] . ( symbol ) ; ok {
value = eval ( p . ( [ ] expression ) [ 0 ] , en )
} else {
value = l
}
} else {
value = eval ( l [ 0 ] , en )
}
} else {
value = item
}
default :
value = item
}
2021-07-26 05:31:22 +00:00
default :
operands := e [ 1 : ]
values := make ( [ ] expression , len ( operands ) )
for i , x := range operands {
values [ i ] = eval ( x , en )
}
value = apply ( eval ( e [ 0 ] , en ) , values )
2021-07-31 20:25:20 +00:00
}
default :
2021-07-26 05:31:22 +00:00
panic ( "Unknown expression type encountered during EVAL" )
2021-07-31 20:25:20 +00:00
}
2021-09-07 05:44:27 +00:00
if e , ok := value . ( exception ) ; panicOnException && ok {
panic ( string ( e ) )
}
2021-07-31 20:25:20 +00:00
return
2021-07-26 05:31:22 +00:00
}
func apply ( procedure expression , args [ ] expression ) ( value expression ) {
switch p := procedure . ( type ) {
case func ( ... expression ) expression :
value = p ( args ... )
case proc : // Mostly used by lambda
en := & env { make ( vars ) , p . en }
switch params := p . params . ( type ) {
case [ ] expression :
2021-09-19 23:05:11 +00:00
if len ( params ) - variadic ( params ) > len ( args ) {
2021-09-30 05:57:18 +00:00
return exception ( fmt . Sprintf ( "Lambda expected %d arguments but received %d" , len ( params ) , len ( args ) ) )
2021-07-26 05:31:22 +00:00
}
for i , param := range params {
2022-05-01 02:48:37 +00:00
if param . ( symbol ) == symbol ( "args-list" ) || param . ( symbol ) == symbol ( "..." ) {
2021-09-19 23:05:11 +00:00
if len ( args ) >= len ( params ) {
en . vars [ param . ( symbol ) ] = args [ i : ]
} else {
en . vars [ param . ( symbol ) ] = make ( [ ] expression , 0 )
}
2021-08-01 03:04:26 +00:00
break
}
2021-07-26 05:31:22 +00:00
en . vars [ param . ( symbol ) ] = args [ i ]
}
default :
2021-08-01 06:31:38 +00:00
en . vars [ params . ( symbol ) ] = args
2021-07-26 05:31:22 +00:00
}
value = eval ( p . body , en )
default :
2021-09-30 05:57:18 +00:00
panic ( "Unknown procedure type encountered during APPLY: " + String ( procedure , true ) )
2021-07-26 05:31:22 +00:00
}
return
}