diff --git a/Readme.md b/Readme.md index e0d032e..f8abce7 100644 --- a/Readme.md +++ b/Readme.md @@ -49,6 +49,10 @@ See `stdlib/control_flow.go`. We have if and while forms: We also have functioning implementations of map and reduce in the stdlib (incomplete) +## Comments +The standard delimiter for comments is ; +any characters after a semicolon will be ignored until end of line + ## 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 @@ -69,9 +73,14 @@ We also have functioning implementations of map and reduce in the stdlib (incomp * one can write arbitrary shs script into `.shsrc` including function and variable declarations * of note are the following variables - `SH_LOGGING` Sets the log level (from 0 to 3) - - `SHS_SH_PROMPT` Sets the prompt + - `SHS_STATIC_PROMPT` Sets the prompt - `SH_HIST_FILE` Sets the history file - - `SH_DEBUG_MODE` Adds additional debug output for the lexer + - `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 + - 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")) @@ -80,6 +89,8 @@ Here is an example of a shs configuration file: (export GIT_TERMINAL_PROMPT 1) (export SH_HIST_FILE (concat HOME "/.shs_hist")) (export SH_LOGGING 0) +(export SHS_STATIC_PROMPT ">") +(func _SH_PROMPT () (concat (?) ($ basename ($ pwd)) "\n")) ``` ## Contributing diff --git a/ast/eval.go b/ast/eval.go index 0f6cc98..48c6ff4 100644 --- a/ast/eval.go +++ b/ast/eval.go @@ -61,6 +61,8 @@ func (in *Token) Eval(funcs FuncTable, vars VarTable, cnvtUndefVars bool) *Token "eval") return nil } + } else { + res.Next = in.Next } case LIST: diff --git a/ast/lex.go b/ast/lex.go index 532f32a..285fd94 100644 --- a/ast/lex.go +++ b/ast/lex.go @@ -121,6 +121,17 @@ func lex(input string) *Token { return -2 } + // returns the end of the string OR the end of the line + matchLineEnd := func(start int) int { + for i := start; i < len(input); i++ { + if input[i] == '\n' { + return i + } + } + + return len(input) + } + needs_alloc := false start_pos := 0 for i := 0; i < len(input); i++ { @@ -137,13 +148,18 @@ func lex(input string) *Token { is_str = true needs_alloc = true - case ' ': + case ' ', '\n', '\t', '\v', '\f', '\r': if i == start_pos { start_pos += 1 continue } needs_alloc = true + + // comment case + case ';': + start_pos = i + 1 + i = matchLineEnd(start_pos) } if needs_alloc { diff --git a/cmd/shs_repl.go b/cmd/shs_repl.go index ea6007c..1535264 100644 --- a/cmd/shs_repl.go +++ b/cmd/shs_repl.go @@ -18,9 +18,10 @@ package main import ( + "os" "fmt" "strconv" - "github.com/chzyer/readline" + "github.com/peterh/liner" "gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/log" "gitlab.com/whom/shs/config" @@ -54,6 +55,7 @@ func main() { var prompt string var debug string var hist string + no_hist := false ast.SyncTablesWithOSEnviron = true ast.ExecWhenFuncUndef = true @@ -67,34 +69,61 @@ func main() { hist_t := ast.GetVar("SH_HIST_FILE", vars) if hist_t != nil { hist = hist_t.Value() + } else { + no_hist = true } - prompt_t := ast.GetVar("SHS_SH_PROMPT", vars) + dyn_prompt := ast.GetFunction("_SH_PROMPT", funcs) + if dyn_prompt == nil || dyn_prompt.Args != 0 { + dyn_prompt = nil + } + + prompt_t := ast.GetVar("SHS_STATIC_PROMPT", vars) if prompt_t != nil { prompt = prompt_t.Value() } else { prompt = def_prompt } - rl, err := readline.NewEx(&readline.Config{ - Prompt: prompt, - HistoryFile: hist, - InterruptPrompt: "^C", - }) + line := liner.NewLiner() + defer line.Close() - defer rl.Close() - if err != nil { - log.Log(log.ERR, "Couldnt initialize readline: " + err.Error(), "repl") - return + line.SetCtrlCAborts(true) + + var histFile *os.File + var err error + if !no_hist { + histFile, err = os.Open(hist) + if err == nil { + line.ReadHistory(histFile) + } else { + log.Log(log.ERR, + "couldnt read history: " + err.Error(), + "repl") + } + defer histFile.Close() } for { setLogLvl(vars) - text, err := rl.Readline() - if err != nil { + var prePrompt string + if dyn_prompt != nil { + p_tok := dyn_prompt.CallFunction(nil, vars, funcs) + if p_tok != nil && p_tok.Tag == ast.STRING { + prePrompt = p_tok.Value() + } + } + + fmt.Printf(prePrompt) + text, err := line.Prompt(prompt) + if err != nil && err != liner.ErrPromptAborted{ log.Log(log.ERR, "couldnt read user input: " + err.Error(), "repl") } + if !no_hist { + line.WriteHistory(histFile) + } + userInput := ast.Lex(text) if userInput == nil { // errors handled in Lex diff --git a/config/config.go b/config/config.go index 6ce7d51..bf4c303 100644 --- a/config/config.go +++ b/config/config.go @@ -18,11 +18,9 @@ package config import ( - "os" - "io" - "bufio" "gitlab.com/whom/shs/log" "gitlab.com/whom/shs/ast" + "gitlab.com/whom/shs/util" "gitlab.com/whom/shs/stdlib" ) @@ -35,32 +33,10 @@ func InitFromConfig(configFile string) (ast.VarTable, ast.FuncTable) { p := ast.GetVar("HOME", vars) configFile = p.Value() + "/" + configFile - cfile, err := os.Open(configFile) - if err != nil { - log.Log(log.DEBUG, - "unable to open config file: " + err.Error(), - "config") - return vars, funcs - } - - r := bufio.NewReader(cfile) - text, err := r.ReadString('\n') - for err != io.EOF { - if err != nil { - log.Log(log.ERR, - "unable to read from config file: " + err.Error(), - "config") - break - } - - // Eval lines in config - ast.Lex(text).Eval(funcs, vars, false) - text, err = r.ReadString('\n') - } + util.LoadScript(configFile, vars, funcs) log.Log(log.DEBUG, "config file fully evaluated", "config") - cfile.Close() return vars, funcs } diff --git a/go.mod b/go.mod index 125370b..2b85407 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module gitlab.com/whom/shs go 1.14 -require github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect +require ( + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/peterh/liner v1.2.0 // indirect +) diff --git a/go.sum b/go.sum index 454fe40..285b298 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/peterh/liner v1.2.0 h1:w/UPXyl5GfahFxcTOz2j9wCIHNI+pUPr2laqpojKNCg= +github.com/peterh/liner v1.2.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= diff --git a/stdlib/call.go b/stdlib/call.go index 4359503..4a1891e 100644 --- a/stdlib/call.go +++ b/stdlib/call.go @@ -254,8 +254,14 @@ func read_cmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { } } + output := out.String() + olen := len(output) + if olen > 0 && output[olen - 1] == '\n' { + output = output[:olen - 1] + } + ret := &ast.Token{Tag: ast.STRING} - ret.Set(out.String()) + ret.Set(output) return ret } diff --git a/stdlib/funcs.go b/stdlib/funcs.go index 93de50d..24805e0 100644 --- a/stdlib/funcs.go +++ b/stdlib/funcs.go @@ -60,30 +60,34 @@ func decl_func(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.To ASTSYNCSTATE := ast.SyncTablesWithOSEnviron inner := func(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { - temp := in.Eval(ft, vt, false) - if temp == nil { - log.Log(log.ERR, - "error parsing arguments", - name.Value()) - return nil - } - - ast.SyncTablesWithOSEnviron = false - key_iter := args.Expand() - val_iter := temp - - for key_iter != nil { - if val_iter == nil { + var temp *ast.Token + if numArgs != 0 || in != nil { + temp = in.Eval(ft, vt, false) + if temp == nil { log.Log(log.ERR, - "Not enough arguments supplied", + "error parsing arguments", name.Value()) + return nil } - ast.SetVar(key_iter.Value(), val_iter, vt) - key_iter = key_iter.Next - val_iter = val_iter.Next + ast.SyncTablesWithOSEnviron = false + key_iter := args.Expand() + val_iter := temp + + for key_iter != nil { + if val_iter == nil { + log.Log(log.ERR, + "Not enough arguments supplied", + name.Value()) + } + + ast.SetVar(key_iter.Value(), val_iter, vt) + key_iter = key_iter.Next + val_iter = val_iter.Next + } } + // maybe we actually should put the inner scope var into the env ast.SyncTablesWithOSEnviron = ASTSYNCSTATE ret := form.Eval(ft, vt, false) ast.SyncTablesWithOSEnviron = false diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 5ad08a5..8b36763 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -22,6 +22,7 @@ import ( "fmt" "gitlab.com/whom/shs/log" "gitlab.com/whom/shs/ast" + "gitlab.com/whom/shs/util" ) func GenFuncTable() ast.FuncTable { @@ -62,13 +63,20 @@ func GenFuncTable() ast.FuncTable { Args: 2, }, - "input": &ast.Function{ + "input": &ast.Function{ Function: input, Name: "input", TimesCalled: 0, Args: 1, }, + "load": &ast.Function{ + Function: load, + Name: "load", + TimesCalled: 0, + Args: 1, + }, + "...": &ast.Function{ Function: expand, Name: "...", @@ -337,3 +345,18 @@ func input(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { ret.Set(output) return ret } + +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) + return nil +} diff --git a/util/scripts.go b/util/scripts.go index 08cfd35..310fd3e 100644 --- a/util/scripts.go +++ b/util/scripts.go @@ -19,7 +19,7 @@ package util import ( "os" - "os/ioutil" + "io/ioutil" "gitlab.com/whom/shs/log" "gitlab.com/whom/shs/ast" ) @@ -33,16 +33,18 @@ func LoadScript(path string, vt ast.VarTable, ft ast.FuncTable) { return } - var body string - body, err := ioutil.ReadFile(path) + var body []byte + body, err = ioutil.ReadFile(path) scriptFile.Close() - if err !- nil { + if err != nil { log.Log(log.ERR, "unable to read script: " + err.Error(), "util") return } - set := ast.Lex(body) - set.Eval(ft, vt, false) + set := ast.Lex(string(body)) + for iter := set; iter != nil; iter = iter.Next { + iter.Eval(ft, vt, false) + } }