Merge branch 'dev' into 'master'

Merge new features to master

See merge request whom/shs!10
This commit is contained in:
Aidan Hahn 2020-08-27 06:51:49 +00:00
commit c2b86b7f4d
14 changed files with 720 additions and 355 deletions

View file

@ -17,7 +17,9 @@
package ast package ast
import "gitlab.com/whom/shs/log" import (
"gitlab.com/whom/shs/log"
)
/* determines whether or not to execute a system binary /* determines whether or not to execute a system binary
* when a function cannot be found in the functable * when a function cannot be found in the functable

View file

@ -18,7 +18,6 @@
package ast package ast
import ( import (
"fmt"
"gitlab.com/whom/shs/log" "gitlab.com/whom/shs/log"
) )
@ -38,8 +37,18 @@ type Function struct {
// number of times user has called this function // number of times user has called this function
TimesCalled int TimesCalled int
// number of args required // list of types (LIST, NUMBER, STRING, etc) representing args
Args int Args []Token_t
NumArgs int // -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 /* holds a mapping of key to function
@ -52,24 +61,58 @@ type FuncTable *map[string]*Function
* makes sure correct arguments are passed in * makes sure correct arguments are passed in
*/ */
func (f Function) ParseFunction(args *Token) bool { func (f Function) ParseFunction(args *Token) bool {
// handle infinite args inc := 0
if f.Args < 0 { for iter := args; iter != nil; iter = iter.Next {
inc += 1
if inc > len(f.Args) {
log.Log(log.ERR,
"too many arguments",
"ftable")
return false
}
if iter.Tag != f.Args[inc - 1] {
log.Log(log.ERR,
"argument is " + GetTagAsStr(iter.Tag) +
" should be " + GetTagAsStr(f.Args[inc - 1]),
"ftable")
return false
}
}
if inc < len(f.Args) {
log.Log(log.ERR,
"not enough args given",
"ftable")
return false
}
return true return true
} }
i := f.Args /* same as ParseFunction but only evaluates the number of args
for iter := args; iter != nil; iter = iter.Next { */
i -= 1 func (f Function) LazyParseFunction(args *Token) bool {
if f.NumArgs < 0 {
return true
} }
if i != 0 { total := 0
for iter := args; iter != nil; iter = iter.Next {
total += 1
}
if total < f.NumArgs {
log.Log(log.ERR, log.Log(log.ERR,
"Incorrect number of arguments", "expected more arguments, try calling info on function",
"eval") "ftable")
log.Log(log.DEBUG, return false
fmt.Sprintf("Function %s expects %d arguments. You've provided %d arguments.", }
f.Name, f.Args, f.Args - i),
"eval") if total > f.NumArgs {
log.Log(log.ERR,
"too many args. try calling info on function",
"ftable")
return false return false
} }
@ -80,7 +123,19 @@ func (f Function) ParseFunction(args *Token) bool {
* calls ParseFunction and increments TimesCalled * calls ParseFunction and increments TimesCalled
*/ */
func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token { func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token {
if !f.ParseFunction(args) { n_args := args
if !f.EvalLazy {
n_args = args.Eval(ft, vt, f.SymLazy)
}
passes := false
if f.ArgLazy {
passes = f.LazyParseFunction(n_args)
} else {
passes = f.ParseFunction(n_args)
}
if !passes {
log.Log(log.ERR, log.Log(log.ERR,
"Couldnt call " + f.Name, "Couldnt call " + f.Name,
"eval") "eval")
@ -88,7 +143,7 @@ func (f Function) CallFunction(args *Token, vt VarTable, ft FuncTable) *Token {
} }
f.TimesCalled += 1 f.TimesCalled += 1
return f.Function(args, vt, ft) return f.Function(n_args, vt, ft)
} }
/* searches for function mapped to argument in FuncTable /* searches for function mapped to argument in FuncTable

View file

@ -162,14 +162,22 @@ func lex(input string) *Token {
// comment case // comment case
case ';': case ';':
i = matchLineEnd(i)
start_pos = i + 1 start_pos = i + 1
i = matchLineEnd(start_pos)
// 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 { if needs_alloc {
needs_alloc = false needs_alloc = false
if (i < 0) { if (i < 0) {
// TODO: Maybe not overload this.
start_pos = i start_pos = i
goto error goto error
} }
@ -186,8 +194,6 @@ func lex(input string) *Token {
return ret return ret
error: error:
// TODO: Hook into error module
// TODO: Finalize and GC alloced tokens
if start_pos == -1 { if start_pos == -1 {
log.Log(log.ERR, log.Log(log.ERR,
"Unmatched string delimiter in input. discarding.", "Unmatched string delimiter in input. discarding.",
@ -209,6 +215,11 @@ error:
func StrIsNumber(arg string) bool { func StrIsNumber(arg string) bool {
dotCount := 0 dotCount := 0
// negative nums
if len(arg) > 1 && arg[0] == '-' {
arg = arg[1:]
}
for _, char := range arg { for _, char := range arg {
if !unicode.IsDigit(char) { if !unicode.IsDigit(char) {
if char == '.' && dotCount == 0 { if char == '.' && dotCount == 0 {

View file

@ -95,7 +95,7 @@ func main() {
} }
dyn_prompt := ast.GetFunction("_SH_PROMPT", funcs) 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 dyn_prompt = nil
} }
@ -149,6 +149,11 @@ func main() {
fmt.Printf(prePrompt) fmt.Printf(prePrompt)
text, err := line.Prompt(prompt) text, err := line.Prompt(prompt)
if err != nil && err != liner.ErrPromptAborted{ 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") log.Log(log.ERR, "couldnt read user input: " + err.Error(), "repl")
continue continue
} }

View file

@ -36,14 +36,6 @@ import (
* Example: (number "3.4") * Example: (number "3.4")
*/ */
func NumCast(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token { 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()) { if !ast.StrIsNumber(in.Value()) {
log.Log(log.ERR, log.Log(log.ERR,
"string failed number cast", "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 { func Add(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
var res float64 var res float64
in = in.Eval(f, a, false)
for i := in; i != nil; i = i.Next { for i := in; i != nil; i = i.Next {
if i.Tag != ast.NUMBER { if i.Tag != ast.NUMBER {
log.Log(log.ERR, "Non-number given to ADD", "add") 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 { func Sub(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
var res float64 var res float64
var sub float64 var sub float64
in = in.Eval(f, a, false)
for i := in; i != nil; i = i.Next { for i := in; i != nil; i = i.Next {
if i.Tag != ast.NUMBER { if i.Tag != ast.NUMBER {
log.Log(log.ERR, "Non-number given to SUB", "sub") 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 { func Mult(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
res := 1.0 res := 1.0
in = in.Eval(f, a, false)
for i := in; i != nil; i = i.Next { for i := in; i != nil; i = i.Next {
if i.Tag != ast.NUMBER { if i.Tag != ast.NUMBER {
log.Log(log.ERR, "Non-number given to MULT", "mult") 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 { func Div(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
var res float64 var res float64
in = in.Eval(f, a, false)
for i := in; i != nil; i = i.Next { for i := in; i != nil; i = i.Next {
inner := 0.0 inner := 0.0

View file

@ -30,14 +30,6 @@ import (
* Example: (bool "F") * Example: (bool "F")
*/ */
func BoolCast(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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() body := in.Value()
if body != ast.TRUE && body != ast.FALSE { if body != ast.TRUE && body != ast.FALSE {
log.Log(log.ERR, 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 { 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 out := ast.TRUE
if in.Value() == ast.TRUE { if in.Value() == ast.TRUE {
out = ast.FALSE out = ast.FALSE
@ -69,10 +54,9 @@ func Not(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
return t return t
} }
// Lazy args this
func Eq(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { func Eq(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
out := ast.TRUE out := ast.TRUE
in = in.Eval(ft, vt, false)
second := in.Next second := in.Next
if in.Tag != second.Tag { 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 out := ast.TRUE
second := in.Next 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) l, _ := strconv.ParseInt(in.Value(), 10, 64)
r, _ := strconv.ParseInt(second.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 out := ast.TRUE
second := in.Next 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) l, _ := strconv.ParseInt(in.Value(), 10, 64)
r, _ := strconv.ParseInt(second.Value(), 10, 64) r, _ := strconv.ParseInt(second.Value(), 10, 64)

View file

@ -54,20 +54,6 @@ func AbsPath(arg string) string {
* (cd (concat HOME "/go")) * (cd (concat HOME "/go"))
*/ */
func Cd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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()) err := os.Chdir(in.Value())
if err != nil { if err != nil {
log.Log(log.ERR, err.Error(), "cd") 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) * (fexists test)
*/ */
func Fexists(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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() filename := in.Value()
out := ast.TRUE out := ast.TRUE
@ -115,7 +93,6 @@ func Fexists(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
* (fread (concat HOME ".shsrc")) * (fread (concat HOME ".shsrc"))
*/ */
func Fread(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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 exists := Fexists(in, vt, ft) // some waste, extra use of Eval
if exists == nil || exists.Tag != ast.BOOL || exists.Value() == ast.FALSE { if exists == nil || exists.Tag != ast.BOOL || exists.Value() == ast.FALSE {
log.Log(log.ERR, 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") * (fwrite "test" "one two three")
*/ */
func Fwrite(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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 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( err := ioutil.WriteFile(
AbsPath(in.Value()), AbsPath(in.Value()),
[]byte(text.Value()), []byte(text.Value()),
0644) 0644)
if err != nil { if err != nil {
log.Log(log.ERR, log.Log(log.ERR,
"error writing file: " + err.Error(), "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") * (fwrite "test" "one two three")
*/ */
func Fappend(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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 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) exists := Fexists(in, vt, ft)
if exists.Value() == ast.FALSE { if exists.Value() == ast.FALSE {
log.Log(log.ERR, log.Log(log.ERR,
@ -212,14 +160,15 @@ func Fappend(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
AbsPath(in.Value()), AbsPath(in.Value()),
os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.O_APPEND|os.O_CREATE|os.O_WRONLY,
0644) 0644)
if err != nil { if err != nil {
log.Log(log.ERR, log.Log(log.ERR,
"couldnt open file for append: " + err.Error(), "couldnt open file for append: " + err.Error(),
"fappend") "fappend")
return nil return nil
} }
defer f.Close()
defer f.Close()
if _, err := f.WriteString(text.Value()); err != nil { if _, err := f.WriteString(text.Value()); err != nil {
log.Log(log.ERR, log.Log(log.ERR,
"error appending to file: " + err.Error(), "error appending to file: " + err.Error(),

View file

@ -113,7 +113,8 @@ func DeclFunc(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Tok
Function: inner, Function: inner,
Name: name.Value(), Name: name.Value(),
TimesCalled: 0, TimesCalled: 0,
Args: numArgs, NumArgs: numArgs,
ArgLazy: true,
} }
return nil return nil
} }

View file

@ -18,6 +18,8 @@
package stdlib package stdlib
import ( import (
"fmt"
"strconv"
"gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/ast"
"gitlab.com/whom/shs/log" "gitlab.com/whom/shs/log"
) )
@ -28,12 +30,7 @@ import (
* in event a not-list is passed in, returns the arg. * in event a not-list is passed in, returns the arg.
*/ */
func Expand(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token { func Expand(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token {
if input.Tag != ast.LIST { return input.Expand()
log.Log(log.INFO, "expand called on not a list", "expand")
return input
}
return input.Eval(funcs, vars, false).Expand()
} }
/* L_APPEND (append from repl) /* L_APPEND (append from repl)
@ -68,3 +65,187 @@ func L_append(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Tok
return src return src
} }
/* Len
* 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 {
if input.Tag != ast.LIST && input.Tag != ast.STRING {
log.Log(log.ERR,
"must provide list or strinig",
"head")
return nil
}
length := 0
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}
ret.Set(fmt.Sprintf("%d", length))
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
*
* Example: (head (2 3 4)) -> 2
*/
func Head(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token {
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 {
iter := input.Expand()
for iter != nil && iter.Next != nil {
iter = iter.Next
}
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 {
start := input
end := input.Next
source := end.Next
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
}

View file

@ -225,8 +225,8 @@ func LaunchProcess(
* Example (l vim file.txt) * Example (l vim file.txt)
*/ */
func Call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { func Call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
in = in.Eval(ft, vt, true)
if in == nil { if in == nil {
log.Log(log.ERR, "no arguments given", "call")
return nil return nil
} }
@ -261,11 +261,6 @@ func Call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
* Example: (bg vim file.txt) * Example: (bg vim file.txt)
*/ */
func Bgcall(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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 { if in.Tag == ast.LIST {
log.Log(log.ERR, "couldnt exec, target bin is a list", "call") log.Log(log.ERR, "couldnt exec, target bin is a list", "call")
return nil return nil
@ -304,14 +299,6 @@ func Fg(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
return nil 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) pid, err := strconv.ParseFloat(in.Value(), 64)
if err != nil { if err != nil {
log.Log(log.ERR, log.Log(log.ERR,
@ -374,12 +361,6 @@ func Jobs(in *ast.Token, vt ast.VarTable, fg ast.FuncTable) *ast.Token {
* Example: ($ echo hello world) * Example: ($ echo hello world)
*/ */
func ReadCmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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 { if in.Tag == ast.LIST {
log.Log(log.ERR, "couldnt exec, target bin is a list", "call") log.Log(log.ERR, "couldnt exec, target bin is a list", "call")
return nil return nil
@ -434,13 +415,6 @@ func GetExit(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
* (this function not added to functable in stdlib.go) * (this function not added to functable in stdlib.go)
*/ */
func Kill(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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) pid, err := strconv.ParseInt(in.Value(), 10, 64)
if err != nil { if err != nil {
log.Log(log.ERR, "error parsing arg to kill: " + err.Error(), "kill") log.Log(log.ERR, "error parsing arg to kill: " + err.Error(), "kill")

View file

@ -20,7 +20,6 @@ package stdlib
import ( import (
"os" "os"
"fmt" "fmt"
"gitlab.com/whom/shs/log"
"gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/ast"
"gitlab.com/whom/shs/util" "gitlab.com/whom/shs/util"
) )
@ -34,225 +33,332 @@ func GenFuncTable() ast.FuncTable {
"if": &ast.Function{ "if": &ast.Function{
Function: ShsIf, Function: ShsIf,
Name: "if", Name: "if",
TimesCalled: 0, NumArgs: 3,
Args: 3, EvalLazy: true,
ArgLazy: true,
}, },
"while": &ast.Function{ "while": &ast.Function{
Function: ShsWhile, Function: ShsWhile,
Name: "while", Name: "while",
TimesCalled: 0, NumArgs: -1,
Args: -1, EvalLazy: true,
ArgLazy: true,
}, },
"progn": &ast.Function{ "progn": &ast.Function{
Function: ShsProgn, Function: ShsProgn,
Name: "shs_progn", Name: "shs_progn",
TimesCalled: 0, NumArgs: -1,
Args: -1, EvalLazy: true,
ArgLazy: true,
}, },
"func": &ast.Function{ "func": &ast.Function{
Function: DeclFunc, Function: DeclFunc,
Name: "decl_func", Name: "decl_func",
TimesCalled: 0, Args: []ast.Token_t{
Args: 3, ast.SYMBOL,
ast.LIST,
ast.LIST,
},
EvalLazy: true,
},
"len": &ast.Function{
Function: Len,
Name: "len",
NumArgs: 1,
ArgLazy: true,
},
"index": &ast.Function{
Function: Index,
Name: "index",
Args: []ast.Token_t{
ast.NUMBER,
ast.LIST,
},
},
"head": &ast.Function{
Function: Head,
Name: "head",
Args: []ast.Token_t{
ast.LIST,
},
},
"tail": &ast.Function{
Function: Tail,
Name: "tail",
Args: []ast.Token_t{
ast.LIST,
},
},
"slice": &ast.Function{
Function: Slice,
Name: "slice",
Args: []ast.Token_t{
ast.NUMBER,
ast.NUMBER,
ast.LIST,
},
}, },
"export": &ast.Function{ "export": &ast.Function{
Function: Export, Function: Export,
Name: "export", Name: "export",
TimesCalled: 0, EvalLazy: true,
Args: 2, ArgLazy: true,
NumArgs: 2,
}, },
"input": &ast.Function{ "input": &ast.Function{
Function: Input, Function: Input,
Name: "input", Name: "input",
TimesCalled: 0, Args: []ast.Token_t{
Args: 1, ast.STRING,
},
}, },
"load": &ast.Function{ "load": &ast.Function{
Function: Load, Function: Load,
Name: "load", Name: "load",
TimesCalled: 0, Args: []ast.Token_t{
Args: 1, ast.STRING,
},
}, },
"bool": &ast.Function{ "bool": &ast.Function{
Function: BoolCast, Function: BoolCast,
Name: "bool", Name: "bool",
TimesCalled: 0, Args: []ast.Token_t{
Args: 1, ast.STRING,
},
}, },
"string": &ast.Function{ "string": &ast.Function{
Function: StrCast, Function: StrCast,
Name: "string", Name: "string",
TimesCalled: 0, NumArgs: 1,
Args: 1, ArgLazy: true,
}, },
"number": &ast.Function{ "number": &ast.Function{
Function: NumCast, Function: NumCast,
Name: "number", Name: "number",
TimesCalled: 0, Args: []ast.Token_t{
Args: 1, ast.STRING,
},
}, },
"...": &ast.Function{ "...": &ast.Function{
Function: Expand, Function: Expand,
Name: "...", Name: "...",
TimesCalled: 0, Args: []ast.Token_t{
Args: 1, ast.LIST,
},
}, },
"append": &ast.Function{ "append": &ast.Function{
Function: L_append, Function: L_append,
Name: "append", Name: "append",
TimesCalled: 0, NumArgs: -1,
Args: -1, ArgLazy: true,
},
"join": &ast.Function{
Function: Join,
Name: "join",
Args: []ast.Token_t{
ast.STRING,
ast.LIST,
},
},
"split": &ast.Function{
Function: Split,
Name: "split",
Args: []ast.Token_t{
ast.STRING,
ast.STRING,
},
},
"replace": &ast.Function{
Function: Replace,
Name: "replace",
Args: []ast.Token_t{
ast.STRING,
ast.STRING,
ast.STRING,
},
},
"substr": &ast.Function{
Function: Substr,
Name: "substr",
Args: []ast.Token_t{
ast.NUMBER,
ast.NUMBER,
ast.STRING,
},
}, },
"exit": &ast.Function{ "exit": &ast.Function{
Function: ExitShell, Function: ExitShell,
Name: "exit", Name: "exit",
TimesCalled: 0, Args: []ast.Token_t{},
Args: 0,
}, },
"eq": &ast.Function{ "eq": &ast.Function{
Function: Eq, Function: Eq,
Name: "==", Name: "==",
TimesCalled: 0, NumArgs: 2,
Args: 2, ArgLazy: true,
}, },
"ne": &ast.Function{ "ne": &ast.Function{
Function: Ne, Function: Ne,
Name: "!=", Name: "!=",
TimesCalled: 0, NumArgs: 2,
Args: 2, ArgLazy: true,
}, },
"<": &ast.Function{ "<": &ast.Function{
Function: Lt, Function: Lt,
Name: "<", Name: "<",
TimesCalled: 0, Args: []ast.Token_t{
Args: 2, ast.NUMBER,
ast.NUMBER,
},
}, },
">": &ast.Function{ ">": &ast.Function{
Function: Gt, Function: Gt,
Name: ">", Name: ">",
TimesCalled: 0, Args: []ast.Token_t{
Args: 2, ast.NUMBER,
ast.NUMBER,
},
}, },
"<=": &ast.Function{ "<=": &ast.Function{
Function: Lte, Function: Lte,
Name: "<=", Name: "<=",
TimesCalled: 0, Args: []ast.Token_t{
Args: 2, ast.NUMBER,
ast.NUMBER,
},
}, },
">=": &ast.Function{ ">=": &ast.Function{
Function: Gte, Function: Gte,
Name: ">=", Name: ">=",
TimesCalled: 0, Args: []ast.Token_t{
Args: 2, ast.NUMBER,
ast.NUMBER,
},
}, },
"!": &ast.Function{ "!": &ast.Function{
Function: Not, Function: Not,
Name: "!", Name: "!",
TimesCalled: 0, Args: []ast.Token_t{
Args: 1, ast.BOOL,
},
}, },
"+": &ast.Function{ "+": &ast.Function{
Function: Add, Function: Add,
Name: "add", Name: "add",
TimesCalled: 0, NumArgs: -1,
Args: -1, ArgLazy: true,
}, },
"-": &ast.Function{ "-": &ast.Function{
Function: Sub, Function: Sub,
Name: "sub", Name: "sub",
TimesCalled: 0, NumArgs: -1,
Args: -1, ArgLazy: true,
}, },
"*": &ast.Function{ "*": &ast.Function{
Function: Mult, Function: Mult,
Name: "mult", Name: "mult",
TimesCalled: 0, NumArgs: -1,
Args: -1, ArgLazy: true,
}, },
"/": &ast.Function{ "/": &ast.Function{
Function: Div, Function: Div,
Name: "div", Name: "div",
TimesCalled: 0, NumArgs: -1,
Args: -1, ArgLazy: true,
}, },
"cd": &ast.Function{ "cd": &ast.Function{
Function: Cd, Function: Cd,
Name: "changedir", Name: "changedir",
TimesCalled: 0, SymLazy: true,
Args: 1, Args: []ast.Token_t{
ast.STRING,
},
}, },
"concat": &ast.Function{ "concat": &ast.Function{
Function: Concat, Function: Concat,
Name: "concatenate", Name: "concatenate",
TimesCalled: 0, NumArgs: -1,
Args: -1, ArgLazy: true,
}, },
"print": &ast.Function{ "print": &ast.Function{
Function: PrintStr, Function: PrintStr,
Name: "print", Name: "print",
TimesCalled: 0, Args: []ast.Token_t{
Args: 1, ast.STRING,
},
}, },
"l": &ast.Function{ "l": &ast.Function{
Function: Call, Function: Call,
Name: "call", Name: "call",
TimesCalled: 0, NumArgs: -1,
Args: -1, ArgLazy: true,
SymLazy: true,
}, },
"bg": &ast.Function{ "bg": &ast.Function{
Function: Bgcall, Function: Bgcall,
Name: "background call", Name: "background call",
TimesCalled: 0, NumArgs: -1,
Args: -1, ArgLazy: true,
SymLazy: true,
}, },
"fg": &ast.Function{ "fg": &ast.Function{
Function: Fg, Function: Fg,
Name: "foreground", Name: "foreground",
TimesCalled: 0, Args: []ast.Token_t{
Args: 1, ast.NUMBER,
},
}, },
"$": &ast.Function{ "$": &ast.Function{
Function: ReadCmd, Function: ReadCmd,
Name: "read cmd", Name: "read cmd",
TimesCalled: 0, NumArgs: -1,
Args: -1, ArgLazy: true,
SymLazy: true,
}, },
"?": &ast.Function{ "?": &ast.Function{
Function: GetExit, Function: GetExit,
Name: "get exit code", Name: "get exit code",
TimesCalled: 0, Args: []ast.Token_t{},
Args: 0,
}, },
/* /*
@ -260,7 +366,6 @@ func GenFuncTable() ast.FuncTable {
"kill": &ast.Function{ "kill": &ast.Function{
Function: kill, Function: kill,
Name: "kill job", Name: "kill job",
TimesCalled: 0,
Args: 1, Args: 1,
}, },
*/ */
@ -268,43 +373,49 @@ func GenFuncTable() ast.FuncTable {
"jobs": &ast.Function{ "jobs": &ast.Function{
Function: Jobs, Function: Jobs,
Name: "list jobs", Name: "list jobs",
TimesCalled: 0, Args: []ast.Token_t{},
Args: 0,
}, },
"info": &ast.Function{ "info": &ast.Function{
Function: ShInfo, Function: ShInfo,
Name: "Shell Info", Name: "Shell Info",
TimesCalled: 0, Args: []ast.Token_t{
Args: 1, ast.SYMBOL,
},
}, },
"fexists": &ast.Function{ "fexists": &ast.Function{
Function: Fexists, Function: Fexists,
Name: "file exists", Name: "file exists",
TimesCalled: 0, Args: []ast.Token_t{
Args: 1, ast.STRING,
},
}, },
"fread": &ast.Function{ "fread": &ast.Function{
Function: Fread, Function: Fread,
Name: "read file", Name: "read file",
TimesCalled: 0, Args: []ast.Token_t{
Args: 1, ast.STRING,
},
}, },
"fwrite": &ast.Function{ "fwrite": &ast.Function{
Function: Fwrite, Function: Fwrite,
Name: "write file", Name: "write file",
TimesCalled: 0, Args: []ast.Token_t{
Args: 2, ast.STRING,
ast.STRING,
},
}, },
"fappend": &ast.Function{ "fappend": &ast.Function{
Function: Fappend, Function: Fappend,
Name: "append to file", Name: "append to file",
TimesCalled: 0, Args: []ast.Token_t{
Args: 2, ast.STRING,
ast.STRING,
},
}, },
} }
@ -327,32 +438,32 @@ func ExitShell(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
* Example: (info append) * Example: (info append)
*/ */
func ShInfo(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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) repr := ast.GetVar(in.Value(), vt)
if repr != nil { if repr != nil {
fmt.Printf("VARIABLE\nTYPE: %s\nVALUE: %s\n", ast.GetTagAsStr(repr.Tag), repr.Value()) fmt.Printf("VARIABLE\nTYPE: %s\nVALUE: %s\n", ast.GetTagAsStr(repr.Tag), repr.Value())
break return nil
} }
funct := ast.GetFunction(in.Value(), ft) funct := ast.GetFunction(in.Value(), ft)
if funct != nil { 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\n\n", funct.Name, funct.TimesCalled)
break 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") fmt.Printf("UNKNOWN SYMBOL\n")
}
return nil return nil
} }
@ -364,14 +475,6 @@ func ShInfo(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
* Example: (print (input)) * Example: (print (input))
*/ */
func Input(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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() prompt := in.Value()
var output string var output string
@ -389,14 +492,6 @@ func Input(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
* Example: (load "myscript.shs") * Example: (load "myscript.shs")
*/ */
func Load(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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 := in.Value()
bp = AbsPath(bp) bp = AbsPath(bp)
util.LoadScript(bp, vt, ft) util.LoadScript(bp, vt, ft)

View file

@ -19,6 +19,7 @@ package stdlib
import ( import (
"fmt" "fmt"
"strings"
"strconv" "strconv"
"gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/ast"
"gitlab.com/whom/shs/log" "gitlab.com/whom/shs/log"
@ -29,8 +30,6 @@ import (
* Example: (concat "hello" " " "world") * Example: (concat "hello" " " "world")
*/ */
func Concat(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { func Concat(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
in = in.Eval(ft, vt, false)
var res string var res string
for i := in; i != nil; i = i.Next { for i := in; i != nil; i = i.Next {
if i.Tag == ast.LIST { if i.Tag == ast.LIST {
@ -53,12 +52,148 @@ func Concat(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
* Example: (string 1) -> 1.0 * Example: (string 1) -> 1.0
*/ */
func StrCast(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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 := &ast.Token{ Tag: ast.STRING }
res.Set(body) res.Set(body)
return res 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 {
body := in.Value()
delim := in.Next.Value()
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 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
*
* Example: (join ", " ("apple" "ananas" "pear")) -> "apple, ananas, pear"
*/
func Join(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
delim := in
strs := in.Next
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 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
end := start.Next
str := end.Next
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 /* Takes one arg, returns nil
* Prints a string to stdout * Prints a string to stdout
* Unquotes string so user can add escaped chars like \n, \t, etc * Unquotes string so user can add escaped chars like \n, \t, etc
@ -66,7 +201,7 @@ func StrCast(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
* Example: (print "Line: \n, Tab: \t") * Example: (print "Line: \n, Tab: \t")
*/ */
func PrintStr(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { 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] != '"' { if body[0] != body[len(body)-1] && body[0] != '"' {
body = "`" + body + "`" body = "`" + body + "`"
} }

View file

@ -34,6 +34,12 @@ func Export(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token
name := input name := input
form := name.Next.Eval(funcs, vars, false) form := name.Next.Eval(funcs, vars, false)
// error in eval process
if form == nil {
return nil
}
if name.Tag != ast.SYMBOL { if name.Tag != ast.SYMBOL {
log.Log(log.ERR, log.Log(log.ERR,
"first arg should be a symbol", "first arg should be a symbol",

View file

@ -80,7 +80,8 @@ func ShellCompleter(line string, vt ast.VarTable, ft ast.FuncTable) []string {
completions := []string{} completions := []string{}
for _, i := range compSource { for _, i := range compSource {
if strings.HasPrefix(i, tail) { if strings.HasPrefix(i, tail) {
completions = append(completions, head + i) str := strings.ReplaceAll(i, " ", "\\ ")
completions = append(completions, head + str)
} }
} }