comments support, script loading support
This commit is contained in:
parent
654e8bd55b
commit
bd22b84699
11 changed files with 145 additions and 69 deletions
15
Readme.md
15
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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
18
ast/lex.go
18
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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
5
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
|
||||
)
|
||||
|
|
|
|||
4
go.sum
4
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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@ 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)
|
||||
var temp *ast.Token
|
||||
if numArgs != 0 || in != nil {
|
||||
temp = in.Eval(ft, vt, false)
|
||||
if temp == nil {
|
||||
log.Log(log.ERR,
|
||||
"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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -69,6 +70,13 @@ func GenFuncTable() ast.FuncTable {
|
|||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue