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)
|
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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
18
ast/lex.go
18
ast/lex.go
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
5
go.mod
|
|
@ -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
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 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=
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue