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)
## 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

View file

@ -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:

View file

@ -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 {

View file

@ -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

View file

@ -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
}

5
go.mod
View file

@ -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
)

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/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.Set(out.String())
ret.Set(output)
return ret
}

View file

@ -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

View file

@ -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
}

View file

@ -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)
}
}