From 15e294085c1f6a5873cde6d0ec028a959f54c82a Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 24 Jul 2020 19:58:09 -0700 Subject: [PATCH 01/24] some rudimentary list operations --- stdlib/list.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ stdlib/stdlib.go | 22 ++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/stdlib/list.go b/stdlib/list.go index 748414b..863718e 100644 --- a/stdlib/list.go +++ b/stdlib/list.go @@ -18,6 +18,7 @@ package stdlib import ( + "fmt" "gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/log" ) @@ -28,6 +29,7 @@ import ( * in event a not-list is passed in, returns the arg. */ func Expand(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { + input = input.Eval(funcs, vars, false) if input.Tag != ast.LIST { log.Log(log.INFO, "expand called on not a list", "expand") return input @@ -42,6 +44,7 @@ func Expand(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token * if no args are a list, a list is made from all args */ func L_append(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { + input = input.Eval(funcs, vars, false) src := input if input.Tag != ast.LIST { @@ -68,3 +71,76 @@ func L_append(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Tok return src } + +/* Len + * Returns length of list as a number + * Returns nil if not a list + * + * Example: () -> 0 + * Example: (1 2 3) -> 3 + */ +func Len(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { + input = input.Eval(funcs, vars, false) + if input.Tag != ast.LIST { + log.Log(log.ERR, + "non-list as parameter to head", + "head") + return nil + } + + length := 0 + for iter := input.Expand(); iter != nil; iter = iter.Next { + length += 1 + } + + ret := &ast.Token{Tag: ast.NUMBER} + ret.Set(fmt.Sprintf("%d", length)) + return ret +} + +/* Head + * Returns first element in the list + * Returns nil if input is not a list or if list is empty + * + * Example: (head (2 3 4)) -> 2 + */ +func Head(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { + input = input.Eval(funcs, vars, false) + if input.Tag != ast.LIST { + log.Log(log.ERR, + "non-list as parameter to head", + "head") + return nil + } + + li := input.Expand().Copy() + if li != nil { + li.Next = nil + } + + return li +} + +/* Tail + * Returns last element in a list + * Returns nil if not a list + * + * Example: (tail (2 3 4)) -> 4 + */ +func Tail(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { + input = input.Eval(funcs, vars, false) + if input.Tag != ast.LIST { + log.Log(log.ERR, + "non-list as parameter to head", + "head") + return nil + } + + + iter := input.Expand() + for iter != nil && iter.Next != nil { + iter = iter.Next + } + + return iter.Copy() +} diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 292b7da..2479249 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -59,6 +59,28 @@ func GenFuncTable() ast.FuncTable { Args: 3, }, + "len": &ast.Function{ + Function: Len, + Name: "len", + TimesCalled: 0, + Args: 1, + }, + + "head": &ast.Function{ + Function: Head, + Name: "head", + TimesCalled: 0, + Args: 1, + }, + + "tail": &ast.Function{ + Function: Tail, + Name: "tail", + TimesCalled: 0, + Args: 1, + }, + + "export": &ast.Function{ Function: Export, Name: "export", From adff10b56a77cd39463f24abd0d1bdd42a93fff4 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 24 Jul 2020 20:32:08 -0700 Subject: [PATCH 02/24] added slice function --- stdlib/list.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++ stdlib/stdlib.go | 6 +++ 2 files changed, 107 insertions(+) diff --git a/stdlib/list.go b/stdlib/list.go index 863718e..8f5d35e 100644 --- a/stdlib/list.go +++ b/stdlib/list.go @@ -19,6 +19,7 @@ package stdlib import ( "fmt" + "strconv" "gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/log" ) @@ -144,3 +145,103 @@ func Tail(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { return iter.Copy() } + +/* Slice + * Takes 3 args and returns a list + * Arg 1: starting index of sublist + * Arg 2: end index of sublist + * Arg 3: source list + * returns sublist, or nil if non list applied, or nil if start or end arent INTEGERS + * first index in a list is 0 + * + * Example: (slice 1 2 (1 2 3)) -> (2 3) + * Example: (slice 0 0 (1 2 3)) -> (1) + */ +func Slice(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { + input = input.Eval(funcs, vars, false) + start := input + end := input.Next + source := end.Next + + if start.Tag != ast.NUMBER || end.Tag != ast.NUMBER { + log.Log(log.ERR, + "start and end must both be integers", + "slice") + return nil + } + + if source.Tag != ast.LIST { + log.Log(log.ERR, + "non-list as parameter to head", + "head") + return nil + } + + st, err := strconv.ParseInt(start.Value(), 10, 64) + en, errr := strconv.ParseInt(end.Value(), 10, 64) + + if err != nil { + log.Log(log.ERR, + "couldnt parse integer from start value: " + err.Error(), + "slice") + return nil + } + + if errr != nil { + log.Log(log.ERR, + "couldnt parse integer from end value: " + errr.Error(), + "slice") + return nil + } + + if st < 0 || en < 0 { + log.Log(log.ERR, + "both indices must be positive", + "slice") + return nil + } + + if st > en { + log.Log(log.ERR, + "end index must be greater than start index", + "slice") + return nil + } + + en = en - st + var inner *ast.Token + buildIter := &inner + sourceIter := source.Expand() + + for st > 0 { + if sourceIter == nil { + log.Log(log.ERR, + "start index out of bounds", + "slice") + return nil + } + + sourceIter = sourceIter.Next + st -= 1 + } + + for en >= 0 { + if sourceIter == nil { + log.Log(log.ERR, + "end index out of bounds", + "slice") + return nil + } + + *buildIter = sourceIter.Copy() + (*buildIter).Next = nil + + buildIter = &((*buildIter).Next) + sourceIter = sourceIter.Next + en -= 1 + } + + ret := &ast.Token{Tag: ast.LIST} + ret.Direct(inner) + return ret +} diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 2479249..9080b15 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -80,6 +80,12 @@ func GenFuncTable() ast.FuncTable { Args: 1, }, + "slice": &ast.Function{ + Function: Slice, + Name: "slice", + TimesCalled: 0, + Args: 3, + }, "export": &ast.Function{ Function: Export, From 530dbe7e21cb925449a203008ed5afb618046085 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 24 Jul 2020 20:37:46 -0700 Subject: [PATCH 03/24] EOF aborts shell --- cmd/shs.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/shs.go b/cmd/shs.go index 82fbf8d..cf58d44 100644 --- a/cmd/shs.go +++ b/cmd/shs.go @@ -149,6 +149,11 @@ func main() { fmt.Printf(prePrompt) text, err := line.Prompt(prompt) if err != nil && err != liner.ErrPromptAborted{ + // must be a better way to do this check + if err.Error() == "EOF" { + return + } + log.Log(log.ERR, "couldnt read user input: " + err.Error(), "repl") continue } From bc8ed0712566d918c0463d0466304f73da0b965b Mon Sep 17 00:00:00 2001 From: Aidan Date: Wed, 29 Jul 2020 06:46:54 -0700 Subject: [PATCH 04/24] clean up segfaults on bad variable decls --- stdlib/vars.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/stdlib/vars.go b/stdlib/vars.go index 52938f2..3856378 100644 --- a/stdlib/vars.go +++ b/stdlib/vars.go @@ -34,6 +34,12 @@ func Export(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token name := input form := name.Next.Eval(funcs, vars, false) + + // error in eval process + if form == nil { + return nil + } + if name.Tag != ast.SYMBOL { log.Log(log.ERR, "first arg should be a symbol", From 61dd498d2767529ad2616598f1c40602784da76e Mon Sep 17 00:00:00 2001 From: Aidan Date: Wed, 29 Jul 2020 07:41:35 -0700 Subject: [PATCH 05/24] fix comment parsing --- ast/lex.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ast/lex.go b/ast/lex.go index 3031cab..6f672e5 100644 --- a/ast/lex.go +++ b/ast/lex.go @@ -162,14 +162,13 @@ func lex(input string) *Token { // comment case case ';': + i = matchLineEnd(i) start_pos = i + 1 - i = matchLineEnd(start_pos) } if needs_alloc { needs_alloc = false if (i < 0) { - // TODO: Maybe not overload this. start_pos = i goto error } @@ -186,8 +185,6 @@ func lex(input string) *Token { return ret error: - // TODO: Hook into error module - // TODO: Finalize and GC alloced tokens if start_pos == -1 { log.Log(log.ERR, "Unmatched string delimiter in input. discarding.", From 546e1711e549c03b56cab5e829dee3e56376c58d Mon Sep 17 00:00:00 2001 From: Aidan Date: Thu, 13 Aug 2020 12:14:10 -0700 Subject: [PATCH 06/24] handle escaped spaces in tokens + filepaths in completions --- ast/lex.go | 9 +++++++++ util/shell_complete.go | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ast/lex.go b/ast/lex.go index 6f672e5..d7db581 100644 --- a/ast/lex.go +++ b/ast/lex.go @@ -164,6 +164,15 @@ func lex(input string) *Token { case ';': i = matchLineEnd(i) start_pos = i + 1 + + // this isnt to handle string escaping + // its only to make sure that escaped spaces stay in + // the same token. + case '\\': + if i != len(input) - 1 && input[i+1] == ' '{ + // eat the backslash + input = input[:i] + input[i+1:] + } } if needs_alloc { diff --git a/util/shell_complete.go b/util/shell_complete.go index 86410d8..197b51b 100644 --- a/util/shell_complete.go +++ b/util/shell_complete.go @@ -80,7 +80,8 @@ func ShellCompleter(line string, vt ast.VarTable, ft ast.FuncTable) []string { completions := []string{} for _, i := range compSource { if strings.HasPrefix(i, tail) { - completions = append(completions, head + i) + str := strings.ReplaceAll(i, " ", "\\ ") + completions = append(completions, head + str) } } From 37e6e24447936225a459a2ebc0fee84ba09dd0d0 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 14 Aug 2020 15:59:38 -0700 Subject: [PATCH 07/24] add split function --- stdlib/stdlib.go | 7 +++++++ stdlib/string.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 9080b15..83f40fd 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -143,6 +143,13 @@ func GenFuncTable() ast.FuncTable { Args: -1, }, + "split": &ast.Function{ + Function: Split, + Name: "split", + TimesCalled: 0, + Args: 2, + }, + "exit": &ast.Function{ Function: ExitShell, Name: "exit", diff --git a/stdlib/string.go b/stdlib/string.go index 3cae616..e9f00be 100644 --- a/stdlib/string.go +++ b/stdlib/string.go @@ -19,6 +19,7 @@ package stdlib import ( "fmt" + "strings" "strconv" "gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/log" @@ -59,6 +60,47 @@ func StrCast(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { return res } +/* Takes 2 arguments, a string and a delimiter + * returns a list of substrings found delimited by the delimiter from the parent string + * Filters out all empty segments between delimiters + * + * Example: (split "/path/to/file" "/") -> ("path" "to" "file") + */ +func Split(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { + in = in.Eval(ft, vt, false) + if in == nil || in.Tag != ast.STRING || in.Next == nil || in.Next.Tag != ast.STRING { + log.Log(log.ERR, + "Args must be two strings", + "split") + return nil + } + + body := in.Value() + delim := in.Next.Value() + if len(body) < len(delim) { + log.Log(log.DEBUG, + "possibly mismatched args" + + "delimiter longer than body", + "split") + } + + var res *ast.Token + builder := &res + strtoks := strings.Split(body, delim) + for _, i := range strtoks { + if i == "" { + continue + } + + *builder = &ast.Token{Tag: ast.STRING} + (*builder).Set(i) + builder = &(*builder).Next + } + + return res +} + + /* Takes one arg, returns nil * Prints a string to stdout * Unquotes string so user can add escaped chars like \n, \t, etc From 69536783c71e0b018936e4381441e095a38cf3aa Mon Sep 17 00:00:00 2001 From: Aidan Date: Thu, 20 Aug 2020 23:39:57 -0700 Subject: [PATCH 08/24] added string join method --- stdlib/stdlib.go | 7 +++++++ stdlib/string.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 83f40fd..fcd0b30 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -143,6 +143,13 @@ func GenFuncTable() ast.FuncTable { Args: -1, }, + "join": &ast.Function{ + Function: Join, + Name: "join", + TimesCalled: 0, + Args: 2, + }, + "split": &ast.Function{ Function: Split, Name: "split", diff --git a/stdlib/string.go b/stdlib/string.go index e9f00be..0cfe9f0 100644 --- a/stdlib/string.go +++ b/stdlib/string.go @@ -100,6 +100,50 @@ func Split(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { return res } +/* Takes two args, a delimiter and a list of strings + * Returns the list of strings concatenated together with the delimiter in between each element + * On error returns nil + * + * Example: (join ", " ("apple" "ananas" "pear")) -> "apple, ananas, pear" + */ +func Join(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { + in = in.Eval(ft, vt, false) + if in == nil || in.Next == nil { + log.Log(log.ERR, + "one or more arguments evaluated to nil", + "join") + return nil + } + + delim := in + strs := in.Next + if delim.Tag != ast.STRING || strs.Tag != ast.LIST { + log.Log(log.ERR, + "first argument must be a string (delimiter) and second argument must be a list (strings)", + "join") + return nil + } + + de := delim.Value() + res := "" + for i := strs.Expand(); i != nil; i = i.Next { + if i.Tag != ast.STRING { + log.Log(log.ERR, + "all items to be joined must be strings", + "join") + return nil + } + + res += i.Value() + if i.Next != nil { + res += de + } + } + + ret := &ast.Token{Tag: ast.STRING} + ret.Set(res) + return ret +} /* Takes one arg, returns nil * Prints a string to stdout From 90284f2d06da32be013fef5f427a4be194b48c76 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 21 Aug 2020 00:14:31 -0700 Subject: [PATCH 09/24] negative number parsing --- ast/lex.go | 5 ++++ stdlib/stdlib.go | 7 +++++ stdlib/string.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/ast/lex.go b/ast/lex.go index d7db581..aa677f5 100644 --- a/ast/lex.go +++ b/ast/lex.go @@ -215,6 +215,11 @@ error: func StrIsNumber(arg string) bool { dotCount := 0 + // negative nums + if len(arg) > 0 && arg[0] == '-' { + arg = arg[1:] + } + for _, char := range arg { if !unicode.IsDigit(char) { if char == '.' && dotCount == 0 { diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index fcd0b30..699bbfb 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -157,6 +157,13 @@ func GenFuncTable() ast.FuncTable { Args: 2, }, + "substr": &ast.Function{ + Function: Substr, + Name: "substr", + TimesCalled: 0, + Args: 3, + }, + "exit": &ast.Function{ Function: ExitShell, Name: "exit", diff --git a/stdlib/string.go b/stdlib/string.go index 0cfe9f0..140600a 100644 --- a/stdlib/string.go +++ b/stdlib/string.go @@ -145,6 +145,80 @@ func Join(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { return ret } +/* takes three arguments: + * 1. start index + * 2. end index + * 3. source + * Returns a substring from source delimited by args 1 and 2. + * First two args must be integers (4 or 4.0 but not 4.3) + * + * Example: (substr 1 5 "Linus Torvalds") -> "inus " + */ +func Substr(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { + start := in.Eval(ft, vt, false) + if start == nil || start.Next == nil || start.Next.Next == nil { + log.Log(log.ERR, + "an argument evaluated to nil", + "substr") + return nil + } + + end := start.Next + str := end.Next + + if start.Tag != ast.NUMBER || end.Tag != ast.NUMBER || str.Tag != ast.STRING { + log.Log(log.ERR, + "incorrect types of args", + "substr") + return nil + } + + ed_idx := 0 + st_idx, err := strconv.Atoi(start.Value()) + ed_idx, err = strconv.Atoi(end.Value()) + if err != nil { + log.Log(log.ERR, + "error parsing args: " + err.Error(), + "substr") + return nil + } + + strlen := len(str.Value()) + if st_idx < 0 { + st_idx += strlen + } + + if ed_idx < 0 { + ed_idx += strlen + } + + if st_idx < 0 || st_idx >= strlen { + log.Log(log.ERR, + "first index out of bounds", + "substr") + return nil + } + + if ed_idx < 0 || ed_idx >= strlen { + log.Log(log.ERR, + "last index out of bounds", + "substr") + return nil + } + + if st_idx > ed_idx { + log.Log(log.ERR, + "start must be less than end", + "substr") + return nil + } + + res := str.Value()[st_idx:ed_idx] + ret := &ast.Token{Tag: ast.STRING} + ret.Set(res) + return ret +} + /* Takes one arg, returns nil * Prints a string to stdout * Unquotes string so user can add escaped chars like \n, \t, etc From ab340ceb0ad25260af7d620dd6262ed3d4031394 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 21 Aug 2020 01:37:04 -0700 Subject: [PATCH 10/24] perform arg type checking and evaluation before function call --- ast/func_table.go | 77 ++++++++++++--- stdlib/arith.go | 20 ---- stdlib/bool.go | 32 +----- stdlib/filesys.go | 57 +---------- stdlib/funcs.go | 3 +- stdlib/list.go | 59 +++-------- stdlib/shell.go | 31 ------ stdlib/stdlib.go | 247 ++++++++++++++++++++++++++-------------------- stdlib/string.go | 51 +--------- 9 files changed, 223 insertions(+), 354 deletions(-) diff --git a/ast/func_table.go b/ast/func_table.go index 734f9ee..f6c9da3 100644 --- a/ast/func_table.go +++ b/ast/func_table.go @@ -38,8 +38,18 @@ type Function struct { // number of times user has called this function TimesCalled int - // number of args required - Args int + // list of types (LIST, NUMBER, STRING, etc) representing args + Args []Token_t + NumArgs bool // -1 means infinite + + // lazy arg checking (use NumArgs instead of args) + ArgLazy bool + + // dont fail on undefined symbol (passed to eval when parsing args) + SymLazy bool + + // dont eval args at all, leave that to the function + EvalLazy bool } /* holds a mapping of key to function @@ -52,24 +62,52 @@ type FuncTable *map[string]*Function * makes sure correct arguments are passed in */ func (f Function) ParseFunction(args *Token) bool { - // handle infinite args - if f.Args < 0 { + total = len(f.Args) + for iter := args; iter != nil; iter = iter.Next { + total -= 1 + if total <= 0 { + log.Log(log.ERR, + "too many arguments", + "ftable") + return false + } + + if iter.Tag != f.Args[len(f.Args) - total] { + log.Log(log.ERR, + "argument of type " + GetTagAsStr(iter.Tag) + + "passed in when " + GetTagAsStr(f.Args[len(f.Args) - total]) + + " was expected", + "ftable") + return false + } + } + + return true +} + +/* same as ParseFunction but only evaluates the number of args + */ +func (f Function) LazyParseFunction(args *Token) bool { + if (f.NumArgs < 0) { return true } - i := f.Args + total := 0 for iter := args; iter != nil; iter = iter.Next { - i -= 1 + total += 1 } - if i != 0 { + if total < f.NumArgs { log.Log(log.ERR, - "Incorrect number of arguments", - "eval") - log.Log(log.DEBUG, - fmt.Sprintf("Function %s expects %d arguments. You've provided %d arguments.", - f.Name, f.Args, f.Args - i), - "eval") + "expected more arguments, try calling info on function", + "ftable") + return false + } + + if total > f.NumArgs { + log.Log(log.ERR, + "too many args. try calling info on function", + "ftable") return false } @@ -80,7 +118,18 @@ func (f Function) ParseFunction(args *Token) bool { * calls ParseFunction and increments TimesCalled */ func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token { - if !f.ParseFunction(args) { + if not f.EvalLazy { + args = args.Eval(ft, vt, !f.SymLazy) + } + + passes := false + if f.ArgsLazy { + passes = LazyParseFunction(args) + } else { + passes = ParseFunction(args) + } + + if passes { log.Log(log.ERR, "Couldnt call " + f.Name, "eval") diff --git a/stdlib/arith.go b/stdlib/arith.go index d516551..fb1e6d6 100644 --- a/stdlib/arith.go +++ b/stdlib/arith.go @@ -36,14 +36,6 @@ import ( * Example: (number "3.4") */ func NumCast(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token { - in = in.Eval(f, a, false) - if in.Tag != ast.STRING { - log.Log(log.ERR, - "only a string can successfully be cast to a number", - "number_cast") - return nil - } - if !ast.StrIsNumber(in.Value()) { log.Log(log.ERR, "string failed number cast", @@ -64,9 +56,6 @@ func NumCast(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token { */ func Add(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token { var res float64 - - in = in.Eval(f, a, false) - for i := in; i != nil; i = i.Next { if i.Tag != ast.NUMBER { log.Log(log.ERR, "Non-number given to ADD", "add") @@ -112,9 +101,6 @@ func Add(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token { func Sub(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token { var res float64 var sub float64 - - in = in.Eval(f, a, false) - for i := in; i != nil; i = i.Next { if i.Tag != ast.NUMBER { log.Log(log.ERR, "Non-number given to SUB", "sub") @@ -166,9 +152,6 @@ func Sub(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token { */ func Mult(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token { res := 1.0 - - in = in.Eval(f, a, false) - for i := in; i != nil; i = i.Next { if i.Tag != ast.NUMBER { log.Log(log.ERR, "Non-number given to MULT", "mult") @@ -214,9 +197,6 @@ func Mult(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token { */ func Div(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token { var res float64 - - in = in.Eval(f, a, false) - for i := in; i != nil; i = i.Next { inner := 0.0 diff --git a/stdlib/bool.go b/stdlib/bool.go index d6b07e6..833de51 100644 --- a/stdlib/bool.go +++ b/stdlib/bool.go @@ -30,14 +30,6 @@ import ( * Example: (bool "F") */ func BoolCast(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, false) - if in.Tag == ast.LIST || in.Tag == ast.NUMBER { - log.Log(log.ERR, - "only strings successfully cast to bool", - "bool cast") - return nil - } - body := in.Value() if body != ast.TRUE && body != ast.FALSE { log.Log(log.ERR, @@ -52,13 +44,6 @@ func BoolCast(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { } func Not(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, false) - - if in.Tag != ast.BOOL { - log.Log(log.ERR, "non-bool argument to 'not'", "not") - return nil - } - out := ast.TRUE if in.Value() == ast.TRUE { out = ast.FALSE @@ -69,10 +54,9 @@ func Not(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { return t } +// Lazy args this func Eq(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { out := ast.TRUE - - in = in.Eval(ft, vt, false) second := in.Next if in.Tag != second.Tag { @@ -155,13 +139,6 @@ func Lt(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { out := ast.TRUE second := in.Next - in = in.Eval(ft, vt, false) - - if in.Tag != ast.NUMBER || second.Tag != ast.NUMBER { - log.Log(log.ERR, "non-number argument to numeric boolean operator", ">/<=") - return nil - } - l, _ := strconv.ParseInt(in.Value(), 10, 64) r, _ := strconv.ParseInt(second.Value(), 10, 64) @@ -178,13 +155,6 @@ func Gt(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { out := ast.TRUE second := in.Next - in = in.Eval(ft, vt, false) - - if in.Tag != ast.NUMBER || second.Tag != ast.NUMBER { - log.Log(log.ERR, "non-number argument to numeric boolean operator", ">/<=") - return nil - } - l, _ := strconv.ParseInt(in.Value(), 10, 64) r, _ := strconv.ParseInt(second.Value(), 10, 64) diff --git a/stdlib/filesys.go b/stdlib/filesys.go index 182cdca..861a4a7 100644 --- a/stdlib/filesys.go +++ b/stdlib/filesys.go @@ -54,20 +54,6 @@ func AbsPath(arg string) string { * (cd (concat HOME "/go")) */ func Cd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, true) - - if in == nil { - log.Log(log.ERR, - "arguments to cd evaluated to nil!", - "cd") - return nil - } - - if in.Tag == ast.LIST { - log.Log(log.ERR, "Couldnt change dir to a list", "cd") - return nil - } - err := os.Chdir(in.Value()) if err != nil { log.Log(log.ERR, err.Error(), "cd") @@ -84,14 +70,6 @@ func Cd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * (fexists test) */ func Fexists(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, false) - if in == nil || in.Tag != ast.STRING { - log.Log(log.ERR, - "argument to fexists must be a string", - "fexists") - return nil - } - filename := in.Value() out := ast.TRUE @@ -115,7 +93,6 @@ func Fexists(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * (fread (concat HOME ".shsrc")) */ func Fread(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, false) exists := Fexists(in, vt, ft) // some waste, extra use of Eval if exists == nil || exists.Tag != ast.BOOL || exists.Value() == ast.FALSE { log.Log(log.ERR, @@ -146,26 +123,12 @@ func Fread(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * (fwrite "test" "one two three") */ func Fwrite(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, false) - if in == nil || in.Tag == ast.SYMBOL || in.Tag == ast.LIST { - log.Log(log.ERR, - "first argument must be a filename", - "fwrite") - return nil - } - text := in.Next - if text == nil || text.Tag == ast.SYMBOL || text.Tag == ast.LIST { - log.Log(log.ERR, - "second argument must be stringable", - "fwrite") - return nil - } - err := ioutil.WriteFile( AbsPath(in.Value()), []byte(text.Value()), 0644) + if err != nil { log.Log(log.ERR, "error writing file: " + err.Error(), @@ -183,22 +146,7 @@ func Fwrite(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * (fwrite "test" "one two three") */ func Fappend(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, false) - if in == nil || in.Tag == ast.SYMBOL || in.Tag == ast.LIST { - log.Log(log.ERR, - "first argument must be a filename", - "fappend") - return nil - } - text := in.Next - if text == nil || text.Tag == ast.SYMBOL || text.Tag == ast.LIST { - log.Log(log.ERR, - "second argument must be stringable", - "fappend") - return nil - } - exists := Fexists(in, vt, ft) if exists.Value() == ast.FALSE { log.Log(log.ERR, @@ -212,14 +160,15 @@ func Fappend(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { AbsPath(in.Value()), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { log.Log(log.ERR, "couldnt open file for append: " + err.Error(), "fappend") return nil } - defer f.Close() + defer f.Close() if _, err := f.WriteString(text.Value()); err != nil { log.Log(log.ERR, "error appending to file: " + err.Error(), diff --git a/stdlib/funcs.go b/stdlib/funcs.go index 63f3130..a0dd068 100644 --- a/stdlib/funcs.go +++ b/stdlib/funcs.go @@ -113,7 +113,8 @@ func DeclFunc(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Tok Function: inner, Name: name.Value(), TimesCalled: 0, - Args: numArgs, + NumArgs: numArgs, + LazyArgs: true } return nil } diff --git a/stdlib/list.go b/stdlib/list.go index 8f5d35e..e1ac6f6 100644 --- a/stdlib/list.go +++ b/stdlib/list.go @@ -30,13 +30,7 @@ import ( * in event a not-list is passed in, returns the arg. */ func Expand(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { - input = input.Eval(funcs, vars, false) - if input.Tag != ast.LIST { - log.Log(log.INFO, "expand called on not a list", "expand") - return input - } - - return input.Eval(funcs, vars, false).Expand() + return input.Expand() } /* L_APPEND (append from repl) @@ -45,7 +39,6 @@ func Expand(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token * if no args are a list, a list is made from all args */ func L_append(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { - input = input.Eval(funcs, vars, false) src := input if input.Tag != ast.LIST { @@ -74,24 +67,28 @@ func L_append(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Tok } /* Len - * Returns length of list as a number - * Returns nil if not a list + * Returns length of list or string as a number + * Returns nil if not a list or string * * Example: () -> 0 * Example: (1 2 3) -> 3 */ func Len(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { - input = input.Eval(funcs, vars, false) - if input.Tag != ast.LIST { + if input.Tag != ast.LIST && input.Tag != ast.STRING { log.Log(log.ERR, - "non-list as parameter to head", + "must provide list or strinig", "head") return nil } length := 0 - for iter := input.Expand(); iter != nil; iter = iter.Next { - length += 1 + if input.Tag = ast.STRING { + length = len(input.Value()) + + } else { + for iter := input.Expand(); iter != nil; iter = iter.Next { + length += 1 + } } ret := &ast.Token{Tag: ast.NUMBER} @@ -106,14 +103,6 @@ func Len(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { * Example: (head (2 3 4)) -> 2 */ func Head(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { - input = input.Eval(funcs, vars, false) - if input.Tag != ast.LIST { - log.Log(log.ERR, - "non-list as parameter to head", - "head") - return nil - } - li := input.Expand().Copy() if li != nil { li.Next = nil @@ -129,15 +118,6 @@ func Head(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { * Example: (tail (2 3 4)) -> 4 */ func Tail(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { - input = input.Eval(funcs, vars, false) - if input.Tag != ast.LIST { - log.Log(log.ERR, - "non-list as parameter to head", - "head") - return nil - } - - iter := input.Expand() for iter != nil && iter.Next != nil { iter = iter.Next @@ -158,25 +138,10 @@ func Tail(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { * Example: (slice 0 0 (1 2 3)) -> (1) */ func Slice(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { - input = input.Eval(funcs, vars, false) start := input end := input.Next source := end.Next - if start.Tag != ast.NUMBER || end.Tag != ast.NUMBER { - log.Log(log.ERR, - "start and end must both be integers", - "slice") - return nil - } - - if source.Tag != ast.LIST { - log.Log(log.ERR, - "non-list as parameter to head", - "head") - return nil - } - st, err := strconv.ParseInt(start.Value(), 10, 64) en, errr := strconv.ParseInt(end.Value(), 10, 64) diff --git a/stdlib/shell.go b/stdlib/shell.go index e67a4bd..0ad770d 100644 --- a/stdlib/shell.go +++ b/stdlib/shell.go @@ -225,11 +225,6 @@ func LaunchProcess( * Example (l vim file.txt) */ func Call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, true) - if in == nil { - return nil - } - if in.Tag == ast.LIST { log.Log(log.ERR, "couldnt exec, target bin is a list", "call") return nil @@ -261,11 +256,6 @@ func Call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * Example: (bg vim file.txt) */ func Bgcall(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, true) - if in == nil { - return nil - } - if in.Tag == ast.LIST { log.Log(log.ERR, "couldnt exec, target bin is a list", "call") return nil @@ -304,14 +294,6 @@ func Fg(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { return nil } - in = in.Eval(ft, vt, false) - if in.Tag != ast.NUMBER && in.Tag != ast.STRING { - log.Log(log.ERR, - "must supply a number or string to fg", - "fg") - return nil - } - pid, err := strconv.ParseFloat(in.Value(), 64) if err != nil { log.Log(log.ERR, @@ -374,12 +356,6 @@ func Jobs(in *ast.Token, vt ast.VarTable, fg ast.FuncTable) *ast.Token { * Example: ($ echo hello world) */ func ReadCmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, true) - - if in == nil { - return nil - } - if in.Tag == ast.LIST { log.Log(log.ERR, "couldnt exec, target bin is a list", "call") return nil @@ -434,13 +410,6 @@ func GetExit(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * (this function not added to functable in stdlib.go) */ func Kill(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, true) - - if in.Tag == ast.LIST { - log.Log(log.ERR, "non-number argument to kill function", "kill") - return nil - } - pid, err := strconv.ParseInt(in.Value(), 10, 64) if err != nil { log.Log(log.ERR, "error parsing arg to kill: " + err.Error(), "kill") diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 699bbfb..24d1de4 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -34,274 +34,314 @@ func GenFuncTable() ast.FuncTable { "if": &ast.Function{ Function: ShsIf, Name: "if", - TimesCalled: 0, - Args: 3, + NumArgs: 3, + EvalLazy true, + ArgLazy true }, "while": &ast.Function{ Function: ShsWhile, Name: "while", - TimesCalled: 0, - Args: -1, + NumArgs: -1, + EvalLazy true, + ArgLazy true, }, "progn": &ast.Function{ Function: ShsProgn, Name: "shs_progn", - TimesCalled: 0, - Args: -1, + NumArgs: -1, + EvalLazy: true, + ArgLazy: true, }, "func": &ast.Function{ Function: DeclFunc, Name: "decl_func", - TimesCalled: 0, - Args: 3, + Args: []ast.Token_t{ + ast.STRING, + ast.LIST, + ast.LIST + }, + EvalLazy: true, }, "len": &ast.Function{ Function: Len, Name: "len", - TimesCalled: 0, - Args: 1, + NumArgs: 1, + ArgLazy: true, }, "head": &ast.Function{ Function: Head, Name: "head", - TimesCalled: 0, - Args: 1, + Args: []ast.Token_t{ + ast.LIST + }, }, "tail": &ast.Function{ Function: Tail, Name: "tail", - TimesCalled: 0, - Args: 1, + Args: []ast.Token_t{ + ast.LIST + }, }, "slice": &ast.Function{ Function: Slice, Name: "slice", - TimesCalled: 0, - Args: 3, + Args: []ast.Token_t{ + ast.NUMBER, + ast.NUMBER, + ast.LIST + }, }, "export": &ast.Function{ Function: Export, Name: "export", - TimesCalled: 0, - Args: 2, + LazyEval: true, + Args: []ast.Token_t{ + ast.STRING, + ast.LIST + }, }, "input": &ast.Function{ Function: Input, Name: "input", - TimesCalled: 0, - Args: 1, + Args: []ast.Token_t{ + ast.STRING + }, }, "load": &ast.Function{ Function: Load, Name: "load", - TimesCalled: 0, - Args: 1, + Args: []ast.Token_t{ + ast.STRING + }, }, "bool": &ast.Function{ Function: BoolCast, Name: "bool", - TimesCalled: 0, - Args: 1, + Args: []ast.Token_t{ + ast.STRING + }, }, "string": &ast.Function{ Function: StrCast, Name: "string", - TimesCalled: 0, - Args: 1, + NumArgs: 1, + ArgLazy: true, }, "number": &ast.Function{ Function: NumCast, Name: "number", - TimesCalled: 0, - Args: 1, + Args: []ast.Token{ + ast.NUMBER + }, }, - "...": &ast.Function{ + "...": &ast.Function{ Function: Expand, Name: "...", - TimesCalled: 0, - Args: 1, + Args: []ast.Token_t{ + ast.LIST + }, }, "append": &ast.Function{ Function: L_append, Name: "append", - TimesCalled: 0, - Args: -1, - }, + NumArgs: -1, + ArgLazy: true, + }, "join": &ast.Function{ Function: Join, Name: "join", - TimesCalled: 0, - Args: 2, + Args: []ast.Token_t{ + ast.STRING, + ast.LIST + }, }, "split": &ast.Function{ Function: Split, Name: "split", - TimesCalled: 0, - Args: 2, + Args: ast.Token_t{ + ast.STRING, + ast.STRING + }, }, "substr": &ast.Function{ Function: Substr, Name: "substr", - TimesCalled: 0, - Args: 3, + Args: []ast.Token_t{ + ast.NUMBER, + ast.NUMBER, + ast.STRING + }, }, "exit": &ast.Function{ Function: ExitShell, Name: "exit", - TimesCalled: 0, - Args: 0, + Args: []ast.Token_t{}, }, "eq": &ast.Function{ Function: Eq, Name: "==", - TimesCalled: 0, Args: 2, + ArgLazy: true, }, "ne": &ast.Function{ Function: Ne, Name: "!=", - TimesCalled: 0, Args: 2, + ArgLazy: true, }, "<": &ast.Function{ Function: Lt, Name: "<", - TimesCalled: 0, - Args: 2, + Args: []ast.Token_t{ + ast.NUMBER, + ast.NUMBER + }, }, ">": &ast.Function{ Function: Gt, Name: ">", - TimesCalled: 0, - Args: 2, + Args: []ast.Token_t{ + ast.NUMBER, + ast.NUMBER + }, }, "<=": &ast.Function{ Function: Lte, Name: "<=", - TimesCalled: 0, - Args: 2, + Args: []ast.Token_t{ + ast.NUMBER, + ast.NUMBER + }, }, ">=": &ast.Function{ Function: Gte, Name: ">=", - TimesCalled: 0, - Args: 2, + Args: []ast.Token_t{ + ast.NUMBER, + ast.NUMBER + }, }, "!": &ast.Function{ Function: Not, Name: "!", - TimesCalled: 0, - Args: 1, + Args: []ast.Token_t{ + ast.Bool + }, }, "+": &ast.Function{ Function: Add, Name: "add", - TimesCalled: 0, - Args: -1, + NumArgs: -1, + ArgLazy: true, }, "-": &ast.Function{ Function: Sub, Name: "sub", - TimesCalled: 0, - Args: -1, + NumArgs: -1, + ArgLazy: true, }, "*": &ast.Function{ Function: Mult, Name: "mult", - TimesCalled: 0, - Args: -1, + NumArgs: -1, + ArgLazy: true, }, "/": &ast.Function{ Function: Div, Name: "div", - TimesCalled: 0, - Args: -1, + NumArgs: -1, + ArgLazy: true, }, "cd": &ast.Function{ Function: Cd, Name: "changedir", - TimesCalled: 0, - Args: 1, + Args: []ast.Token_t{ + ast.STRING + }, }, "concat": &ast.Function{ Function: Concat, Name:"concatenate", - TimesCalled: 0, - Args: -1, + NumArgs: -1, + ArgLazy: true, }, "print": &ast.Function{ Function: PrintStr, Name: "print", - TimesCalled: 0, - Args: 1, + Args: []ast.Token_t{ + ast.STRING + }, }, "l": &ast.Function{ Function: Call, Name: "call", - TimesCalled: 0, - Args: -1, + NumArgs: -1, + ArgLazy: true, + SymLazy: true, }, "bg": &ast.Function{ Function: Bgcall, Name: "background call", - TimesCalled: 0, - Args: -1, + NumArgs: -1, + ArgLazy: true, + SymLazy: true, }, "fg": &ast.Function{ Function: Fg, Name: "foreground", - TimesCalled: 0, - Args: 1, + NumArgs: []ast.Token_t{ + ast.NUMBER + }, }, "$": &ast.Function{ Function: ReadCmd, Name: "read cmd", - TimesCalled: 0, - Args: -1, + NumnArgs: -1, + ArgLazy: true, + SymLazy: true, }, "?": &ast.Function{ Function: GetExit, Name:"get exit code", - TimesCalled: 0, - Args: 0, + Args: ast.Token_t{}, }, /* @@ -309,7 +349,6 @@ func GenFuncTable() ast.FuncTable { "kill": &ast.Function{ Function: kill, Name: "kill job", - TimesCalled: 0, Args: 1, }, */ @@ -317,43 +356,49 @@ func GenFuncTable() ast.FuncTable { "jobs": &ast.Function{ Function: Jobs, Name: "list jobs", - TimesCalled: 0, - Args: 0, + Args: ast.Token_t{}, }, "info": &ast.Function{ Function: ShInfo, Name: "Shell Info", - TimesCalled: 0, - Args: 1, + Args: ast.Token_t{ + ast.SYMBOL + }, }, "fexists": &ast.Function{ Function: Fexists, Name: "file exists", - TimesCalled: 0, - Args: 1, + Args: []ast.Token_t{ + ast.STRING + }, }, "fread": &ast.Function{ Function: Fread, Name: "read file", - TimesCalled: 0, - Args: 1, + Args: []ast.Token_t{ + ast.STRING + }, }, "fwrite": &ast.Function{ Function: Fwrite, Name: "write file", - TimesCalled: 0, - Args: 2, + Args: []ast.Token_t{ + ast.STRING, + ast.STRING + }, }, "fappend": &ast.Function{ Function: Fappend, Name:"append to file", - TimesCalled: 0, - Args: 2, + Args: []ast.Token_t{ + ast.STRING, + ast.STRING + }, }, } @@ -395,10 +440,12 @@ func ShInfo(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { funct := ast.GetFunction(in.Value(), ft) if funct != nil { - fmt.Printf("FUNCTION\nNAME: %s\nTIMES CALLED: %s\nNUM ARGS: %d\n", funct.Name, funct.TimesCalled, funct.Args) + fmt.Printf("FUNCTION\nNAME: %s\nTIMES CALLED: %s\nNUM ARGS: %d\n", funct.Name, funct.TimesCalled, funct.NumArgs) break } + // TODO: print func args + fmt.Printf("UNKNOWN SYMBOL\n") } @@ -413,14 +460,6 @@ func ShInfo(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * Example: (print (input)) */ func Input(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, false) - if in.Tag != ast.STRING && in.Tag != ast.NUMBER { - log.Log(log.ERR, - "argument to input must be a string or number", - "input") - return nil - } - prompt := in.Value() var output string @@ -438,14 +477,6 @@ func Input(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * Example: (load "myscript.shs") */ func Load(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, true) - if in.Tag != ast.STRING { - log.Log(log.ERR, - "argument to load must be a string", - "load") - return nil - } - bp := in.Value() bp = AbsPath(bp) util.LoadScript(bp, vt, ft) diff --git a/stdlib/string.go b/stdlib/string.go index 140600a..01d1f60 100644 --- a/stdlib/string.go +++ b/stdlib/string.go @@ -30,8 +30,6 @@ import ( * Example: (concat "hello" " " "world") */ func Concat(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, false) - var res string for i := in; i != nil; i = i.Next { if i.Tag == ast.LIST { @@ -54,7 +52,7 @@ func Concat(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * Example: (string 1) -> 1.0 */ func StrCast(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - body := in.Eval(ft, vt, false).String() + body := in.String() res := &ast.Token{ Tag: ast.STRING } res.Set(body) return res @@ -67,22 +65,8 @@ func StrCast(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * Example: (split "/path/to/file" "/") -> ("path" "to" "file") */ func Split(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, false) - if in == nil || in.Tag != ast.STRING || in.Next == nil || in.Next.Tag != ast.STRING { - log.Log(log.ERR, - "Args must be two strings", - "split") - return nil - } - body := in.Value() delim := in.Next.Value() - if len(body) < len(delim) { - log.Log(log.DEBUG, - "possibly mismatched args" + - "delimiter longer than body", - "split") - } var res *ast.Token builder := &res @@ -107,25 +91,11 @@ func Split(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * Example: (join ", " ("apple" "ananas" "pear")) -> "apple, ananas, pear" */ func Join(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - in = in.Eval(ft, vt, false) - if in == nil || in.Next == nil { - log.Log(log.ERR, - "one or more arguments evaluated to nil", - "join") - return nil - } - delim := in strs := in.Next - if delim.Tag != ast.STRING || strs.Tag != ast.LIST { - log.Log(log.ERR, - "first argument must be a string (delimiter) and second argument must be a list (strings)", - "join") - return nil - } - de := delim.Value() res := "" + for i := strs.Expand(); i != nil; i = i.Next { if i.Tag != ast.STRING { log.Log(log.ERR, @@ -155,24 +125,9 @@ func Join(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * Example: (substr 1 5 "Linus Torvalds") -> "inus " */ func Substr(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - start := in.Eval(ft, vt, false) - if start == nil || start.Next == nil || start.Next.Next == nil { - log.Log(log.ERR, - "an argument evaluated to nil", - "substr") - return nil - } - end := start.Next str := end.Next - if start.Tag != ast.NUMBER || end.Tag != ast.NUMBER || str.Tag != ast.STRING { - log.Log(log.ERR, - "incorrect types of args", - "substr") - return nil - } - ed_idx := 0 st_idx, err := strconv.Atoi(start.Value()) ed_idx, err = strconv.Atoi(end.Value()) @@ -226,7 +181,7 @@ func Substr(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * Example: (print "Line: \n, Tab: \t") */ func PrintStr(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - body := in.Eval(ft, vt, false).String() + body := in.String() if body[0] != body[len(body)-1] && body[0] != '"' { body = "`" + body + "`" } From 87004ff7fd7ca7c08d2c4e90fc74d22ea4b18cd1 Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 21 Aug 2020 17:37:54 -0700 Subject: [PATCH 11/24] fix build from last night --- ast/func_table.go | 15 +- cmd/shs.go | 2 +- stdlib/funcs.go | 2 +- stdlib/list.go | 2 +- stdlib/stdlib.go | 401 +++++++++++++++++++++++----------------------- stdlib/string.go | 1 + 6 files changed, 210 insertions(+), 213 deletions(-) diff --git a/ast/func_table.go b/ast/func_table.go index f6c9da3..03e16bd 100644 --- a/ast/func_table.go +++ b/ast/func_table.go @@ -18,7 +18,6 @@ package ast import ( - "fmt" "gitlab.com/whom/shs/log" ) @@ -40,7 +39,7 @@ type Function struct { // list of types (LIST, NUMBER, STRING, etc) representing args Args []Token_t - NumArgs bool // -1 means infinite + NumArgs int // -1 means infinite // lazy arg checking (use NumArgs instead of args) ArgLazy bool @@ -62,7 +61,7 @@ type FuncTable *map[string]*Function * makes sure correct arguments are passed in */ func (f Function) ParseFunction(args *Token) bool { - total = len(f.Args) + total := len(f.Args) for iter := args; iter != nil; iter = iter.Next { total -= 1 if total <= 0 { @@ -88,7 +87,7 @@ func (f Function) ParseFunction(args *Token) bool { /* same as ParseFunction but only evaluates the number of args */ func (f Function) LazyParseFunction(args *Token) bool { - if (f.NumArgs < 0) { + if f.NumArgs < 0 { return true } @@ -118,15 +117,15 @@ func (f Function) LazyParseFunction(args *Token) bool { * calls ParseFunction and increments TimesCalled */ func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token { - if not f.EvalLazy { + if !f.EvalLazy { args = args.Eval(ft, vt, !f.SymLazy) } passes := false - if f.ArgsLazy { - passes = LazyParseFunction(args) + if f.ArgLazy { + passes = f.LazyParseFunction(args) } else { - passes = ParseFunction(args) + passes = f.ParseFunction(args) } if passes { diff --git a/cmd/shs.go b/cmd/shs.go index cf58d44..bad550c 100644 --- a/cmd/shs.go +++ b/cmd/shs.go @@ -95,7 +95,7 @@ func main() { } dyn_prompt := ast.GetFunction("_SH_PROMPT", funcs) - if dyn_prompt == nil || dyn_prompt.Args != 0 { + if dyn_prompt == nil || dyn_prompt.NumArgs != 0 { dyn_prompt = nil } diff --git a/stdlib/funcs.go b/stdlib/funcs.go index a0dd068..2e39599 100644 --- a/stdlib/funcs.go +++ b/stdlib/funcs.go @@ -114,7 +114,7 @@ func DeclFunc(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Tok Name: name.Value(), TimesCalled: 0, NumArgs: numArgs, - LazyArgs: true + ArgLazy: true, } return nil } diff --git a/stdlib/list.go b/stdlib/list.go index e1ac6f6..13c1cdc 100644 --- a/stdlib/list.go +++ b/stdlib/list.go @@ -82,7 +82,7 @@ func Len(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { } length := 0 - if input.Tag = ast.STRING { + if input.Tag == ast.STRING { length = len(input.Value()) } else { diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 24d1de4..be21deb 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -20,7 +20,6 @@ package stdlib import ( "os" "fmt" - "gitlab.com/whom/shs/log" "gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/util" ) @@ -32,316 +31,316 @@ func GenFuncTable() ast.FuncTable { var stdlib ast.FuncTable stdlib = &map[string]*ast.Function{ "if": &ast.Function{ - Function: ShsIf, + Function: ShsIf, Name: "if", - NumArgs: 3, - EvalLazy true, - ArgLazy true + NumArgs: 3, + EvalLazy: true, + ArgLazy: true, }, "while": &ast.Function{ - Function: ShsWhile, + Function: ShsWhile, Name: "while", - NumArgs: -1, - EvalLazy true, - ArgLazy true, + NumArgs: -1, + EvalLazy: true, + ArgLazy: true, }, "progn": &ast.Function{ Function: ShsProgn, - Name: "shs_progn", - NumArgs: -1, - EvalLazy: true, - ArgLazy: true, + Name: "shs_progn", + NumArgs: -1, + EvalLazy: true, + ArgLazy: true, }, "func": &ast.Function{ - Function: DeclFunc, - Name: "decl_func", - Args: []ast.Token_t{ + Function: DeclFunc, + Name: "decl_func", + Args: []ast.Token_t{ ast.STRING, ast.LIST, - ast.LIST + ast.LIST, }, - EvalLazy: true, + EvalLazy: true, }, "len": &ast.Function{ - Function: Len, - Name: "len", - NumArgs: 1, - ArgLazy: true, + Function: Len, + Name: "len", + NumArgs: 1, + ArgLazy: true, }, "head": &ast.Function{ - Function: Head, + Function: Head, Name: "head", - Args: []ast.Token_t{ - ast.LIST + Args: []ast.Token_t{ + ast.LIST, }, }, "tail": &ast.Function{ Function: Tail, - Name: "tail", - Args: []ast.Token_t{ - ast.LIST + Name: "tail", + Args: []ast.Token_t{ + ast.LIST, }, }, "slice": &ast.Function{ Function: Slice, - Name: "slice", - Args: []ast.Token_t{ + Name: "slice", + Args: []ast.Token_t{ ast.NUMBER, ast.NUMBER, - ast.LIST + ast.LIST, }, }, "export": &ast.Function{ - Function: Export, - Name: "export", - LazyEval: true, - Args: []ast.Token_t{ + Function: Export, + Name: "export", + EvalLazy: true, + Args: []ast.Token_t{ ast.STRING, - ast.LIST + ast.LIST, }, }, "input": &ast.Function{ - Function: Input, - Name: "input", - Args: []ast.Token_t{ - ast.STRING + Function: Input, + Name: "input", + Args: []ast.Token_t{ + ast.STRING, }, }, "load": &ast.Function{ - Function: Load, - Name: "load", - Args: []ast.Token_t{ - ast.STRING + Function: Load, + Name: "load", + Args: []ast.Token_t{ + ast.STRING, }, }, "bool": &ast.Function{ - Function: BoolCast, - Name: "bool", - Args: []ast.Token_t{ - ast.STRING + Function: BoolCast, + Name: "bool", + Args: []ast.Token_t{ + ast.STRING, }, }, "string": &ast.Function{ - Function: StrCast, - Name: "string", - NumArgs: 1, - ArgLazy: true, + Function: StrCast, + Name: "string", + NumArgs: 1, + ArgLazy: true, }, "number": &ast.Function{ - Function: NumCast, - Name: "number", - Args: []ast.Token{ - ast.NUMBER + Function: NumCast, + Name: "number", + Args: []ast.Token_t{ + ast.NUMBER, }, }, "...": &ast.Function{ - Function: Expand, - Name: "...", - Args: []ast.Token_t{ - ast.LIST + Function: Expand, + Name: "...", + Args: []ast.Token_t{ + ast.LIST, }, }, "append": &ast.Function{ - Function: L_append, - Name: "append", - NumArgs: -1, - ArgLazy: true, + Function: L_append, + Name: "append", + NumArgs: -1, + ArgLazy: true, }, "join": &ast.Function{ - Function: Join, - Name: "join", - Args: []ast.Token_t{ + Function: Join, + Name: "join", + Args: []ast.Token_t{ ast.STRING, - ast.LIST + ast.LIST, }, }, "split": &ast.Function{ Function: Split, - Name: "split", - Args: ast.Token_t{ + Name: "split", + Args: []ast.Token_t{ + ast.STRING, ast.STRING, - ast.STRING }, }, "substr": &ast.Function{ - Function: Substr, - Name: "substr", - Args: []ast.Token_t{ + Function: Substr, + Name: "substr", + Args: []ast.Token_t{ ast.NUMBER, ast.NUMBER, - ast.STRING + ast.STRING, }, }, "exit": &ast.Function{ - Function: ExitShell, - Name: "exit", - Args: []ast.Token_t{}, + Function: ExitShell, + Name: "exit", + Args: []ast.Token_t{}, }, "eq": &ast.Function{ - Function: Eq, - Name: "==", - Args: 2, - ArgLazy: true, + Function: Eq, + Name: "==", + NumArgs: 2, + ArgLazy: true, }, "ne": &ast.Function{ - Function: Ne, - Name: "!=", - Args: 2, - ArgLazy: true, + Function: Ne, + Name: "!=", + NumArgs: 2, + ArgLazy: true, }, "<": &ast.Function{ - Function: Lt, - Name: "<", - Args: []ast.Token_t{ + Function: Lt, + Name: "<", + Args: []ast.Token_t{ + ast.NUMBER, ast.NUMBER, - ast.NUMBER }, }, ">": &ast.Function{ - Function: Gt, - Name: ">", - Args: []ast.Token_t{ + Function: Gt, + Name: ">", + Args: []ast.Token_t{ + ast.NUMBER, ast.NUMBER, - ast.NUMBER }, }, "<=": &ast.Function{ - Function: Lte, - Name: "<=", - Args: []ast.Token_t{ + Function: Lte, + Name: "<=", + Args: []ast.Token_t{ + ast.NUMBER, ast.NUMBER, - ast.NUMBER }, }, ">=": &ast.Function{ - Function: Gte, - Name: ">=", - Args: []ast.Token_t{ + Function: Gte, + Name: ">=", + Args: []ast.Token_t{ + ast.NUMBER, ast.NUMBER, - ast.NUMBER }, }, "!": &ast.Function{ - Function: Not, - Name: "!", - Args: []ast.Token_t{ - ast.Bool + Function: Not, + Name: "!", + Args: []ast.Token_t{ + ast.BOOL, }, }, "+": &ast.Function{ - Function: Add, - Name: "add", - NumArgs: -1, - ArgLazy: true, + Function: Add, + Name: "add", + NumArgs: -1, + ArgLazy: true, }, "-": &ast.Function{ - Function: Sub, - Name: "sub", - NumArgs: -1, - ArgLazy: true, + Function: Sub, + Name: "sub", + NumArgs: -1, + ArgLazy: true, }, "*": &ast.Function{ - Function: Mult, + Function: Mult, Name: "mult", - NumArgs: -1, - ArgLazy: true, + NumArgs: -1, + ArgLazy: true, }, "/": &ast.Function{ - Function: Div, - Name: "div", - NumArgs: -1, - ArgLazy: true, + Function: Div, + Name: "div", + NumArgs: -1, + ArgLazy: true, }, "cd": &ast.Function{ - Function: Cd, - Name: "changedir", - Args: []ast.Token_t{ - ast.STRING + Function: Cd, + Name: "changedir", + Args: []ast.Token_t{ + ast.STRING, }, }, "concat": &ast.Function{ Function: Concat, - Name:"concatenate", - NumArgs: -1, - ArgLazy: true, + Name: "concatenate", + NumArgs: -1, + ArgLazy: true, }, "print": &ast.Function{ - Function: PrintStr, - Name: "print", - Args: []ast.Token_t{ - ast.STRING + Function: PrintStr, + Name: "print", + Args: []ast.Token_t{ + ast.STRING, }, }, "l": &ast.Function{ - Function: Call, + Function: Call, Name: "call", - NumArgs: -1, - ArgLazy: true, - SymLazy: true, + NumArgs: -1, + ArgLazy: true, + SymLazy: true, }, "bg": &ast.Function{ - Function: Bgcall, - Name: "background call", - NumArgs: -1, - ArgLazy: true, - SymLazy: true, + Function: Bgcall, + Name: "background call", + NumArgs: -1, + ArgLazy: true, + SymLazy: true, }, "fg": &ast.Function{ - Function: Fg, - Name: "foreground", - NumArgs: []ast.Token_t{ - ast.NUMBER + Function: Fg, + Name: "foreground", + Args: []ast.Token_t{ + ast.NUMBER, }, }, "$": &ast.Function{ - Function: ReadCmd, - Name: "read cmd", - NumnArgs: -1, - ArgLazy: true, - SymLazy: true, + Function: ReadCmd, + Name: "read cmd", + NumArgs: -1, + ArgLazy: true, + SymLazy: true, }, "?": &ast.Function{ - Function: GetExit, - Name:"get exit code", - Args: ast.Token_t{}, + Function: GetExit, + Name: "get exit code", + Args: []ast.Token_t{}, }, /* @@ -354,50 +353,50 @@ func GenFuncTable() ast.FuncTable { */ "jobs": &ast.Function{ - Function: Jobs, - Name: "list jobs", - Args: ast.Token_t{}, + Function: Jobs, + Name: "list jobs", + Args: []ast.Token_t{}, }, "info": &ast.Function{ - Function: ShInfo, - Name: "Shell Info", - Args: ast.Token_t{ - ast.SYMBOL + Function: ShInfo, + Name: "Shell Info", + Args: []ast.Token_t{ + ast.SYMBOL, }, }, "fexists": &ast.Function{ - Function: Fexists, - Name: "file exists", - Args: []ast.Token_t{ - ast.STRING + Function: Fexists, + Name: "file exists", + Args: []ast.Token_t{ + ast.STRING, }, }, "fread": &ast.Function{ - Function: Fread, - Name: "read file", - Args: []ast.Token_t{ - ast.STRING + Function: Fread, + Name: "read file", + Args: []ast.Token_t{ + ast.STRING, }, }, "fwrite": &ast.Function{ - Function: Fwrite, - Name: "write file", - Args: []ast.Token_t{ + Function: Fwrite, + Name: "write file", + Args: []ast.Token_t{ + ast.STRING, ast.STRING, - ast.STRING }, }, "fappend": &ast.Function{ - Function: Fappend, - Name:"append to file", - Args: []ast.Token_t{ + Function: Fappend, + Name: "append to file", + Args: []ast.Token_t{ + ast.STRING, ast.STRING, - ast.STRING }, }, } @@ -421,34 +420,32 @@ func ExitShell(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * Example: (info append) */ func ShInfo(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - switch in.Tag { - case ast.BOOL: - fmt.Printf("BOOL LITERAL\nValue: %s\n", in.Value()) - case ast.STRING: - fmt.Printf("STRING LITERAL \nValue: %s\n", in.Value()) - case ast.NUMBER: - fmt.Printf("NUMBER LITERAL \nValue: %s\n", in.Value()) - case ast.LIST: - fmt.Printf("LIST \nString Value: %s, AST:\n", in.String()) - ast.PrintSExprsIndividually(in) - case ast.SYMBOL: - repr := ast.GetVar(in.Value(), vt) - if repr != nil { - fmt.Printf("VARIABLE\nTYPE: %s\nVALUE: %s\n", ast.GetTagAsStr(repr.Tag), repr.Value()) - break - } - - funct := ast.GetFunction(in.Value(), ft) - if funct != nil { - fmt.Printf("FUNCTION\nNAME: %s\nTIMES CALLED: %s\nNUM ARGS: %d\n", funct.Name, funct.TimesCalled, funct.NumArgs) - break - } - - // TODO: print func args - - fmt.Printf("UNKNOWN SYMBOL\n") + repr := ast.GetVar(in.Value(), vt) + if repr != nil { + fmt.Printf("VARIABLE\nTYPE: %s\nVALUE: %s\n", ast.GetTagAsStr(repr.Tag), repr.Value()) + return nil } + funct := ast.GetFunction(in.Value(), ft) + if funct != nil { + fmt.Printf("FUNCTION\nNAME: %s\nTIMES CALLED: %s\n\n", funct.Name, funct.TimesCalled) + if funct.ArgLazy { + fmt.Printf("function does not evaluate args by type\nARG COUNT: %s\n", funct.NumArgs) + } else { + fmt.Printf("function evaluates args by type\nARGS: ") + for _, i := range(funct.Args) { + fmt.Printf(ast.GetTagAsStr(i)) + } + } + + fmt.Printf("SYMLAZY: %t\n", funct.SymLazy) + fmt.Printf("(can undefined symbols be passed in)\n") + fmt.Printf("EVALLAZY: %t\n", funct.EvalLazy) + fmt.Printf("(are all forms in evaluated before function call)\n") + return nil + } + + fmt.Printf("UNKNOWN SYMBOL\n") return nil } diff --git a/stdlib/string.go b/stdlib/string.go index 01d1f60..090932e 100644 --- a/stdlib/string.go +++ b/stdlib/string.go @@ -125,6 +125,7 @@ func Join(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { * Example: (substr 1 5 "Linus Torvalds") -> "inus " */ func Substr(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { + start := in end := start.Next str := end.Next From 4ce1f7137cf01353c71d6bbf173578e107472b8f Mon Sep 17 00:00:00 2001 From: Aidan Date: Fri, 21 Aug 2020 18:02:49 -0700 Subject: [PATCH 12/24] better log line, fix issue with sym eval --- ast/func_table.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ast/func_table.go b/ast/func_table.go index 03e16bd..2121874 100644 --- a/ast/func_table.go +++ b/ast/func_table.go @@ -73,9 +73,8 @@ func (f Function) ParseFunction(args *Token) bool { if iter.Tag != f.Args[len(f.Args) - total] { log.Log(log.ERR, - "argument of type " + GetTagAsStr(iter.Tag) + - "passed in when " + GetTagAsStr(f.Args[len(f.Args) - total]) + - " was expected", + "argument is " + GetTagAsStr(iter.Tag) + + "should be " + GetTagAsStr(f.Args[len(f.Args) - total]), "ftable") return false } @@ -118,7 +117,7 @@ func (f Function) LazyParseFunction(args *Token) bool { */ func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token { if !f.EvalLazy { - args = args.Eval(ft, vt, !f.SymLazy) + args = args.Eval(ft, vt, f.SymLazy) } passes := false From 6afd01da2ac11a22a6382018abde0aacbdd21362 Mon Sep 17 00:00:00 2001 From: Aidan Date: Sat, 22 Aug 2020 12:37:06 -0700 Subject: [PATCH 13/24] syncing unfinished work with dev --- ast/func_table.go | 15 ++++++++++++--- stdlib/stdlib.go | 6 ++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/ast/func_table.go b/ast/func_table.go index 2121874..0d8fa12 100644 --- a/ast/func_table.go +++ b/ast/func_table.go @@ -18,6 +18,7 @@ package ast import ( + "fmt" "gitlab.com/whom/shs/log" ) @@ -64,7 +65,7 @@ func (f Function) ParseFunction(args *Token) bool { total := len(f.Args) for iter := args; iter != nil; iter = iter.Next { total -= 1 - if total <= 0 { + if total < 0 { log.Log(log.ERR, "too many arguments", "ftable") @@ -74,12 +75,19 @@ func (f Function) ParseFunction(args *Token) bool { if iter.Tag != f.Args[len(f.Args) - total] { log.Log(log.ERR, "argument is " + GetTagAsStr(iter.Tag) + - "should be " + GetTagAsStr(f.Args[len(f.Args) - total]), + " should be " + GetTagAsStr(f.Args[len(f.Args) - total]), "ftable") return false } } + if total > 0 { + log.Log(log.ERR, + "not enough args given", + "ftable") + return false + } + return true } @@ -127,13 +135,14 @@ func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token { passes = f.ParseFunction(args) } - if passes { + if !passes { log.Log(log.ERR, "Couldnt call " + f.Name, "eval") return nil } + fmt.Printf("ARGS: %+v", *args) f.TimesCalled += 1 return f.Function(args, vt, ft) } diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index be21deb..d76f7e6 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -102,10 +102,8 @@ func GenFuncTable() ast.FuncTable { Function: Export, Name: "export", EvalLazy: true, - Args: []ast.Token_t{ - ast.STRING, - ast.LIST, - }, + ArgLazy: true, + NumArgs: 2, }, "input": &ast.Function{ From 591402b428426e5ffe8143b0dfdfc20738657302 Mon Sep 17 00:00:00 2001 From: Aidan Date: Wed, 26 Aug 2020 21:36:06 -0700 Subject: [PATCH 14/24] finish fixing things --- ast/eval.go | 4 +++- ast/func_table.go | 25 +++++++++++++------------ ast/lex.go | 2 +- stdlib/stdlib.go | 4 ++-- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/ast/eval.go b/ast/eval.go index e82a89b..cbbae5d 100644 --- a/ast/eval.go +++ b/ast/eval.go @@ -17,7 +17,9 @@ package ast -import "gitlab.com/whom/shs/log" +import ( + "gitlab.com/whom/shs/log" +) /* determines whether or not to execute a system binary * when a function cannot be found in the functable diff --git a/ast/func_table.go b/ast/func_table.go index 0d8fa12..001f149 100644 --- a/ast/func_table.go +++ b/ast/func_table.go @@ -18,7 +18,6 @@ package ast import ( - "fmt" "gitlab.com/whom/shs/log" ) @@ -62,26 +61,26 @@ type FuncTable *map[string]*Function * makes sure correct arguments are passed in */ func (f Function) ParseFunction(args *Token) bool { - total := len(f.Args) + inc := 0 for iter := args; iter != nil; iter = iter.Next { - total -= 1 - if total < 0 { + inc += 1 + if inc > len(f.Args) { log.Log(log.ERR, "too many arguments", "ftable") return false } - if iter.Tag != f.Args[len(f.Args) - total] { + if iter.Tag != f.Args[inc - 1] { log.Log(log.ERR, "argument is " + GetTagAsStr(iter.Tag) + - " should be " + GetTagAsStr(f.Args[len(f.Args) - total]), + " should be " + GetTagAsStr(f.Args[inc - 1]), "ftable") return false } } - if total > 0 { + if inc < len(f.Args) { log.Log(log.ERR, "not enough args given", "ftable") @@ -124,15 +123,18 @@ func (f Function) LazyParseFunction(args *Token) bool { * calls ParseFunction and increments TimesCalled */ func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token { + var n_args *Token if !f.EvalLazy { - args = args.Eval(ft, vt, f.SymLazy) + n_args = args.Eval(ft, vt, f.SymLazy) + } else { + n_args = args } passes := false if f.ArgLazy { - passes = f.LazyParseFunction(args) + passes = f.LazyParseFunction(n_args) } else { - passes = f.ParseFunction(args) + passes = f.ParseFunction(n_args) } if !passes { @@ -142,9 +144,8 @@ func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token { return nil } - fmt.Printf("ARGS: %+v", *args) f.TimesCalled += 1 - return f.Function(args, vt, ft) + return f.Function(n_args, vt, ft) } /* searches for function mapped to argument in FuncTable diff --git a/ast/lex.go b/ast/lex.go index aa677f5..7c33bac 100644 --- a/ast/lex.go +++ b/ast/lex.go @@ -216,7 +216,7 @@ func StrIsNumber(arg string) bool { dotCount := 0 // negative nums - if len(arg) > 0 && arg[0] == '-' { + if len(arg) > 1 && arg[0] == '-' { arg = arg[1:] } diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index d76f7e6..dd70b5e 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -58,7 +58,7 @@ func GenFuncTable() ast.FuncTable { Function: DeclFunc, Name: "decl_func", Args: []ast.Token_t{ - ast.STRING, + ast.SYMBOL, ast.LIST, ast.LIST, }, @@ -141,7 +141,7 @@ func GenFuncTable() ast.FuncTable { Function: NumCast, Name: "number", Args: []ast.Token_t{ - ast.NUMBER, + ast.STRING, }, }, From 12caeedf682532d890f07e683b454a769393c6b2 Mon Sep 17 00:00:00 2001 From: Aidan Date: Wed, 26 Aug 2020 22:32:01 -0700 Subject: [PATCH 15/24] add a string replace method --- ast/func_table.go | 4 +--- stdlib/shell.go | 5 +++++ stdlib/stdlib.go | 11 +++++++++++ stdlib/string.go | 19 +++++++++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/ast/func_table.go b/ast/func_table.go index 001f149..81e006b 100644 --- a/ast/func_table.go +++ b/ast/func_table.go @@ -123,11 +123,9 @@ func (f Function) LazyParseFunction(args *Token) bool { * calls ParseFunction and increments TimesCalled */ func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token { - var n_args *Token + n_args := args if !f.EvalLazy { n_args = args.Eval(ft, vt, f.SymLazy) - } else { - n_args = args } passes := false diff --git a/stdlib/shell.go b/stdlib/shell.go index 0ad770d..823bff3 100644 --- a/stdlib/shell.go +++ b/stdlib/shell.go @@ -225,6 +225,11 @@ func LaunchProcess( * Example (l vim file.txt) */ func Call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { + if in == nil { + log.Log(log.ERR, "no arguments given", "call") + return nil + } + if in.Tag == ast.LIST { log.Log(log.ERR, "couldnt exec, target bin is a list", "call") return nil diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index dd70b5e..7f85b05 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -178,6 +178,16 @@ func GenFuncTable() ast.FuncTable { }, }, + "replace": &ast.Function{ + Function: Replace, + Name: "replace", + Args: []ast.Token_t{ + ast.STRING, + ast.STRING, + ast.STRING, + }, + }, + "substr": &ast.Function{ Function: Substr, Name: "substr", @@ -283,6 +293,7 @@ func GenFuncTable() ast.FuncTable { "cd": &ast.Function{ Function: Cd, Name: "changedir", + SymLazy: true, Args: []ast.Token_t{ ast.STRING, }, diff --git a/stdlib/string.go b/stdlib/string.go index 090932e..b3d7a72 100644 --- a/stdlib/string.go +++ b/stdlib/string.go @@ -84,6 +84,25 @@ func Split(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { return res } +/* Takes three args + * 1. source string + * 2. token to be replaced + * 3. token to swap in place + * All three args are strings + * Returns final string + */ +func Replace(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { + body := in.Value() + sub := in.Next.Value() + tok := in.Next.Next.Value() + + body = strings.ReplaceAll(body, sub, tok) + + res := &ast.Token{Tag: ast.STRING} + res.Set(body) + return res +} + /* Takes two args, a delimiter and a list of strings * Returns the list of strings concatenated together with the delimiter in between each element * On error returns nil From fcdde50faaaac5e79a2de08b70de0ade9b190cd9 Mon Sep 17 00:00:00 2001 From: Aidan Date: Wed, 26 Aug 2020 23:45:02 -0700 Subject: [PATCH 16/24] added list index function --- stdlib/list.go | 39 +++++++++++++++++++++++++++++++++++++++ stdlib/stdlib.go | 9 +++++++++ 2 files changed, 48 insertions(+) diff --git a/stdlib/list.go b/stdlib/list.go index 13c1cdc..5e34364 100644 --- a/stdlib/list.go +++ b/stdlib/list.go @@ -96,6 +96,45 @@ func Len(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { return ret } +/* Takes two arguments + * An index and a list + * Returns element indexed by index arg + * can return nil if index is out of bounds + * + * Example (index 2 (1 2 3)) -> 2 + */ +func Index(in *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { + idx, err := strconv.ParseInt(in.Value(), 10, 64) + if err != nil || idx < 0 { + log.Log(log.ERR, + "index must be a positive integer: " + err.Error(), + "index") + return nil + } + + series := in.Next.Expand() + iter := &series + i := int64(0) + for i <= idx { + if *iter == nil { + log.Log(log.ERR, + "index out of bounds", + "index") + return nil + } + + if i < idx { + iter = &((*iter).Next) + } + + i += 1 + } + + ret := (*iter).Copy() + ret.Next = nil + return ret +} + /* Head * Returns first element in the list * Returns nil if input is not a list or if list is empty diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 7f85b05..7781a96 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -72,6 +72,15 @@ func GenFuncTable() ast.FuncTable { ArgLazy: true, }, + "index": &ast.Function{ + Function: Index, + Name: "index", + Args: []ast.Token_t{ + ast.NUMBER, + ast.LIST, + }, + }, + "head": &ast.Function{ Function: Head, Name: "head", From 87b11cff7ab6426549cbd77eac2615afd28ac3ad Mon Sep 17 00:00:00 2001 From: Aidan Date: Sun, 23 May 2021 21:03:22 -0700 Subject: [PATCH 17/24] updated readme to improve documentation --- Readme.md | 144 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 103 insertions(+), 41 deletions(-) diff --git a/Readme.md b/Readme.md index 448e0b5..6223f87 100644 --- a/Readme.md +++ b/Readme.md @@ -2,86 +2,147 @@ Syntactically Homogeneous Shell ## Overview -This shell was created to have extremely simple syntax. S-Expressions were chosen to represent statements and the scope of language features were constrained to what could be considered practical for daily shell use. This program is meant to be practical for administrators and daily power users. It can be used for both system administration (as one might use bash or zsh) as well as for the creation of simple programs. +This shell was created to have extremely simple syntax. S-Expressions were chosen to represent statements and the scope of the language features were constrained to what could be considered practical for daily shell use. This program is meant to be practical for administrators and daily power users. It can be used for both system administration (as one might use bash or zsh) as well as for the creation of simple programs as one might use Python, Racket, or Visual Basic. ## Basic Syntax -When in doubt the `print_ast` utility can be used to examine the output of the Lex+Parse process. Here you can spot any bugs regarding syntax. +When in doubt the `print_ast` utility can be used to examine the output of the Lexing and Parsing process. Here you can spot any bugs regarding syntax. +`$ print_ast (example invocation` ### Lists -Any sequence of items within a set of parenthesis is a list -`(1 "two" three 4)` +Any sequence of items within a set of parenthesis is a list. -Lists can be infinitely nested -`("one" (2 3 4 (5)))` +For Example: `(1 "two" three 4)` + +Lists can be infinitely nested: `("one" (2 3 4 (5)))` ### Data types -We use the following data types -* Number: 1, 2.0, etc -* String: "this is a string" (string delimiters: ' " \`) -* Bool: T or F -* Symbol: a string with no delimiters -* List: a sequence of elements within parenthesis +Elements in a list can be one of the following data types: +* **Number**: A number can be written in one of the following formats: + - integers: 1, 2, 3, etc... + - rational numbers: 1.22, 2.24, etc... +* **String**: Strings must use one of the following delimiters: + - "this string is a proper string" + - 'this is also a proper string' + - \` this is another proper string\` +* **Boolean**: Boolean values can take the following forms: + - true: "T" + - false: "F" +* **Symbol**: + - A symbol looks like a string without delimiters. + - A symbol denotes a variable or a function. +* **List**: Any combination of multiple items seperated by a space. ### Function calls -Any list beginning in a symbol will be considered a function call. -From within the `shs_repl` utility, unknown symbols will be assumed to be system binaries. +Any list beginning in a symbol will be considered a function call. This departs from traditional lisps, and a user may typically expect to be able to start a list with a variable. Given the current architecture this pattern must be maintained in order to enable shell functionality. Thus, from within the `shs_repl` utility unknown symbols will be assumed to be system binaries. -`(append () 1 2 3)` -`(vim Readme.md)` -`(if (eq "example" (fread 'test_file')) (print "test worked) (rm -rf /))` +Function call: `(append () 1 2 3)` + +System binary call: `(vim Readme.md)` + +Control flow statement (enabled by function call): `(if (eq "example" (fread 'test_file')) (print "test worked) (rm -rf /))` + +There may be alternative REPLs, officially or from community members, that do not exhibit this behavior. ### Variable declaration -There are a few ways to export variables -* export: `(export NAME (value))` -* let: `(let ((var1 val1) (var2 val2)) (form_to_be_evaluated))` +Export is used to define variables. -Currently, let has yet to be implemented +`(export NAME (value))` + +Users may also leverage the `export` function to create aliases in the SHS shell. + +``` +(export vim nvim) +(vim /path/to/file.c) +``` + +The above example will call exec with the arguments "nvim" and "/path/to/file.c". ### Function declaration Use the `func` function from the stdlib: -`(func name (var1, var2, var3) (form_to_be_evaluated))` -In this case, `(form_to_be_evaluated)` will not be evaluated until the function is called. + +Example call: `(func name (var1, var2, var3) (form_to_be_evaluated))` + +In the above example, a function is defined with the following attributes: +- The function is named "name". +- The function takes three arguments: arg1, arg2, and arg3. +- When the function is called, "(form_to_be_evaluated)" is evaluated. + +SHS will only lex (form_to_be_evaluated). It will not parse or execute it until the function is invoked. + +In the below example a function named addthree is defined. The function takes three arguments, and returns the sum of them. + +``` +(func addthree (a b c) (+ a b c)) +(addthree 2 3 4) +Output: 9 +``` ### Control flow -See `stdlib/control_flow.go`. We have if, while, and progn forms: -`(if (cond) (then) (else))` -`(when (cond) (form1)....... (formN))` -`(progn (form1)..... (formN))` -If and While should be self explanatory. For those new to LISP, the rough idea of progn is to evaluate a sequence of N forms and return the result of the final one. -We also have functioning implementations of map and reduce in the stdlib (incomplete) +SHS currently uses the following control flow forms for daily use. Although, it is not hard to create your own in the SHS source code. +#### if statements +The if form takes 3 arguments. It evaluates the first (the condition) if it evaluates to true (T) it evaluates the second argument. If the first argument evaluates to false (F) the `if` routine then evaluates the third argument. The argument that is not used will not be parsed or evaluated. The whole statement will, however, be lexed. + +``` +(if (cond) (then) (else)) +(if (= 1 1.0) (print "numbers are equal") (print "numbers are not equal")) +``` + +#### while loops +The while loop takes N arguments. The first argument, the conditional, must evaluate to a boolean value (T or F). If the first argument is evaluated to T, all subsequent arguments are evaluated. If the first argument evaluates to F, while returns. Finally, the conditional is re-evaluated and the loop begins again. + +``` +(while (cond) (form1)....... (formN)) + +(export iter 1) +(while (< iter 4) + (print (concat "current iteration: " + (string iter))) + (export iter + (+ iter 1))) + +Output: +current iteration: 1 +current iteration: 2 +current iteration: 3 +``` ## Comments The standard delimiter for comments is ; -any characters after a semicolon will be ignored until end of line +Any characters after a semicolon will be ignored until end of line. Semicolons found in strings will not be considered comment delimiters. + +``` +; this comment explains the value in myvar +(export myvar "testvar") +``` ## How to build ### Compiling/Installation -- For now simply run `go install cmd/...` for each utility you wish to use. If you have GOPATH and GOBIN set it should be usable from PATH +- For now simply run `go install ./cmd/shs` for each utility you wish to use. If you have $GOPATH and $GOBIN set in your shell it should be usable with $PATH. ### Adding SHS to your application +Here are some important tips for integrating an SHS REPL into another codebase. + * Make sure to set ast.SyncTablesWithOSEnviron, ast.ExecWhenFuncUndef. All of which control integrations with the underlying system. - If you do not want the user to be able to set environment variables set ast.SyncTablesWithOSEnviron to false. - If you do not want the user to be able to call binaries from the host system, set ast.ExecWhenFuncUndef to false. -- Get text you are interested in parsing -- Create a new VarTable and FuncTable (see ast/var_table.go and ast/func_table.go) -- Call `Lex(text)` on the `text` you want to evaluate to recieve a tree of parsed lexemes. -- Call `tree.Eval(FuncTable, VarTable, false)` where tree is the returned data from Lex, and the final boolean argument is whether or not to convert unknown symbols to strings. (this is a helpful option if you are writing functions such as those in stdlib/call.go, or any funciton in which you may want to be able to edit and transform the final ast based on your own varaiable table) -- Make sure the GPLv3 is adhered to -- *OVERRIDE THE STDLIB GenFuncTable FUNCTION.* You very likely do NOT want an available function to call system binaries in your embedded shell. Make sure the stdlib Call function is not included. +- Create a new VarTable and FuncTable (see ast/var_table.go and ast/func_table.go). +- Make sure to adhere to the terms and conditions stated in the GPLv3. +- *OVERRIDE THE STDLIB GenFuncTable FUNCTION.* You very likely do NOT want an available function to call system binaries in your embedded shell. Make sure the stdlib Call function is not included in your GenFuncTable inplementation. ## Configuration -* variables exported in the repl, if of types string or number, will result in a corresponding variable added to the Environment. -* one can write arbitrary shs script into `.shsrc` including function and variable declarations -* of note are the following variables +* Variables exported in the REPL, if of type string or number, will result in a corresponding environment variable. +* One can write arbitrary shs script into `.shsrc` including function and variable declarations +* Particularly useful are the following variables: - `SH_LOGGING` Sets the log level (from 0 to 3) - `SHS_STATIC_PROMPT` Sets the prompt - `SH_HIST_FILE` Sets the history file - `SH_DEBUG_MODE` Adds additional debug output for the lexer (high clutter) -* additionally, the repl will evaluate any function you define as `_SH_PROMPT` before the shell prompt +* Additionally, the REPL will evaluate any function you define as `_SH_PROMPT` before the shell prompt. - if defined, the function will be evaluated before printing the prompt - the function will be given 0 arguments - if the function does not return a string, its output will be discarded - afterwards, the repl will print the values in `SHS_STATIC_PROMPT` + Here is an example of a shs configuration file: ```lisp (export GOPATH (concat HOME "/go")) @@ -95,6 +156,7 @@ Here is an example of a shs configuration file: ``` ## The Docs +What follows are links to documentation for the code and interfaces used by SHS. This documentation is automatically generated by godoc, and in times of transition may not be complete. - [Documentation on language interpreter](https://godoc.org/gitlab.com/whom/shs/ast) - [Documentation on logging module](https://godoc.org/gitlab.com/whom/shs/log) - [Documentation on configuration module](https://godoc.org/gitlab.com/whom/shs/config) From 8d2811be8706c208ce1c479d3b19d1a404f963c8 Mon Sep 17 00:00:00 2001 From: Aidan Date: Sun, 23 May 2021 21:06:18 -0700 Subject: [PATCH 18/24] squash! updated readme to improve documentation --- Readme.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 6223f87..4d99973 100644 --- a/Readme.md +++ b/Readme.md @@ -6,7 +6,8 @@ This shell was created to have extremely simple syntax. S-Expressions were chose ## Basic Syntax When in doubt the `print_ast` utility can be used to examine the output of the Lexing and Parsing process. Here you can spot any bugs regarding syntax. -`$ print_ast (example invocation` + +`$ print_ast (example invocation)` ### Lists Any sequence of items within a set of parenthesis is a list. @@ -39,7 +40,7 @@ Function call: `(append () 1 2 3)` System binary call: `(vim Readme.md)` -Control flow statement (enabled by function call): `(if (eq "example" (fread 'test_file')) (print "test worked) (rm -rf /))` +Control flow statement: `(if (eq "example" (fread 'test_file')) (print "test worked) (rm -rf /))` There may be alternative REPLs, officially or from community members, that do not exhibit this behavior. From 7e3639d48b29c4bfa79d69dff4d0afbc2383289a Mon Sep 17 00:00:00 2001 From: Aidan Date: Sun, 23 May 2021 21:45:26 -0700 Subject: [PATCH 19/24] more readme updates --- Readme.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index 4d99973..0a3fdc7 100644 --- a/Readme.md +++ b/Readme.md @@ -1,4 +1,4 @@ -# shs +# SHS Syntactically Homogeneous Shell ## Overview @@ -31,10 +31,10 @@ Elements in a list can be one of the following data types: * **Symbol**: - A symbol looks like a string without delimiters. - A symbol denotes a variable or a function. -* **List**: Any combination of multiple items seperated by a space. +* **List**: Any combination of multiple items seperated by a space. (See above.) ### Function calls -Any list beginning in a symbol will be considered a function call. This departs from traditional lisps, and a user may typically expect to be able to start a list with a variable. Given the current architecture this pattern must be maintained in order to enable shell functionality. Thus, from within the `shs_repl` utility unknown symbols will be assumed to be system binaries. +Any list beginning in a symbol will be considered a function call. A user may typically expect to be able to start a list with a variable. However, given the current architecture this pattern must be maintained in order to enable shell functionality. Thus, from within the `shs` utility unknown symbols at the start of a list will be assumed to be system binaries. Function call: `(append () 1 2 3)` @@ -81,7 +81,7 @@ Output: 9 ### Control flow SHS currently uses the following control flow forms for daily use. Although, it is not hard to create your own in the SHS source code. #### if statements -The if form takes 3 arguments. It evaluates the first (the condition) if it evaluates to true (T) it evaluates the second argument. If the first argument evaluates to false (F) the `if` routine then evaluates the third argument. The argument that is not used will not be parsed or evaluated. The whole statement will, however, be lexed. +The if form takes 3 arguments. It evaluates the first argument, the condition, and if it evaluates to true (T) it evaluates the second argument. If the first argument evaluates to false (F) the `if` routine then evaluates the third argument. The argument that is not used will not be parsed or evaluated. The whole statement will, however, be lexed. ``` (if (cond) (then) (else)) @@ -118,12 +118,12 @@ Any characters after a semicolon will be ignored until end of line. Semicolons f ## How to build ### Compiling/Installation -- For now simply run `go install ./cmd/shs` for each utility you wish to use. If you have $GOPATH and $GOBIN set in your shell it should be usable with $PATH. +- For now simply run `go install ./cmd/shs` . If you have $GOPATH and $GOBIN set in your shell it should be usable with $PATH. ### Adding SHS to your application Here are some important tips for integrating an SHS REPL into another codebase. -* Make sure to set ast.SyncTablesWithOSEnviron, ast.ExecWhenFuncUndef. All of which control integrations with the underlying system. +* Make sure to set ast.SyncTablesWithOSEnviron and ast.ExecWhenFuncUndef, both of which control integrations with the underlying system. - If you do not want the user to be able to set environment variables set ast.SyncTablesWithOSEnviron to false. - If you do not want the user to be able to call binaries from the host system, set ast.ExecWhenFuncUndef to false. - Create a new VarTable and FuncTable (see ast/var_table.go and ast/func_table.go). @@ -132,7 +132,7 @@ Here are some important tips for integrating an SHS REPL into another codebase. ## Configuration * Variables exported in the REPL, if of type string or number, will result in a corresponding environment variable. -* One can write arbitrary shs script into `.shsrc` including function and variable declarations +* One can write arbitrary SHS script into `.shsrc` including function and variable declarations * Particularly useful are the following variables: - `SH_LOGGING` Sets the log level (from 0 to 3) - `SHS_STATIC_PROMPT` Sets the prompt @@ -144,7 +144,7 @@ Here are some important tips for integrating an SHS REPL into another codebase. - if the function does not return a string, its output will be discarded - afterwards, the repl will print the values in `SHS_STATIC_PROMPT` -Here is an example of a shs configuration file: +Here is an example of a SHS configuration file: ```lisp (export GOPATH (concat HOME "/go")) (export GOBIN (concat GOPATH "/bin")) From aae28f68b67fd1339a872d65048aadc729d2c862 Mon Sep 17 00:00:00 2001 From: Aidan Date: Sun, 23 May 2021 22:01:46 -0700 Subject: [PATCH 20/24] fixed issue in documentation of while loop --- Readme.md | 14 +++++--------- stdlib/control_flow.go | 6 +++--- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Readme.md b/Readme.md index 0a3fdc7..42f50ec 100644 --- a/Readme.md +++ b/Readme.md @@ -94,17 +94,13 @@ The while loop takes N arguments. The first argument, the conditional, must eval ``` (while (cond) (form1)....... (formN)) -(export iter 1) -(while (< iter 4) - (print (concat "current iteration: " - (string iter))) - (export iter - (+ iter 1))) +(export cond T) +(while cond + (print "this will only be printed once") + (export cond F)) Output: -current iteration: 1 -current iteration: 2 -current iteration: 3 +this will only be printed once ``` ## Comments diff --git a/stdlib/control_flow.go b/stdlib/control_flow.go index 51c4deb..e02def5 100644 --- a/stdlib/control_flow.go +++ b/stdlib/control_flow.go @@ -77,9 +77,9 @@ func ShsIf(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { /* continually eval n forms while element #1 evals to T * has rather progn like behavior in that it returns the result of the last form to be evaluated * - * Example: - * (export cond F) - * (while cond (export cond T) (print "will only be printed once") (+ 1 2)) + * Example: + * (export cond T) + * (while cond (export cond F) (print "will only be printed once") (+ 1 2)) * loop will iter one time, print "will only be printed once" and return 3 */ func ShsWhile(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { From f1d17a2d5fb6393c5fde52999b3c240d0acbbc88 Mon Sep 17 00:00:00 2001 From: Aidan Date: Sun, 23 May 2021 22:02:48 -0700 Subject: [PATCH 21/24] rename license file for godoc --- License.md => LICENSE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename License.md => LICENSE.md (100%) diff --git a/License.md b/LICENSE.md similarity index 100% rename from License.md rename to LICENSE.md From 4efe4bfcaaf74052790b49917439bec9108ddd58 Mon Sep 17 00:00:00 2001 From: Aidan Date: Mon, 31 May 2021 15:05:37 -0700 Subject: [PATCH 22/24] more readme edits --- Readme.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 42f50ec..bb2f536 100644 --- a/Readme.md +++ b/Readme.md @@ -40,7 +40,12 @@ Function call: `(append () 1 2 3)` System binary call: `(vim Readme.md)` -Control flow statement: `(if (eq "example" (fread 'test_file')) (print "test worked) (rm -rf /))` +``` +; complex call +(if (eq "example" (fread 'test_file')) + (print "test worked") + (rm -rf /)) +``` There may be alternative REPLs, officially or from community members, that do not exhibit this behavior. From 901037fa3078ca41665bc0f8866845449c03fc27 Mon Sep 17 00:00:00 2001 From: Aidan Date: Mon, 31 May 2021 15:05:37 -0700 Subject: [PATCH 23/24] more readme edits --- Readme.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 42f50ec..1f32e88 100644 --- a/Readme.md +++ b/Readme.md @@ -40,7 +40,12 @@ Function call: `(append () 1 2 3)` System binary call: `(vim Readme.md)` -Control flow statement: `(if (eq "example" (fread 'test_file')) (print "test worked) (rm -rf /))` +``` +; complex call +(if (eq "example" (fread 'test_file')) + (print "test worked") + (rm -rf /)) +``` There may be alternative REPLs, officially or from community members, that do not exhibit this behavior. @@ -49,6 +54,11 @@ Export is used to define variables. `(export NAME (value))` +Example export: +``` +(export vim 'nvim') +``` + Users may also leverage the `export` function to create aliases in the SHS shell. ``` @@ -85,7 +95,9 @@ The if form takes 3 arguments. It evaluates the first argument, the condition, a ``` (if (cond) (then) (else)) -(if (= 1 1.0) (print "numbers are equal") (print "numbers are not equal")) +(if (eq 1 1.0) + (print "numbers are equal") + (print "numbers are not equal")) ``` #### while loops @@ -114,7 +126,12 @@ Any characters after a semicolon will be ignored until end of line. Semicolons f ## How to build ### Compiling/Installation -- For now simply run `go install ./cmd/shs` . If you have $GOPATH and $GOBIN set in your shell it should be usable with $PATH. +1. [follow Google's instructions to install Go](https://golang.org/doc/install) + - after these instructions you should have $GOPATH set +2. run `$ go get -u gitlab.com/whom/shs` +3. change directory to $GOPATH/src/gitlab.com/whom/shs +4. run `$ go install ./cmd/shs.go` + - if you have the Go binary directory in your path you can now call `shs` via the shell ### Adding SHS to your application Here are some important tips for integrating an SHS REPL into another codebase. @@ -153,13 +170,14 @@ Here is an example of a SHS configuration file: ``` ## The Docs + +#### Godoc What follows are links to documentation for the code and interfaces used by SHS. This documentation is automatically generated by godoc, and in times of transition may not be complete. - [Documentation on language interpreter](https://godoc.org/gitlab.com/whom/shs/ast) - [Documentation on logging module](https://godoc.org/gitlab.com/whom/shs/log) - [Documentation on configuration module](https://godoc.org/gitlab.com/whom/shs/config) - [Documentation on utility functions](https://godoc.org/gitlab.com/whom/shs/util) - [Documentation on standard library](https://godoc.org/gitlab.com/whom/shs/stdlib) -- [Guide to standard library](https://gitlab.com/whom/shs/-/blob/master/stdlib/Readme.md) ## Contributing - Any contribution to this software is welcome as long as it adheres to the conduct guidelines specified in the `Contributing.md` file in this repository. From 5b8716c0c41a950b7255cfddddda893dcb26879e Mon Sep 17 00:00:00 2001 From: Aidan Date: Mon, 31 May 2021 15:36:12 -0700 Subject: [PATCH 24/24] extend build instructions --- Readme.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Readme.md b/Readme.md index 1f32e88..88944fd 100644 --- a/Readme.md +++ b/Readme.md @@ -127,11 +127,14 @@ Any characters after a semicolon will be ignored until end of line. Semicolons f ## How to build ### Compiling/Installation 1. [follow Google's instructions to install Go](https://golang.org/doc/install) - - after these instructions you should have $GOPATH set -2. run `$ go get -u gitlab.com/whom/shs` -3. change directory to $GOPATH/src/gitlab.com/whom/shs -4. run `$ go install ./cmd/shs.go` - - if you have the Go binary directory in your path you can now call `shs` via the shell +2. run `$ mkdir -p ~/go` +3. add the following commands to your shell configuration, and run them in your shell + - `export GOPATH $HOME/go` + - `export PATH $PATH:$GOPATH/bin` +4. run `$ go get -u gitlab.com/whom/shs` +5. change directory to `$GOPATH/src/gitlab.com/whom/shs` +6. run `$ go install ./cmd/shs.go` +7. you can now call `shs` via the shell ### Adding SHS to your application Here are some important tips for integrating an SHS REPL into another codebase.