comments support, script loading support

This commit is contained in:
Aidan 2020-07-15 18:41:54 -07:00
parent 654e8bd55b
commit bd22b84699
No known key found for this signature in database
GPG key ID: 327711E983899316
11 changed files with 145 additions and 69 deletions

View file

@ -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) 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 ## How to build
### Compiling/Installation ### 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/...` 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 * one can write arbitrary shs script into `.shsrc` including function and variable declarations
* of note are the following variables * of note are the following variables
- `SH_LOGGING` Sets the log level (from 0 to 3) - `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_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: Here is an example of a shs configuration file:
```lisp ```lisp
(export GOPATH (concat HOME "/go")) (export GOPATH (concat HOME "/go"))
@ -80,6 +89,8 @@ Here is an example of a shs configuration file:
(export GIT_TERMINAL_PROMPT 1) (export GIT_TERMINAL_PROMPT 1)
(export SH_HIST_FILE (concat HOME "/.shs_hist")) (export SH_HIST_FILE (concat HOME "/.shs_hist"))
(export SH_LOGGING 0) (export SH_LOGGING 0)
(export SHS_STATIC_PROMPT ">")
(func _SH_PROMPT () (concat (?) ($ basename ($ pwd)) "\n"))
``` ```
## Contributing ## Contributing

View file

@ -61,6 +61,8 @@ func (in *Token) Eval(funcs FuncTable, vars VarTable, cnvtUndefVars bool) *Token
"eval") "eval")
return nil return nil
} }
} else {
res.Next = in.Next
} }
case LIST: case LIST:

View file

@ -121,6 +121,17 @@ func lex(input string) *Token {
return -2 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 needs_alloc := false
start_pos := 0 start_pos := 0
for i := 0; i < len(input); i++ { for i := 0; i < len(input); i++ {
@ -137,13 +148,18 @@ func lex(input string) *Token {
is_str = true is_str = true
needs_alloc = true needs_alloc = true
case ' ': case ' ', '\n', '\t', '\v', '\f', '\r':
if i == start_pos { if i == start_pos {
start_pos += 1 start_pos += 1
continue continue
} }
needs_alloc = true needs_alloc = true
// comment case
case ';':
start_pos = i + 1
i = matchLineEnd(start_pos)
} }
if needs_alloc { if needs_alloc {

View file

@ -18,9 +18,10 @@
package main package main
import ( import (
"os"
"fmt" "fmt"
"strconv" "strconv"
"github.com/chzyer/readline" "github.com/peterh/liner"
"gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/ast"
"gitlab.com/whom/shs/log" "gitlab.com/whom/shs/log"
"gitlab.com/whom/shs/config" "gitlab.com/whom/shs/config"
@ -54,6 +55,7 @@ func main() {
var prompt string var prompt string
var debug string var debug string
var hist string var hist string
no_hist := false
ast.SyncTablesWithOSEnviron = true ast.SyncTablesWithOSEnviron = true
ast.ExecWhenFuncUndef = true ast.ExecWhenFuncUndef = true
@ -67,34 +69,61 @@ func main() {
hist_t := ast.GetVar("SH_HIST_FILE", vars) hist_t := ast.GetVar("SH_HIST_FILE", vars)
if hist_t != nil { if hist_t != nil {
hist = hist_t.Value() 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 { if prompt_t != nil {
prompt = prompt_t.Value() prompt = prompt_t.Value()
} else { } else {
prompt = def_prompt prompt = def_prompt
} }
rl, err := readline.NewEx(&readline.Config{ line := liner.NewLiner()
Prompt: prompt, defer line.Close()
HistoryFile: hist,
InterruptPrompt: "^C",
})
defer rl.Close() line.SetCtrlCAborts(true)
if err != nil {
log.Log(log.ERR, "Couldnt initialize readline: " + err.Error(), "repl") var histFile *os.File
return 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 { for {
setLogLvl(vars) setLogLvl(vars)
text, err := rl.Readline() var prePrompt string
if err != nil { 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") log.Log(log.ERR, "couldnt read user input: " + err.Error(), "repl")
} }
if !no_hist {
line.WriteHistory(histFile)
}
userInput := ast.Lex(text) userInput := ast.Lex(text)
if userInput == nil { if userInput == nil {
// errors handled in Lex // errors handled in Lex

View file

@ -18,11 +18,9 @@
package config package config
import ( import (
"os"
"io"
"bufio"
"gitlab.com/whom/shs/log" "gitlab.com/whom/shs/log"
"gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/ast"
"gitlab.com/whom/shs/util"
"gitlab.com/whom/shs/stdlib" "gitlab.com/whom/shs/stdlib"
) )
@ -35,32 +33,10 @@ func InitFromConfig(configFile string) (ast.VarTable, ast.FuncTable) {
p := ast.GetVar("HOME", vars) p := ast.GetVar("HOME", vars)
configFile = p.Value() + "/" + configFile configFile = p.Value() + "/" + configFile
cfile, err := os.Open(configFile) util.LoadScript(configFile, vars, funcs)
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')
}
log.Log(log.DEBUG, log.Log(log.DEBUG,
"config file fully evaluated", "config file fully evaluated",
"config") "config")
cfile.Close()
return vars, funcs return vars, funcs
} }

5
go.mod
View file

@ -2,4 +2,7 @@ module gitlab.com/whom/shs
go 1.14 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
)

4
go.sum
View file

@ -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 h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 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=

View file

@ -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 := &ast.Token{Tag: ast.STRING}
ret.Set(out.String()) ret.Set(output)
return ret return ret
} }

View file

@ -60,7 +60,9 @@ func decl_func(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.To
ASTSYNCSTATE := ast.SyncTablesWithOSEnviron ASTSYNCSTATE := ast.SyncTablesWithOSEnviron
inner := func(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token { inner := func(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
temp := in.Eval(ft, vt, false) var temp *ast.Token
if numArgs != 0 || in != nil {
temp = in.Eval(ft, vt, false)
if temp == nil { if temp == nil {
log.Log(log.ERR, log.Log(log.ERR,
"error parsing arguments", "error parsing arguments",
@ -83,7 +85,9 @@ func decl_func(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.To
key_iter = key_iter.Next key_iter = key_iter.Next
val_iter = val_iter.Next val_iter = val_iter.Next
} }
}
// maybe we actually should put the inner scope var into the env
ast.SyncTablesWithOSEnviron = ASTSYNCSTATE ast.SyncTablesWithOSEnviron = ASTSYNCSTATE
ret := form.Eval(ft, vt, false) ret := form.Eval(ft, vt, false)
ast.SyncTablesWithOSEnviron = false ast.SyncTablesWithOSEnviron = false

View file

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"gitlab.com/whom/shs/log" "gitlab.com/whom/shs/log"
"gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/ast"
"gitlab.com/whom/shs/util"
) )
func GenFuncTable() ast.FuncTable { func GenFuncTable() ast.FuncTable {
@ -69,6 +70,13 @@ func GenFuncTable() ast.FuncTable {
Args: 1, Args: 1,
}, },
"load": &ast.Function{
Function: load,
Name: "load",
TimesCalled: 0,
Args: 1,
},
"...": &ast.Function{ "...": &ast.Function{
Function: expand, Function: expand,
Name: "...", Name: "...",
@ -337,3 +345,18 @@ func input(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
ret.Set(output) ret.Set(output)
return ret 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
}

View file

@ -19,7 +19,7 @@ package util
import ( import (
"os" "os"
"os/ioutil" "io/ioutil"
"gitlab.com/whom/shs/log" "gitlab.com/whom/shs/log"
"gitlab.com/whom/shs/ast" "gitlab.com/whom/shs/ast"
) )
@ -33,16 +33,18 @@ func LoadScript(path string, vt ast.VarTable, ft ast.FuncTable) {
return return
} }
var body string var body []byte
body, err := ioutil.ReadFile(path) body, err = ioutil.ReadFile(path)
scriptFile.Close() scriptFile.Close()
if err !- nil { if err != nil {
log.Log(log.ERR, log.Log(log.ERR,
"unable to read script: " + err.Error(), "unable to read script: " + err.Error(),
"util") "util")
return return
} }
set := ast.Lex(body) set := ast.Lex(string(body))
set.Eval(ft, vt, false) for iter := set; iter != nil; iter = iter.Next {
iter.Eval(ft, vt, false)
}
} }