Merge branch 'dev' into 'master'
merge current dev to master See merge request whom/shs!4
This commit is contained in:
commit
5363896c85
22 changed files with 1570 additions and 380 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -14,3 +14,6 @@ print_ast
|
||||||
*~
|
*~
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
|
||||||
|
# notes
|
||||||
|
*.txt*
|
||||||
|
|
|
||||||
71
Readme.md
71
Readme.md
|
|
@ -4,15 +4,84 @@ Syntactically Homogeneous Shell
|
||||||
## Overview
|
## Overview
|
||||||
This shell was created to have extremely simple syntax. S-Expressions were chosen to represent statements and the scope of language features were constrained to what could be considered practical for daily shell use. This program is meant to be practical for administrators and daily power users.
|
This shell was created to have extremely simple syntax. S-Expressions were chosen to represent statements and the scope of language features were constrained to what could be considered practical for daily shell use. This program is meant to be practical for administrators and daily power users.
|
||||||
|
|
||||||
|
## Basic Syntax
|
||||||
|
When in doubt the `print_ast` utility can be used to examine the output of the Lex+Parse process. Here you can spot any bugs regarding syntax.
|
||||||
|
|
||||||
|
### Lists
|
||||||
|
Any sequence of items within a set of parenthesis is a list
|
||||||
|
`(1 "two" three 4)`
|
||||||
|
|
||||||
|
Lists can be infinitely nested
|
||||||
|
`("one" (2 3 4 (5)))`
|
||||||
|
|
||||||
|
### Data types
|
||||||
|
We use the following data types
|
||||||
|
* Number: 1, 2.0, etc
|
||||||
|
* String: "this is a string" (string delimiters: ' " \`)
|
||||||
|
* Bool: T or F
|
||||||
|
* Symbol: a string with no delimiters
|
||||||
|
* List: a sequence of elements within parenthesis
|
||||||
|
|
||||||
|
### Function calls
|
||||||
|
Any list beginning in a symbol will be considered a function call.
|
||||||
|
From within the `shs_repl` utility, unknown symbols will be assumed to be system binaries.
|
||||||
|
|
||||||
|
`(append () 1 2 3)`
|
||||||
|
`(vim Readme.md)`
|
||||||
|
`(if (eq "example" (fread 'test_file')) (print "test worked) (rm -rf /))`
|
||||||
|
|
||||||
|
### Variable declaration
|
||||||
|
There are a few ways to export variables
|
||||||
|
* export: `(export NAME (value))`
|
||||||
|
* let: `(let ((var1 val1) (var2 val2)) (form_to_be_evaluated))`
|
||||||
|
|
||||||
|
Currently, let has yet to be implemented
|
||||||
|
|
||||||
|
### Function declaration
|
||||||
|
Use the `func` function from the stdlib:
|
||||||
|
`(func name (var1, var2, var3) (form_to_be_evaluated))`
|
||||||
|
In this case, `(form_to_be_evaluated)` will not be evaluated until the function is called.
|
||||||
|
|
||||||
|
### Control flow
|
||||||
|
See `stdlib/control_flow.go`. We have if and while forms:
|
||||||
|
`(if (cond) (then) (else))`
|
||||||
|
`(when (cond) (form1)....... (formN))`
|
||||||
|
|
||||||
|
We also have functioning implementations of map and reduce in the stdlib (incomplete)
|
||||||
|
|
||||||
## 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
|
||||||
|
|
||||||
### Adding SHS to your application
|
### Adding SHS to your application
|
||||||
- TODO: write a how to here
|
* Make sure to set ast.SyncTablesWithOSEnviron, ast.ExecWhenFuncUndef. All of which control integrations with the underlying system.
|
||||||
|
- If you do not want the user to be able to set environment variables set ast.SyncTablesWithOSEnviron to false.
|
||||||
|
- If you do not want the user to be able to call binaries from the host system, set ast.ExecWhenFuncUndef to false.
|
||||||
|
- Get text you are interested in parsing
|
||||||
|
- Create a new VarTable and FuncTable (see ast/var_table.go and ast/func_table.go)
|
||||||
|
- Call `Lex(text)` on the `text` you want to evaluate to recieve a tree of parsed lexemes.
|
||||||
|
- Call `tree.Eval(FuncTable, VarTable, false)` where tree is the returned data from Lex, and the final boolean argument is whether or not to convert unknown symbols to strings. (this is a helpful option if you are writing functions such as those in stdlib/call.go, or any funciton in which you may want to be able to edit and transform the final ast based on your own varaiable table)
|
||||||
- Make sure the GPLv3 is adhered to
|
- Make sure the GPLv3 is adhered to
|
||||||
- *OVERRIDE THE STDLIB GenFuncTable FUNCTION.* You very likely do NOT want an available function to call system binaries in your embedded shell. Make sure the stdlib Call function is not included.
|
- *OVERRIDE THE STDLIB GenFuncTable FUNCTION.* You very likely do NOT want an available function to call system binaries in your embedded shell. Make sure the stdlib Call function is not included.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
* variables exported in the repl, if of types string or number, will result in a corresponding variable added to the Environment.
|
||||||
|
* 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
|
||||||
|
- `SH_HIST_FILE` Sets the history file
|
||||||
|
- `SH_DEBUG_MODE` Adds additional debug output for the lexer
|
||||||
|
Here is an example of a shs configuration file:
|
||||||
|
```lisp
|
||||||
|
(export "GOPATH" (concat HOME "/go"))
|
||||||
|
(export "GOBIN" (concat GOPATH "/bin"))
|
||||||
|
(export "PATH" (concat PATH ":" GOBIN))
|
||||||
|
(export "GIT_TERMINAL_PROMPT" 1)
|
||||||
|
(export "SH_HIST_FILE" (concat HOME "/.shs_hist"))
|
||||||
|
(export "SH_LOGGING" 0)
|
||||||
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
- Any contribution to this software is welcome as long as it adheres to the conduct guidelines specified in the `Contributing.md` file in this repository.
|
- Any contribution to this software is welcome as long as it adheres to the conduct guidelines specified in the `Contributing.md` file in this repository.
|
||||||
- Consider reading the [STDLIB Readme](https://git.callpipe.com/aidan/shs/-/blob/master/stdlib/Readme.md) for more information on how to extend this project.
|
- Consider reading the [STDLIB Readme](https://git.callpipe.com/aidan/shs/-/blob/master/stdlib/Readme.md) for more information on how to extend this project.
|
||||||
|
|
|
||||||
150
ast/eval.go
150
ast/eval.go
|
|
@ -19,86 +19,100 @@ package ast
|
||||||
|
|
||||||
import "gitlab.com/whom/shs/log"
|
import "gitlab.com/whom/shs/log"
|
||||||
|
|
||||||
var CallExecutablesFromUndefFuncCalls = false
|
/* determines whether or not to execute a system call
|
||||||
var CallExecutableToken = "l"
|
* when a function cannot be found in the functable
|
||||||
|
* (use case: shell)
|
||||||
|
* ExecFunc determines the name of the system call function to fetch
|
||||||
|
*/
|
||||||
|
var ExecWhenFuncUndef = false
|
||||||
|
var ExecFunc = "l"
|
||||||
|
|
||||||
func (t *Token) Eval(funcs FuncTable, vars VarTable) (*Token, bool) {
|
/* Runs through an AST of tokens
|
||||||
if t == nil {
|
* Evaluates the Tokens to determine simplest form
|
||||||
return nil, false
|
* Returns simplest form
|
||||||
|
*
|
||||||
|
* canFunc determines whether a symbol could be a function to call
|
||||||
|
* (true when first elem of a list)
|
||||||
|
*/
|
||||||
|
func (in *Token) Eval(funcs FuncTable, vars VarTable, cnvtUndefVars bool) *Token {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var reduce func(*Token) *Token
|
var res *Token
|
||||||
reduce = func(t_ *Token) *Token {
|
|
||||||
var unwrap bool
|
|
||||||
|
|
||||||
if t_.Next != nil {
|
switch in.Tag {
|
||||||
t_.Next = reduce(t_.Next)
|
case BOOL, NUMBER, STRING:
|
||||||
}
|
res = in.Copy()
|
||||||
|
|
||||||
switch (t_.Tag) {
|
case SYMBOL:
|
||||||
case SYMBOL:
|
res = GetVar(in.Value(), vars)
|
||||||
maybeToken := GetVar(t_.Inner.(string), vars)
|
if res == nil {
|
||||||
if maybeToken != nil {
|
res = in.Copy()
|
||||||
tok, _ := maybeToken.Eval(funcs, vars)
|
|
||||||
tok.Next = t_.Next
|
|
||||||
return tok
|
|
||||||
}
|
|
||||||
|
|
||||||
case LIST:
|
if GetFunction(in.Value(), funcs) == nil {
|
||||||
t_.Inner, unwrap = t_.Inner.(*Token).Eval(funcs, vars)
|
if cnvtUndefVars {
|
||||||
if unwrap {
|
res.Tag = STRING
|
||||||
next := t_.Next
|
break
|
||||||
t_ = t_.Inner.(*Token)
|
|
||||||
if t_ == nil {
|
|
||||||
log.Log(log.DEBUG, "nil Inner on list unwrap", "eval")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
i := &t_
|
log.Log(log.ERR,
|
||||||
for (*i).Next != nil {
|
"undefined symbol: "+in.Value(),
|
||||||
i = &((*i).Next)
|
|
||||||
}
|
|
||||||
|
|
||||||
(*i).Next = next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return t_
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := reduce(t)
|
|
||||||
if ret == nil {
|
|
||||||
log.Log(log.INFO, "reduce returned nil", "eval")
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
//if symbol in front of a list, could be a function call
|
|
||||||
if ret.Tag == SYMBOL {
|
|
||||||
f := GetFunction(ret.Inner.(string), funcs)
|
|
||||||
if f == nil {
|
|
||||||
if CallExecutablesFromUndefFuncCalls {
|
|
||||||
f = GetFunction(CallExecutableToken, funcs)
|
|
||||||
if f == nil {
|
|
||||||
log.Log(log.DEBUG, "Symbol " + ret.Inner.(string) +
|
|
||||||
" has no definition in function table. Additionally " +
|
|
||||||
"the configured LoadExecutableToken is also not defined",
|
|
||||||
"eval")
|
|
||||||
return ret, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// see the use of CallFunction below
|
|
||||||
ret = &Token{Next: ret}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
log.Log(log.DEBUG,
|
|
||||||
"could not find definition for symbol " + ret.Inner.(string),
|
|
||||||
"eval")
|
"eval")
|
||||||
return ret, false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (*f).CallFunction(ret.Next, vars, funcs), true
|
case LIST:
|
||||||
|
inner := in.Expand()
|
||||||
|
if inner == nil {
|
||||||
|
res = in.Copy()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if inner.Tag != SYMBOL {
|
||||||
|
in.Direct(inner.Eval(funcs, vars, cnvtUndefVars))
|
||||||
|
res = in.Copy()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
makeHead := false
|
||||||
|
funct := GetFunction(inner.Value(), funcs)
|
||||||
|
if funct == nil {
|
||||||
|
if ExecWhenFuncUndef {
|
||||||
|
funct = GetFunction(ExecFunc, funcs)
|
||||||
|
makeHead = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if funct != nil {
|
||||||
|
if makeHead {
|
||||||
|
inner = &Token{Next: inner}
|
||||||
|
}
|
||||||
|
res = funct.CallFunction(inner.Next, vars, funcs).Eval(funcs, vars, false)
|
||||||
|
if res == nil {
|
||||||
|
// function failed. logging is its responsibility
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Append(in.Next)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"undefined function "+inner.Value()+" called",
|
||||||
|
"eval")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"Eval hit unknown token type!",
|
||||||
|
"eval")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, false
|
if res.Next != nil {
|
||||||
|
res.Next = res.Next.Eval(funcs, vars, cnvtUndefVars)
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ func GetFunction(arg string, table FuncTable) *Function {
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Log(log.DEBUG,
|
log.Log(log.DEBUG,
|
||||||
"function " + arg + " not found",
|
"function " + arg + " not found",
|
||||||
"eval")
|
"ftable")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
203
ast/lex.go
Normal file
203
ast/lex.go
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
/* SHS: Syntactically Homogeneous Shell
|
||||||
|
* Copyright (C) 2019 Aidan Hahn
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/whom/shs/log"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const string_delims string = "\"'`"
|
||||||
|
|
||||||
|
func Lex(input string) *Token {
|
||||||
|
ret := lex(input)
|
||||||
|
if ret == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret.Tag != LIST {
|
||||||
|
temp := &Token{Tag: LIST}
|
||||||
|
temp.Direct(ret)
|
||||||
|
ret = temp
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func lex(input string) *Token {
|
||||||
|
if len(input) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret *Token
|
||||||
|
iter := &ret
|
||||||
|
is_str := false
|
||||||
|
is_list := false
|
||||||
|
|
||||||
|
tokenBuilder := func (pos int, tok string) {
|
||||||
|
if len(tok) == 0 && !is_list && !is_str {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*iter = new(Token)
|
||||||
|
(*iter).Position = pos
|
||||||
|
|
||||||
|
if is_list {
|
||||||
|
(*iter).inner = lex(tok)
|
||||||
|
(*iter).Tag = LIST
|
||||||
|
is_list = false
|
||||||
|
|
||||||
|
} else {
|
||||||
|
(*iter).inner = tok
|
||||||
|
if is_str {
|
||||||
|
(*iter).Tag = STRING
|
||||||
|
is_str = false
|
||||||
|
|
||||||
|
} else if StrIsNumber(tok) {
|
||||||
|
(*iter).Tag = NUMBER
|
||||||
|
|
||||||
|
} else if tok == "T" || tok == "F" {
|
||||||
|
(*iter).Tag = BOOL
|
||||||
|
|
||||||
|
} else {
|
||||||
|
(*iter).Tag = SYMBOL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iter = &(*iter).Next
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns -1 on unmatched string delim
|
||||||
|
matchStrEnd := func(start int, delim byte) int {
|
||||||
|
for i := start; i < len(input); i++ {
|
||||||
|
if input[i] == delim {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns -1 on unmatched string delim
|
||||||
|
// returns -2 on unmatched list delim
|
||||||
|
matchListEnd := func(start int) int {
|
||||||
|
depth := 0
|
||||||
|
|
||||||
|
for i := start; i < len(input); i++ {
|
||||||
|
switch input[i] {
|
||||||
|
case '"','\'','`':
|
||||||
|
i = matchStrEnd(i + 1, input[i])
|
||||||
|
if i == -1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
case '(':
|
||||||
|
depth++
|
||||||
|
|
||||||
|
case ')':
|
||||||
|
if depth == 0 {
|
||||||
|
return i
|
||||||
|
} else {
|
||||||
|
depth -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -2
|
||||||
|
}
|
||||||
|
|
||||||
|
needs_alloc := false
|
||||||
|
start_pos := 0
|
||||||
|
for i := 0; i < len(input); i++ {
|
||||||
|
switch input[i] {
|
||||||
|
case '(':
|
||||||
|
start_pos = i + 1
|
||||||
|
i = matchListEnd(start_pos)
|
||||||
|
is_list = true
|
||||||
|
needs_alloc = true
|
||||||
|
|
||||||
|
case '"','\'','`':
|
||||||
|
start_pos = i + 1
|
||||||
|
i = matchStrEnd(start_pos, input[i])
|
||||||
|
is_str = true
|
||||||
|
needs_alloc = true
|
||||||
|
|
||||||
|
case ' ':
|
||||||
|
if i == start_pos {
|
||||||
|
start_pos += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
needs_alloc = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if needs_alloc {
|
||||||
|
needs_alloc = false
|
||||||
|
if (i < 0) {
|
||||||
|
// TODO: Maybe not overload this.
|
||||||
|
start_pos = i
|
||||||
|
goto error
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenBuilder(start_pos, input[start_pos:i])
|
||||||
|
start_pos = i+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if start_pos < len(input) {
|
||||||
|
tokenBuilder(start_pos, input[start_pos:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
error:
|
||||||
|
// TODO: Hook into error module
|
||||||
|
// TODO: Finalize and GC alloced tokens
|
||||||
|
if start_pos == -1 {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"Unmatched string delimiter in input. discarding.",
|
||||||
|
"lex")
|
||||||
|
} else if start_pos == -2 {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"Unmatched list delimiter in input. discarding.",
|
||||||
|
"lex")
|
||||||
|
} else {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"Unknown error in input. discarding.",
|
||||||
|
"lex")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StrIsNumber(arg string) bool {
|
||||||
|
dotCount := 0
|
||||||
|
|
||||||
|
for _, char := range arg {
|
||||||
|
if !unicode.IsDigit(char) {
|
||||||
|
if char == '.' && dotCount == 0 {
|
||||||
|
dotCount++
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
64
ast/print.go
64
ast/print.go
|
|
@ -19,39 +19,8 @@ package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Print function which is better suited for repl.
|
|
||||||
* This one prints the SEXPRs as one would write them.
|
|
||||||
*/
|
|
||||||
func (t *Token) String() string {
|
|
||||||
switch t.Tag {
|
|
||||||
case STRING:
|
|
||||||
return "\"" + t.Inner.(string) + "\""
|
|
||||||
|
|
||||||
case NUMBER:
|
|
||||||
return t.Inner.(string)
|
|
||||||
|
|
||||||
case LIST:
|
|
||||||
repr := "("
|
|
||||||
if t.Inner.(*Token) == nil {
|
|
||||||
return repr + ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := t.Inner.(*Token); i != nil; i = i.Next {
|
|
||||||
repr = repr + i.String() + " "
|
|
||||||
}
|
|
||||||
// remove trailing space
|
|
||||||
return repr[:len(repr)-1] + ")"
|
|
||||||
|
|
||||||
case SYMBOL:
|
|
||||||
return "<" + t.Inner.(string) + ">"
|
|
||||||
}
|
|
||||||
|
|
||||||
return "[UNKNOWN CELL TYPE]"
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print function which breaks each embedded list out on individual lines.
|
/* Print function which breaks each embedded list out on individual lines.
|
||||||
* Used in the print_ast debug tool. not too useful for repl applications.
|
* Used in the print_ast debug tool. not too useful for repl applications.
|
||||||
* Very useful for debugging syntax though.
|
* Very useful for debugging syntax though.
|
||||||
|
|
@ -74,42 +43,13 @@ loop:
|
||||||
|
|
||||||
for iter := i; iter != nil; iter = iter.Next {
|
for iter := i; iter != nil; iter = iter.Next {
|
||||||
if iter.Tag == LIST {
|
if iter.Tag == LIST {
|
||||||
lists.Push(iter.Inner.(*Token))
|
lists.Push(iter.Expand())
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor.WriteString(FmtToken(iter))
|
constructor.WriteString(iter.FmtToken())
|
||||||
}
|
}
|
||||||
|
|
||||||
println(constructor.String())
|
println(constructor.String())
|
||||||
goto loop
|
goto loop
|
||||||
}
|
}
|
||||||
|
|
||||||
func FmtToken(arg *Token) string {
|
|
||||||
suffix := "->"
|
|
||||||
if arg.Next == nil {
|
|
||||||
suffix = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
switch arg.Tag {
|
|
||||||
case LIST:
|
|
||||||
return fmt.Sprintf("(%s, [List])%s", "LIST", suffix)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("(%s, %s)%s", GetTagAsStr(arg.Tag), arg.Inner, suffix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTagAsStr(tag Token_t) string {
|
|
||||||
switch tag {
|
|
||||||
case LIST:
|
|
||||||
return "LIST"
|
|
||||||
case STRING:
|
|
||||||
return "STRING"
|
|
||||||
case NUMBER:
|
|
||||||
return "NUMBER"
|
|
||||||
case SYMBOL:
|
|
||||||
return "SYMBOL"
|
|
||||||
}
|
|
||||||
return "UNKNOWN"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
284
ast/token.go
284
ast/token.go
|
|
@ -17,10 +17,7 @@
|
||||||
|
|
||||||
package ast
|
package ast
|
||||||
|
|
||||||
import (
|
import "fmt"
|
||||||
"gitlab.com/whom/shs/log"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Token_t int
|
type Token_t int
|
||||||
const (
|
const (
|
||||||
|
|
@ -28,172 +25,151 @@ const (
|
||||||
STRING Token_t = iota
|
STRING Token_t = iota
|
||||||
NUMBER Token_t = iota
|
NUMBER Token_t = iota
|
||||||
SYMBOL Token_t = iota
|
SYMBOL Token_t = iota
|
||||||
|
BOOL Token_t = iota
|
||||||
|
|
||||||
|
TRUE string = "T"
|
||||||
|
FALSE string = "F"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Next *Token
|
Next *Token
|
||||||
Tag Token_t
|
Tag Token_t
|
||||||
Position int
|
Position int
|
||||||
Inner interface{}
|
inner interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
const string_delims string = "\"'`"
|
/* Appends another token to the end of this token list
|
||||||
|
*/
|
||||||
|
func (t *Token) Append(arg *Token) {
|
||||||
|
if t.Next != nil {
|
||||||
|
t.Next.Append(arg)
|
||||||
|
} else {
|
||||||
|
t.Next = arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Lex(input string) *Token {
|
/* Shallow Copy
|
||||||
if len(input) == 0 {
|
* in case of a LIST,
|
||||||
|
* inner will point to the same list.
|
||||||
|
*/
|
||||||
|
func (t *Token) Copy() *Token {
|
||||||
|
return &Token{
|
||||||
|
Tag: t.Tag,
|
||||||
|
inner: t.inner,
|
||||||
|
Next: t.Next,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print function which is better suited for repl.
|
||||||
|
* This one prints the SEXPRs as one would write them.
|
||||||
|
* Does not evaluate tokens.
|
||||||
|
*/
|
||||||
|
func (t *Token) String() string {
|
||||||
|
switch t.Tag {
|
||||||
|
case STRING:
|
||||||
|
return "\"" + t.inner.(string) + "\""
|
||||||
|
|
||||||
|
case NUMBER, BOOL:
|
||||||
|
return t.inner.(string)
|
||||||
|
|
||||||
|
case LIST:
|
||||||
|
repr := "("
|
||||||
|
if t.inner.(*Token) == nil {
|
||||||
|
return repr + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := t.inner.(*Token); i != nil; i = i.Next {
|
||||||
|
repr = repr + i.String() + " "
|
||||||
|
}
|
||||||
|
// remove trailing space
|
||||||
|
return repr[:len(repr)-1] + ")"
|
||||||
|
|
||||||
|
case SYMBOL:
|
||||||
|
return "<" + t.inner.(string) + ">"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "[UNKNOWN CELL TYPE]"
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns a list held by a token
|
||||||
|
* returns nil if token holds no list
|
||||||
|
*/
|
||||||
|
func (t *Token) Expand() *Token {
|
||||||
|
if t.Tag != LIST {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret *Token
|
return t.inner.(*Token)
|
||||||
iter := &ret
|
|
||||||
is_str := false
|
|
||||||
is_list := false
|
|
||||||
|
|
||||||
tokenBuilder := func (pos int, tok string) {
|
|
||||||
if len(tok) == 0 && !is_list && !is_str {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
*iter = new(Token)
|
|
||||||
(*iter).Position = pos
|
|
||||||
|
|
||||||
if is_list {
|
|
||||||
(*iter).Inner = Lex(tok)
|
|
||||||
(*iter).Tag = LIST
|
|
||||||
is_list = false
|
|
||||||
|
|
||||||
} else {
|
|
||||||
(*iter).Inner = tok
|
|
||||||
if is_str {
|
|
||||||
(*iter).Tag = STRING
|
|
||||||
is_str = false
|
|
||||||
|
|
||||||
} else if StrIsNumber(tok) {
|
|
||||||
(*iter).Tag = NUMBER
|
|
||||||
|
|
||||||
} else {
|
|
||||||
(*iter).Tag = SYMBOL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iter = &(*iter).Next
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns -1 on unmatched string delim
|
|
||||||
matchStrEnd := func(start int, delim byte) int {
|
|
||||||
for i := start; i < len(input); i++ {
|
|
||||||
if input[i] == delim {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns -1 on unmatched string delim
|
|
||||||
// returns -2 on unmatched list delim
|
|
||||||
matchListEnd := func(start int) int {
|
|
||||||
depth := 0
|
|
||||||
|
|
||||||
for i := start; i < len(input); i++ {
|
|
||||||
switch input[i] {
|
|
||||||
case '"','\'','`':
|
|
||||||
i = matchStrEnd(i + 1, input[i])
|
|
||||||
if i == -1 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
case '(':
|
|
||||||
depth++
|
|
||||||
|
|
||||||
case ')':
|
|
||||||
if depth == 0 {
|
|
||||||
return i
|
|
||||||
} else {
|
|
||||||
depth -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -2
|
|
||||||
}
|
|
||||||
|
|
||||||
needs_alloc := false
|
|
||||||
start_pos := 0
|
|
||||||
for i := 0; i < len(input); i++ {
|
|
||||||
switch input[i] {
|
|
||||||
case '(':
|
|
||||||
start_pos = i + 1
|
|
||||||
i = matchListEnd(start_pos)
|
|
||||||
is_list = true
|
|
||||||
needs_alloc = true
|
|
||||||
|
|
||||||
case '"','\'','`':
|
|
||||||
start_pos = i + 1
|
|
||||||
i = matchStrEnd(start_pos, input[i])
|
|
||||||
is_str = true
|
|
||||||
needs_alloc = true
|
|
||||||
|
|
||||||
case ' ':
|
|
||||||
if i == start_pos {
|
|
||||||
start_pos += 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
needs_alloc = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if needs_alloc {
|
|
||||||
needs_alloc = false
|
|
||||||
if (i < 0) {
|
|
||||||
// TODO: Maybe not overload this.
|
|
||||||
start_pos = i
|
|
||||||
goto error
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenBuilder(start_pos, input[start_pos:i])
|
|
||||||
start_pos = i+1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if start_pos < len(input) {
|
|
||||||
tokenBuilder(start_pos, input[start_pos:])
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
error:
|
|
||||||
// TODO: Hook into error module
|
|
||||||
// TODO: Finalize and GC alloced tokens
|
|
||||||
if start_pos == -1 {
|
|
||||||
log.Log(log.ERR,
|
|
||||||
"Unmatched string delimiter in input. discarding.",
|
|
||||||
"lex")
|
|
||||||
} else if start_pos == -2 {
|
|
||||||
log.Log(log.ERR,
|
|
||||||
"Unmatched list delimiter in input. discarding.",
|
|
||||||
"lex")
|
|
||||||
} else {
|
|
||||||
log.Log(log.ERR,
|
|
||||||
"Unknown error in input. discarding.",
|
|
||||||
"lex")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func StrIsNumber(arg string) bool {
|
/* Sets inner to a Token value
|
||||||
dotCount := 0
|
* returns false if parent token is not a list
|
||||||
|
* otherwise returns true
|
||||||
for _, char := range arg {
|
*/
|
||||||
if !unicode.IsDigit(char) {
|
func (t *Token) Direct(head *Token) bool {
|
||||||
if char == '.' && dotCount == 0 {
|
if t.Tag != LIST {
|
||||||
dotCount++
|
return false
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.inner = head
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If token holds an atomic value
|
||||||
|
* (not a symbol or list)
|
||||||
|
* will return its value as a string
|
||||||
|
* else returns ""
|
||||||
|
*/
|
||||||
|
func (t *Token) Value() string {
|
||||||
|
if t.Tag == LIST {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.inner.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* returns an ascii representation of a token
|
||||||
|
*/
|
||||||
|
func (t *Token) FmtToken() string {
|
||||||
|
suffix := "->"
|
||||||
|
if t.Next == nil {
|
||||||
|
suffix = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t.Tag {
|
||||||
|
case LIST:
|
||||||
|
return fmt.Sprintf("(%s, [List])%s", "LIST", suffix)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("(%s, %s)%s", GetTagAsStr(t.Tag), t.inner.(string), suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sets the string value for a non-list token
|
||||||
|
*/
|
||||||
|
func (t *Token) Set(arg string) bool {
|
||||||
|
if t.Tag == LIST {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
t.inner = arg
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns a tag in text
|
||||||
|
*/
|
||||||
|
func GetTagAsStr(tag Token_t) string {
|
||||||
|
switch tag {
|
||||||
|
case LIST:
|
||||||
|
return "LIST"
|
||||||
|
case STRING:
|
||||||
|
return "STRING"
|
||||||
|
case BOOL:
|
||||||
|
return "BOOL"
|
||||||
|
case NUMBER:
|
||||||
|
return "NUMBER"
|
||||||
|
case SYMBOL:
|
||||||
|
return "SYMBOL"
|
||||||
|
}
|
||||||
|
return "UNKNOWN"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ func GetVar(arg string, vt VarTable) *Token {
|
||||||
|
|
||||||
e := os.Getenv(arg)
|
e := os.Getenv(arg)
|
||||||
if e != "" {
|
if e != "" {
|
||||||
t := &Token{Inner: e}
|
t := &Token{inner: e}
|
||||||
if StrIsNumber(e) {
|
if StrIsNumber(e) {
|
||||||
t.Tag = NUMBER
|
t.Tag = NUMBER
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -56,11 +56,12 @@ func GetVar(arg string, vt VarTable) *Token {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this could be much more optimal
|
// TODO: this could be much more optimal
|
||||||
|
// probably a stdlib thing
|
||||||
func SetVar(variable string, value *Token, vt VarTable) {
|
func SetVar(variable string, value *Token, vt VarTable) {
|
||||||
(*vt)[variable] = value
|
(*vt)[variable] = value
|
||||||
if SyncTablesWithOSEnviron &&
|
if SyncTablesWithOSEnviron &&
|
||||||
(value.Tag == NUMBER || value.Tag == STRING) {
|
(value.Tag == NUMBER || value.Tag == STRING) {
|
||||||
token := value.Inner.(string)
|
token := value.Value()
|
||||||
if value.Tag == NUMBER {
|
if value.Tag == NUMBER {
|
||||||
// make sure its an int
|
// make sure its an int
|
||||||
a, err := strconv.ParseFloat(token, 64)
|
a, err := strconv.ParseFloat(token, 64)
|
||||||
|
|
@ -89,15 +90,22 @@ func GetVarFromTables(arg string, library []VarTable) *Token {
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitVarTable(table VarTable) {
|
func InitVarTable(table VarTable) {
|
||||||
|
if !SyncTablesWithOSEnviron {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, val := range os.Environ() {
|
for _, val := range os.Environ() {
|
||||||
variable := strings.Split(val, "=")
|
variable := strings.Split(val, "=")
|
||||||
t := &Token{Inner: variable[1]}
|
t := &Token{inner: variable[1]}
|
||||||
if StrIsNumber(variable[1]) {
|
if StrIsNumber(variable[1]) {
|
||||||
t.Tag = NUMBER
|
t.Tag = NUMBER
|
||||||
} else {
|
} else {
|
||||||
t.Tag = STRING
|
t.Tag = STRING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if variable[0] == "HOME" {
|
||||||
|
SetVar("~", t, table)
|
||||||
|
}
|
||||||
SetVar(variable[0], t, table)
|
SetVar(variable[0], t, table)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,3 +121,15 @@ func DeleteVarTable(table VarTable) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RemoveVar(arg string, table VarTable) {
|
||||||
|
if SyncTablesWithOSEnviron {
|
||||||
|
err := os.Unsetenv(arg)
|
||||||
|
if err != nil {
|
||||||
|
log.Log(log.DEBUG,
|
||||||
|
"Failed to remove "+arg+" from env: "+err.Error(),
|
||||||
|
"vartable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(*table, arg)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"os"
|
"os"
|
||||||
"gitlab.com/whom/shs/log"
|
|
||||||
"gitlab.com/whom/shs/ast"
|
"gitlab.com/whom/shs/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.PrintSExprsIndividually(ast.Lex(strings.Join(os.Args[1:], " ")))
|
ast.PrintSExprsIndividually(ast.Lex(strings.Join(os.Args[1:], " ")))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,26 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"github.com/chzyer/readline"
|
"github.com/chzyer/readline"
|
||||||
"gitlab.com/whom/shs/ast"
|
"gitlab.com/whom/shs/ast"
|
||||||
"gitlab.com/whom/shs/log"
|
"gitlab.com/whom/shs/log"
|
||||||
"gitlab.com/whom/shs/stdlib"
|
"gitlab.com/whom/shs/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
def_prompt string = "λ "
|
def_prompt string = "λ "
|
||||||
)
|
)
|
||||||
|
|
||||||
func setLogLvl() {
|
func setLogLvl(vars ast.VarTable) {
|
||||||
loglvl := os.Getenv("SH_LOGGING")
|
var loglvl string
|
||||||
|
|
||||||
|
loglvl_t := ast.GetVar("SH_LOGGING", vars)
|
||||||
|
if loglvl_t != nil {
|
||||||
|
loglvl = loglvl_t.Value()
|
||||||
|
}
|
||||||
|
|
||||||
if loglvl != "" {
|
if loglvl != "" {
|
||||||
llvl, err := strconv.ParseInt(loglvl, 10, 8)
|
llvl, err := strconv.ParseInt(loglvl, 10, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -46,23 +51,28 @@ func setLogLvl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ast.CallExecutablesFromUndefFuncCalls = true
|
var prompt string
|
||||||
|
var debug string
|
||||||
|
var hist string
|
||||||
|
|
||||||
debug := os.Getenv("SH_DEBUG_MODE")
|
|
||||||
hist := os.Getenv("SH_HIST_FILE")
|
|
||||||
prompt := os.Getenv("SHS_SH_PROMPT")
|
|
||||||
|
|
||||||
var vars ast.VarTable
|
|
||||||
var funcs ast.FuncTable
|
|
||||||
|
|
||||||
funcs = stdlib.GenFuncTable()
|
|
||||||
vars = &map[string]*ast.Token{}
|
|
||||||
ast.InitVarTable(vars)
|
|
||||||
ast.SyncTablesWithOSEnviron = true
|
ast.SyncTablesWithOSEnviron = true
|
||||||
|
ast.ExecWhenFuncUndef = true
|
||||||
|
|
||||||
var err error
|
vars, funcs := config.InitFromConfig(".shsrc")
|
||||||
|
debug_t := ast.GetVar("SH_DEBUG_MODE", vars)
|
||||||
|
if debug_t != nil {
|
||||||
|
debug = debug_t.Value()
|
||||||
|
}
|
||||||
|
|
||||||
if prompt == "" {
|
hist_t := ast.GetVar("SH_HIST_FILE", vars)
|
||||||
|
if hist_t != nil {
|
||||||
|
hist = hist_t.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_t := ast.GetVar("SHS_SH_PROMPT", vars)
|
||||||
|
if prompt_t != nil {
|
||||||
|
prompt = prompt_t.Value()
|
||||||
|
} else {
|
||||||
prompt = def_prompt
|
prompt = def_prompt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +89,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
setLogLvl()
|
setLogLvl(vars)
|
||||||
text, err := rl.Readline()
|
text, err := rl.Readline()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log(log.ERR, "couldnt read user input: " + err.Error(), "repl")
|
log.Log(log.ERR, "couldnt read user input: " + err.Error(), "repl")
|
||||||
|
|
@ -96,12 +106,8 @@ func main() {
|
||||||
ast.PrintSExprsIndividually(userInput)
|
ast.PrintSExprsIndividually(userInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, unwrap := userInput.Eval(funcs, vars)
|
result := userInput.Eval(funcs, vars, false)
|
||||||
if result != nil {
|
if result != nil {
|
||||||
if result.Tag == ast.LIST && unwrap {
|
|
||||||
result = result.Inner.(*ast.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := result; i != nil; i = i.Next {
|
for i := result; i != nil; i = i.Next {
|
||||||
fmt.Printf(i.String() + " ")
|
fmt.Printf(i.String() + " ")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
66
config/config.go
Normal file
66
config/config.go
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
/* SHS: Syntactically Homogeneous Shell
|
||||||
|
* Copyright (C) 2019 Aidan Hahn
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"io"
|
||||||
|
"bufio"
|
||||||
|
"gitlab.com/whom/shs/log"
|
||||||
|
"gitlab.com/whom/shs/ast"
|
||||||
|
"gitlab.com/whom/shs/stdlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitFromConfig(configFile string) (ast.VarTable, ast.FuncTable) {
|
||||||
|
funcs := stdlib.GenFuncTable()
|
||||||
|
vars := &map[string]*ast.Token{}
|
||||||
|
|
||||||
|
ast.InitVarTable(vars)
|
||||||
|
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Log(log.DEBUG,
|
||||||
|
"config file fully evaluated",
|
||||||
|
"config")
|
||||||
|
cfile.Close()
|
||||||
|
return vars, funcs
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ The standard library is loaded during the init step of the repl (or interpreter
|
||||||
[Tokens](https://git.callpipe.com/aidan/shs/-/blob/master/ast/token.go) are a rudimentary linked list of parsed [Lexemes](https://en.wikipedia.org/wiki/Lexeme). In the ast package there are definitions for Tokens, as well as code for the combined Lex/Parse loop that creates them. Tokens are built in a way that makes operating over them with either recursive or iterative alrogithms easy. When consuming Tokens, one can expect their type by looking at the Tag field. The data stored in the Inner field will be either a string or a \*Token depending on what Tag is. You can expect a \*Token if the Tag field is ast.LIST, and a string in all other cases. If the Tag field is ast.SYMBOL you can look it up in the VarTable or the FuncTable. The VarTable will return either a \*Token (if the symbol is a Variable) or *nil* if nothing is found. The FuncTable will return either a \*Function (if there is a match) or it will return *nil*.
|
[Tokens](https://git.callpipe.com/aidan/shs/-/blob/master/ast/token.go) are a rudimentary linked list of parsed [Lexemes](https://en.wikipedia.org/wiki/Lexeme). In the ast package there are definitions for Tokens, as well as code for the combined Lex/Parse loop that creates them. Tokens are built in a way that makes operating over them with either recursive or iterative alrogithms easy. When consuming Tokens, one can expect their type by looking at the Tag field. The data stored in the Inner field will be either a string or a \*Token depending on what Tag is. You can expect a \*Token if the Tag field is ast.LIST, and a string in all other cases. If the Tag field is ast.SYMBOL you can look it up in the VarTable or the FuncTable. The VarTable will return either a \*Token (if the symbol is a Variable) or *nil* if nothing is found. The FuncTable will return either a \*Function (if there is a match) or it will return *nil*.
|
||||||
P.S.: Ideally a token should not be re-used. You may consider them disposable. It is up to you to make sure that any Token you edit/reuse remains consistant with the type declared in its TAG. Make sure to differentiate between NUMBER and STRING with the `ast.StrIsNumber(arg string) bool` function.
|
P.S.: Ideally a token should not be re-used. You may consider them disposable. It is up to you to make sure that any Token you edit/reuse remains consistant with the type declared in its TAG. Make sure to differentiate between NUMBER and STRING with the `ast.StrIsNumber(arg string) bool` function.
|
||||||
## Adding a function
|
## Adding a function
|
||||||
|
There are two ways to define functions: Either by writing it in shs code (using the 'func' function) or by extending the standard library. The steps below assume you are extending the standard library.
|
||||||
1. *Write your function in the form of an `ast.Operation`.* Any function that has the defined signature can be an Operation.
|
1. *Write your function in the form of an `ast.Operation`.* Any function that has the defined signature can be an Operation.
|
||||||
2. *Create a `Function` to encapsulate your `Operation`.* Make sure to set the `args` and `name` fields. Args will be used to validate function calls and Name will be used in debug/log output.
|
2. *Create a `Function` to encapsulate your `Operation`.* Make sure to set the `args` and `name` fields. Args will be used to validate function calls and Name will be used in debug/log output.
|
||||||
3. *Add your `Function` to the `FuncTable`.* Make sure your `Operations`s get added to the table generated in `GenFuncTable`.
|
3. *Add your `Function` to the `FuncTable`.* Make sure your `Operations`s get added to the table generated in `GenFuncTable`.
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,15 @@ import (
|
||||||
func add(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
func add(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
||||||
var res float64
|
var res float64
|
||||||
|
|
||||||
|
in = in.Eval(f, a, false)
|
||||||
|
|
||||||
for i := in; i != nil; i = i.Next {
|
for i := in; i != nil; i = i.Next {
|
||||||
if i.Tag != ast.NUMBER {
|
if i.Tag != ast.NUMBER {
|
||||||
log.Log(log.ERR, "Non-number given to ADD", "add")
|
log.Log(log.ERR, "Non-number given to ADD", "add")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
token := i.Inner.(string)
|
token := i.Value()
|
||||||
isFloat := false
|
isFloat := false
|
||||||
for _, char := range token {
|
for _, char := range token {
|
||||||
if char == '.' {
|
if char == '.' {
|
||||||
|
|
@ -64,20 +66,24 @@ func add(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.NUMBER, Inner: fmt.Sprintf("%f", res)}
|
t := &ast.Token{Tag: ast.NUMBER}
|
||||||
|
t.Set(fmt.Sprintf("%f", res))
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func sub(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
func sub(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
||||||
var res float64
|
var res float64
|
||||||
var sub float64
|
var sub float64
|
||||||
|
|
||||||
|
in = in.Eval(f, a, false)
|
||||||
|
|
||||||
for i := in; i != nil; i = i.Next {
|
for i := in; i != nil; i = i.Next {
|
||||||
if i.Tag != ast.NUMBER {
|
if i.Tag != ast.NUMBER {
|
||||||
log.Log(log.ERR, "Non-number given to SUB", "sub")
|
log.Log(log.ERR, "Non-number given to SUB", "sub")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
token := i.Inner.(string)
|
token := i.Value()
|
||||||
isFloat := false
|
isFloat := false
|
||||||
var inner float64
|
var inner float64
|
||||||
for _, char := range token {
|
for _, char := range token {
|
||||||
|
|
@ -110,19 +116,23 @@ func sub(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.NUMBER, Inner: fmt.Sprintf("%f", res - sub)}
|
t := &ast.Token{Tag: ast.NUMBER}
|
||||||
|
t.Set(fmt.Sprintf("%f", res - sub))
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func mult(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
func mult(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
||||||
res := 1.0
|
res := 1.0
|
||||||
|
|
||||||
|
in = in.Eval(f, a, false)
|
||||||
|
|
||||||
for i := in; i != nil; i = i.Next {
|
for i := in; i != nil; i = i.Next {
|
||||||
if i.Tag != ast.NUMBER {
|
if i.Tag != ast.NUMBER {
|
||||||
log.Log(log.ERR, "Non-number given to MULT", "mult")
|
log.Log(log.ERR, "Non-number given to MULT", "mult")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
token := i.Inner.(string)
|
token := i.Value()
|
||||||
isFloat := false
|
isFloat := false
|
||||||
for _, char := range token {
|
for _, char := range token {
|
||||||
if char == '.' {
|
if char == '.' {
|
||||||
|
|
@ -148,12 +158,16 @@ func mult(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.NUMBER, Inner: fmt.Sprintf("%f", res)}
|
t := &ast.Token{Tag: ast.NUMBER}
|
||||||
|
t.Set(fmt.Sprintf("%f", res))
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func div(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
func div(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
||||||
var res float64
|
var res float64
|
||||||
|
|
||||||
|
in = in.Eval(f, a, false)
|
||||||
|
|
||||||
for i := in; i != nil; i = i.Next {
|
for i := in; i != nil; i = i.Next {
|
||||||
inner := 0.0
|
inner := 0.0
|
||||||
|
|
||||||
|
|
@ -162,7 +176,7 @@ func div(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
token := i.Inner.(string)
|
token := i.Value()
|
||||||
isFloat := false
|
isFloat := false
|
||||||
for _, char := range token {
|
for _, char := range token {
|
||||||
if char == '.' {
|
if char == '.' {
|
||||||
|
|
@ -194,5 +208,7 @@ func div(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.NUMBER, Inner: fmt.Sprintf("%f", res)}
|
t := &ast.Token{Tag: ast.NUMBER}
|
||||||
|
t.Set(fmt.Sprintf("%f", res))
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
|
||||||
182
stdlib/bool.go
Normal file
182
stdlib/bool.go
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
/* SHS: Syntactically Homogeneous Shell
|
||||||
|
* Copyright (C) 2019 Aidan Hahn
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package stdlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"gitlab.com/whom/shs/log"
|
||||||
|
"gitlab.com/whom/shs/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
func not(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
in = in.Eval(ft, vt, false)
|
||||||
|
|
||||||
|
if in.Tag != ast.BOOL {
|
||||||
|
log.Log(log.ERR, "non-bool argument to 'not'", "not")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
out := ast.TRUE
|
||||||
|
if in.Value() == ast.TRUE {
|
||||||
|
out = ast.FALSE
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &ast.Token{Tag: ast.BOOL}
|
||||||
|
t.Set(out)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func eq(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
out := ast.TRUE
|
||||||
|
|
||||||
|
in = in.Eval(ft, vt, false)
|
||||||
|
second := in.Next
|
||||||
|
|
||||||
|
if in.Tag != second.Tag {
|
||||||
|
out = ast.FALSE
|
||||||
|
} else {
|
||||||
|
switch in.Tag {
|
||||||
|
case ast.LIST:
|
||||||
|
// returns true if difference found
|
||||||
|
var consume_list func(*ast.Token, *ast.Token) bool
|
||||||
|
consume_list = func(l *ast.Token, r *ast.Token) bool {
|
||||||
|
if (l == nil && r != nil) || (r == nil && l != nil) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.Tag != r.Tag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
l_iter := l
|
||||||
|
r_iter := r
|
||||||
|
for l_iter != nil {
|
||||||
|
if r_iter == nil || l_iter.Tag != r_iter.Tag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if l_iter.Tag == ast.LIST {
|
||||||
|
diff := consume_list(l_iter.Expand(), r_iter.Expand())
|
||||||
|
if diff {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if l_iter.Value() != r_iter.Value() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l_iter = l_iter.Next
|
||||||
|
r_iter = r_iter.Next
|
||||||
|
}
|
||||||
|
|
||||||
|
if r_iter != nil {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if consume_list(in.Expand(), second.Expand()) {
|
||||||
|
out = ast.FALSE
|
||||||
|
}
|
||||||
|
|
||||||
|
case ast.STRING, ast.BOOL:
|
||||||
|
if in.Value() != second.Value() {
|
||||||
|
out = ast.FALSE
|
||||||
|
}
|
||||||
|
|
||||||
|
case ast.NUMBER:
|
||||||
|
l_val, parse_err := strconv.ParseFloat(in.Value(), 64)
|
||||||
|
r_val, parse_err := strconv.ParseFloat(second.Value(), 64)
|
||||||
|
if parse_err != nil {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"error parsing number: "+parse_err.Error(),
|
||||||
|
"eq")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if l_val != r_val {
|
||||||
|
out = ast.FALSE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &ast.Token{Tag: ast.BOOL}
|
||||||
|
t.Set(out)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func lt(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
out := ast.TRUE
|
||||||
|
second := in.Next
|
||||||
|
|
||||||
|
in = in.Eval(ft, vt, false)
|
||||||
|
|
||||||
|
if in.Tag != ast.NUMBER || second.Tag != ast.NUMBER {
|
||||||
|
log.Log(log.ERR, "non-number argument to numeric boolean operator", ">/<=")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l, _ := strconv.ParseInt(in.Value(), 10, 64)
|
||||||
|
r, _ := strconv.ParseInt(second.Value(), 10, 64)
|
||||||
|
|
||||||
|
if l >= r {
|
||||||
|
out = ast.FALSE
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &ast.Token{Tag: ast.BOOL}
|
||||||
|
t.Set(out)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func gt(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
out := ast.TRUE
|
||||||
|
second := in.Next
|
||||||
|
|
||||||
|
in = in.Eval(ft, vt, false)
|
||||||
|
|
||||||
|
if in.Tag != ast.NUMBER || second.Tag != ast.NUMBER {
|
||||||
|
log.Log(log.ERR, "non-number argument to numeric boolean operator", ">/<=")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l, _ := strconv.ParseInt(in.Value(), 10, 64)
|
||||||
|
r, _ := strconv.ParseInt(second.Value(), 10, 64)
|
||||||
|
|
||||||
|
if l <= r {
|
||||||
|
out = ast.FALSE
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &ast.Token{Tag: ast.BOOL}
|
||||||
|
t.Set(out)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func ne(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
return not(eq(in, vt, ft), vt, ft)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gte(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
return not(lt(in, vt, ft), vt, ft)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lte(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
return not(gt(in, vt, ft), vt, ft)
|
||||||
|
}
|
||||||
144
stdlib/call.go
144
stdlib/call.go
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"fmt"
|
"fmt"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strconv"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
@ -30,15 +31,30 @@ import (
|
||||||
|
|
||||||
var bgProcs = make([]*exec.Cmd, 0)
|
var bgProcs = make([]*exec.Cmd, 0)
|
||||||
var LastExitCode int
|
var LastExitCode int
|
||||||
|
var sigs = []os.Signal{
|
||||||
|
os.Interrupt,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
syscall.SIGTSTP,
|
||||||
|
syscall.SIGTTIN,
|
||||||
|
syscall.SIGTTOU,
|
||||||
|
syscall.SIGCONT,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
func call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
in = in.Eval(ft, vt, true)
|
||||||
if in == nil {
|
if in == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
path, err := exec.LookPath(in.Inner.(string))
|
if in.Tag == ast.LIST {
|
||||||
|
log.Log(log.ERR, "couldnt exec, target bin is a list", "call")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := exec.LookPath(in.Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log(log.ERR, "Couldnt exec " + in.Inner.(string) + ", file not found", "call")
|
log.Log(log.ERR, "Couldnt exec " + in.Value() + ", file not found", "call")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,7 +65,7 @@ func call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, i.Inner.(string))
|
args = append(args, i.Value())
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
|
|
@ -63,7 +79,7 @@ func call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
|
|
||||||
signalChan := make(chan os.Signal, 2)
|
signalChan := make(chan os.Signal, 2)
|
||||||
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(signalChan, sigs...)
|
||||||
go func() {
|
go func() {
|
||||||
sig := <-signalChan
|
sig := <-signalChan
|
||||||
cmd.Process.Signal(sig)
|
cmd.Process.Signal(sig)
|
||||||
|
|
@ -71,7 +87,7 @@ func call(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
close(signalChan)
|
close(signalChan)
|
||||||
signal.Reset(os.Interrupt, syscall.SIGTERM)
|
signal.Reset(sigs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
LastExitCode = exitError.ExitCode()
|
LastExitCode = exitError.ExitCode()
|
||||||
|
|
@ -88,9 +104,14 @@ func bgcall(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
path, err := exec.LookPath(in.Inner.(string))
|
if in.Tag == ast.LIST {
|
||||||
|
log.Log(log.ERR, "couldnt exec, target bin is a list", "call")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := exec.LookPath(in.Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log(log.ERR, "Couldnt exec " + in.Inner.(string) + ", file not found", "call")
|
log.Log(log.ERR, "Couldnt exec " + in.Value() + ", file not found", "call")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,7 +122,7 @@ func bgcall(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, i.Inner.(string))
|
args = append(args, i.Value())
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
|
|
@ -113,7 +134,12 @@ func bgcall(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
bgProcs = append(bgProcs, cmd)
|
bgProcs = append(bgProcs, cmd)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
cmd.Start()
|
cmd.Start()
|
||||||
|
cmd.Process.Signal(syscall.SIGTSTP)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,19 +151,17 @@ func fg(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
cmd := bgProcs[0]
|
cmd := bgProcs[0]
|
||||||
bgProcs = bgProcs[1:]
|
bgProcs = bgProcs[1:]
|
||||||
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
|
|
||||||
signalChan := make(chan os.Signal, 2)
|
signalChan := make(chan os.Signal, 2)
|
||||||
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(signalChan, sigs...)
|
||||||
go func() {
|
go func() {
|
||||||
sig := <-signalChan
|
sig := <-signalChan
|
||||||
cmd.Process.Signal(sig)
|
cmd.Process.Signal(sig)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
cmd.Process.Signal(syscall.SIGCONT)
|
||||||
err := cmd.Wait()
|
err := cmd.Wait()
|
||||||
close(signalChan)
|
close(signalChan)
|
||||||
signal.Reset(os.Interrupt, syscall.SIGTERM)
|
signal.Reset(sigs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
LastExitCode = exitError.ExitCode()
|
LastExitCode = exitError.ExitCode()
|
||||||
|
|
@ -149,15 +173,46 @@ func fg(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func jobs(in *ast.Token, vt ast.VarTable, fg ast.FuncTable) *ast.Token {
|
||||||
|
ret := &ast.Token{
|
||||||
|
Tag: ast.LIST,
|
||||||
|
}
|
||||||
|
|
||||||
|
_inner := &ast.Token{
|
||||||
|
Tag: ast.STRING,
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Direct(_inner)
|
||||||
|
_inner.Set(fmt.Sprintf("Total: %d", len(bgProcs)))
|
||||||
|
|
||||||
|
iter := &_inner
|
||||||
|
for i := 0; i < len(bgProcs); i += 1 {
|
||||||
|
(*iter).Next = &ast.Token{
|
||||||
|
Tag: ast.STRING,
|
||||||
|
}
|
||||||
|
(*iter).Next.Set(fmt.Sprintf("[%d]: %d", i, bgProcs[i].Process.Pid))
|
||||||
|
iter = &(*iter).Next
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
func read_cmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
func read_cmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
in = in.Eval(ft, vt, true)
|
||||||
|
|
||||||
if in == nil {
|
if in == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if in.Tag == ast.LIST {
|
||||||
|
log.Log(log.ERR, "couldnt exec, target bin is a list", "call")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
path, err := exec.LookPath(in.Inner.(string))
|
path, err := exec.LookPath(in.Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log(log.ERR, "Couldnt exec " + in.Inner.(string) + ", file not found", "call")
|
log.Log(log.ERR, "Couldnt exec " + in.Value() + ", file not found", "call")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,7 +223,7 @@ func read_cmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, i.Inner.(string))
|
args = append(args, i.Value())
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
|
|
@ -182,7 +237,7 @@ func read_cmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
|
|
||||||
signalChan := make(chan os.Signal, 2)
|
signalChan := make(chan os.Signal, 2)
|
||||||
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(signalChan, sigs...)
|
||||||
go func() {
|
go func() {
|
||||||
sig := <-signalChan
|
sig := <-signalChan
|
||||||
cmd.Process.Signal(sig)
|
cmd.Process.Signal(sig)
|
||||||
|
|
@ -190,7 +245,7 @@ func read_cmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
close(signalChan)
|
close(signalChan)
|
||||||
signal.Reset(os.Interrupt, syscall.SIGTERM)
|
signal.Reset(sigs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
LastExitCode = exitError.ExitCode()
|
LastExitCode = exitError.ExitCode()
|
||||||
|
|
@ -199,10 +254,59 @@ func read_cmd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.STRING, Inner: out.String()}
|
ret := &ast.Token{Tag: ast.STRING}
|
||||||
|
ret.Set(out.String())
|
||||||
|
return ret
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_exit(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
func get_exit(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
return &ast.Token{Tag: ast.NUMBER, Inner: fmt.Sprintf("%d", LastExitCode)}
|
ret := &ast.Token{Tag: ast.NUMBER}
|
||||||
|
ret.Set(fmt.Sprintf("%d", LastExitCode))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func kill(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
in = in.Eval(ft, vt, true)
|
||||||
|
|
||||||
|
if in.Tag == ast.LIST {
|
||||||
|
log.Log(log.ERR, "non-number argument to kill function", "kill")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := strconv.ParseInt(in.Value(), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Log(log.ERR, "error parsing arg to kill: " + err.Error(), "kill")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
newBgProcs := []*exec.Cmd{}
|
||||||
|
for _, i := range bgProcs {
|
||||||
|
if i.Process.Pid != int(pid) {
|
||||||
|
newBgProcs = append(newBgProcs, i)
|
||||||
|
} else {
|
||||||
|
found = true
|
||||||
|
err = i.Process.Kill()
|
||||||
|
if err != nil {
|
||||||
|
log.Log(log.ERR, fmt.Sprintf("error killing process %d: %s",
|
||||||
|
int(pid), err.Error()), "kill")
|
||||||
|
newBgProcs = append(newBgProcs, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bgProcs = newBgProcs
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
// docs say no error on unix systems
|
||||||
|
proc, _ := os.FindProcess(int(pid))
|
||||||
|
err = proc.Kill()
|
||||||
|
if err != nil {
|
||||||
|
log.Log(log.ERR, fmt.Sprintf("error killing process %d: %s",
|
||||||
|
int(pid), err.Error()), "kill")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
85
stdlib/control_flow.go
Normal file
85
stdlib/control_flow.go
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
/* SHS: Syntactically Homogeneous Shell
|
||||||
|
* Copyright (C) 2019 Aidan Hahn
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package stdlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/whom/shs/ast"
|
||||||
|
"gitlab.com/whom/shs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* return one evaluated form or another based on the boolean statement
|
||||||
|
*/
|
||||||
|
func shs_if(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
cond := in
|
||||||
|
t := cond.Next
|
||||||
|
f := t.Next
|
||||||
|
cond.Next = nil
|
||||||
|
t.Next = nil
|
||||||
|
|
||||||
|
cond = cond.Eval(ft, vt, false)
|
||||||
|
if cond == nil || cond.Tag != ast.BOOL {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"first argument to if must be a bool statement",
|
||||||
|
"if")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cond.Value() {
|
||||||
|
case ast.TRUE:
|
||||||
|
return t
|
||||||
|
|
||||||
|
case ast.FALSE:
|
||||||
|
return f
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"improper bool!",
|
||||||
|
"if")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* continually eval n forms while element #1 evals to T
|
||||||
|
*/
|
||||||
|
func shs_while(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
cond := in
|
||||||
|
forms := in.Next
|
||||||
|
in.Next = nil
|
||||||
|
var res *ast.Token
|
||||||
|
|
||||||
|
eval := cond.Eval(ft, vt, false)
|
||||||
|
if eval == nil || eval.Tag != ast.BOOL {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"first argument to while must be a bool statement",
|
||||||
|
"while")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// slight downside here: doesnt log when the wrong tag is set
|
||||||
|
for eval.Tag == ast.BOOL && eval.Value() == ast.TRUE {
|
||||||
|
// eval all forms
|
||||||
|
for i := forms; i != nil; i = i.Next {
|
||||||
|
res = i.Eval(ft, vt, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// retest eval
|
||||||
|
eval = cond.Eval(ft, vt, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
@ -19,19 +19,176 @@ package stdlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"io/ioutil"
|
||||||
"gitlab.com/whom/shs/ast"
|
"gitlab.com/whom/shs/ast"
|
||||||
"gitlab.com/whom/shs/log"
|
"gitlab.com/whom/shs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/* Take a path, return the absolute path
|
||||||
|
* does not verify that the absolute path is correct
|
||||||
|
* currently only supports paths using forward slashes
|
||||||
|
*
|
||||||
|
* TODO: handle ~
|
||||||
|
*/
|
||||||
|
func AbsPath(arg string) string {
|
||||||
|
if arg[0] != '/' {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"Couldnt get working directory: " + err.Error(),
|
||||||
|
"path")
|
||||||
|
return arg
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir + "/" + arg
|
||||||
|
}
|
||||||
|
|
||||||
|
return arg
|
||||||
|
}
|
||||||
|
|
||||||
func cd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
func cd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
in = in.Eval(ft, vt, true)
|
||||||
|
|
||||||
|
if in == nil {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"arguments to cd evaluated to nil!",
|
||||||
|
"cd")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if in.Tag == ast.LIST {
|
if in.Tag == ast.LIST {
|
||||||
log.Log(log.ERR, "Couldnt change dir to a list", "cd")
|
log.Log(log.ERR, "Couldnt change dir to a list", "cd")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := os.Chdir(in.Inner.(string))
|
err := os.Chdir(in.Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Log(log.ERR, err.Error(), "cd")
|
log.Log(log.ERR, err.Error(), "cd")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fexists(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
in = in.Eval(ft, vt, false)
|
||||||
|
if in == nil || (in.Tag != ast.NUMBER && in.Tag != ast.STRING) {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"argument to fexists must be a string or number",
|
||||||
|
"fexists")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := in.Value()
|
||||||
|
out := ast.TRUE
|
||||||
|
|
||||||
|
if _, err := os.Stat(AbsPath(filename)); err != nil {
|
||||||
|
log.Log(log.DEBUG,
|
||||||
|
"couldnt stat file: " + err.Error(),
|
||||||
|
"fexists")
|
||||||
|
out = ast.FALSE
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := &ast.Token{Tag: ast.BOOL}
|
||||||
|
ret.Set(out)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func fread(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
in = in.Eval(ft, vt, false)
|
||||||
|
exists := fexists(in, vt, ft) // some waste, extra use of Eval
|
||||||
|
if exists == nil || exists.Tag != ast.BOOL || exists.Value() == ast.FALSE {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"error calling fexists or file doesnt exist",
|
||||||
|
"fread")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fname := in.Value()
|
||||||
|
text, err := ioutil.ReadFile(fname)
|
||||||
|
if err != nil {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"error reading file" + err.Error(),
|
||||||
|
"fread")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := &ast.Token{Tag: ast.STRING}
|
||||||
|
ret.Set(string(text))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func fwrite(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
in = in.Eval(ft, vt, false)
|
||||||
|
if in == nil || in.Tag == ast.SYMBOL || in.Tag == ast.LIST {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"first argument must be a filename",
|
||||||
|
"fwrite")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := in.Next
|
||||||
|
if text == nil || text.Tag == ast.SYMBOL || text.Tag == ast.LIST {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"second argument must be stringable",
|
||||||
|
"fwrite")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ioutil.WriteFile(
|
||||||
|
AbsPath(in.Value()),
|
||||||
|
[]byte(text.Value()),
|
||||||
|
0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"error writing file: " + err.Error(),
|
||||||
|
"fwrite")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fappend(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
in = in.Eval(ft, vt, false)
|
||||||
|
if in == nil || in.Tag == ast.SYMBOL || in.Tag == ast.LIST {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"first argument must be a filename",
|
||||||
|
"fappend")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := in.Next
|
||||||
|
if text == nil || text.Tag == ast.SYMBOL || text.Tag == ast.LIST {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"second argument must be stringable",
|
||||||
|
"fappend")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
exists := fexists(in, vt, ft)
|
||||||
|
if exists.Value() == ast.FALSE {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"file "+in.Value()+" does not exist",
|
||||||
|
"fappend")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
f, err := os.OpenFile(
|
||||||
|
AbsPath(in.Value()),
|
||||||
|
os.O_APPEND|os.O_CREATE|os.O_WRONLY,
|
||||||
|
0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"couldnt open file for append: " + err.Error(),
|
||||||
|
"fappend")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := f.WriteString(text.Value()); err != nil {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"error appending to file: " + err.Error(),
|
||||||
|
"fappend")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
106
stdlib/funcs.go
Normal file
106
stdlib/funcs.go
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
/* SHS: Syntactically Homogeneous Shell
|
||||||
|
* Copyright (C) 2019 Aidan Hahn
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package stdlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/whom/shs/ast"
|
||||||
|
"gitlab.com/whom/shs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func decl_func(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token {
|
||||||
|
name := input
|
||||||
|
if name.Tag != ast.SYMBOL {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"argument 1 of func must be a symbol to be exported",
|
||||||
|
"func")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var numArgs int
|
||||||
|
args := name.Next
|
||||||
|
if args.Tag != ast.LIST {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"argument 2 of func must be a flat list of argument symbols",
|
||||||
|
"func")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
form := args.Next
|
||||||
|
if form.Tag != ast.LIST {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"argument 3 of func must be a form to be evaluated",
|
||||||
|
"func")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := args.Expand(); i != nil; i = i.Next {
|
||||||
|
if i.Tag != ast.SYMBOL {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"all args in user defined functions must be declared in the form of symbols",
|
||||||
|
"func")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
numArgs += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.SyncTablesWithOSEnviron = ASTSYNCSTATE
|
||||||
|
ret := form.Eval(ft, vt, false)
|
||||||
|
ast.SyncTablesWithOSEnviron = false
|
||||||
|
for i := args.Expand(); i != nil; i = i.Next {
|
||||||
|
ast.RemoveVar(i.Value(), vt)
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.SyncTablesWithOSEnviron = ASTSYNCSTATE
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
(*funcs)[name.Value()] = &ast.Function{
|
||||||
|
Function: inner,
|
||||||
|
Name: name.Value(),
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: numArgs,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ func expand(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
||||||
return input.Inner.(*ast.Token)
|
return input.Eval(funcs, vars, false).Expand()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* L_APPEND (append from repl)
|
/* L_APPEND (append from repl)
|
||||||
|
|
@ -45,15 +45,16 @@ func l_append(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Tok
|
||||||
src := input
|
src := input
|
||||||
|
|
||||||
if input.Tag != ast.LIST {
|
if input.Tag != ast.LIST {
|
||||||
// TODO: position??
|
r := &ast.Token{Tag: ast.LIST}
|
||||||
return input
|
r.Direct(input)
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// deref inner first
|
// deref inner first
|
||||||
i := src.Inner.(*ast.Token)
|
i := src.Expand()
|
||||||
iter := &i
|
iter := &i
|
||||||
if *iter == nil {
|
if *iter == nil {
|
||||||
src.Inner = input.Next
|
src.Direct(input.Next)
|
||||||
src.Next = nil
|
src.Next = nil
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
255
stdlib/stdlib.go
255
stdlib/stdlib.go
|
|
@ -19,12 +19,56 @@ package stdlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"fmt"
|
||||||
|
"gitlab.com/whom/shs/log"
|
||||||
"gitlab.com/whom/shs/ast"
|
"gitlab.com/whom/shs/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenFuncTable() ast.FuncTable {
|
func GenFuncTable() ast.FuncTable {
|
||||||
var stdlib ast.FuncTable
|
var stdlib ast.FuncTable
|
||||||
stdlib = &map[string]*ast.Function{
|
stdlib = &map[string]*ast.Function{
|
||||||
|
"if": &ast.Function{
|
||||||
|
Function: shs_if,
|
||||||
|
Name: "if",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 3,
|
||||||
|
},
|
||||||
|
|
||||||
|
"while": &ast.Function{
|
||||||
|
Function: shs_while,
|
||||||
|
Name: "while",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: -1,
|
||||||
|
},
|
||||||
|
|
||||||
|
"eval": &ast.Function{
|
||||||
|
Function: eval,
|
||||||
|
Name: "eval",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: -1,
|
||||||
|
},
|
||||||
|
|
||||||
|
"func": &ast.Function{
|
||||||
|
Function: decl_func,
|
||||||
|
Name: "decl_func",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 3,
|
||||||
|
},
|
||||||
|
|
||||||
|
"export": &ast.Function{
|
||||||
|
Function: export,
|
||||||
|
Name: "export",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
"input": &ast.Function{
|
||||||
|
Function: input,
|
||||||
|
Name: "input",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 1,
|
||||||
|
},
|
||||||
|
|
||||||
"...": &ast.Function{
|
"...": &ast.Function{
|
||||||
Function: expand,
|
Function: expand,
|
||||||
Name: "...",
|
Name: "...",
|
||||||
|
|
@ -39,6 +83,62 @@ func GenFuncTable() ast.FuncTable {
|
||||||
Args: -1,
|
Args: -1,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"exit": &ast.Function{
|
||||||
|
Function: exit_shell,
|
||||||
|
Name: "exit",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
"eq": &ast.Function{
|
||||||
|
Function: eq,
|
||||||
|
Name: "==",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ne": &ast.Function{
|
||||||
|
Function: ne,
|
||||||
|
Name: "!=",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
"<": &ast.Function{
|
||||||
|
Function: lt,
|
||||||
|
Name: "<",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
">": &ast.Function{
|
||||||
|
Function: gt,
|
||||||
|
Name: ">",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
"<=": &ast.Function{
|
||||||
|
Function: lte,
|
||||||
|
Name: "<=",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
">=": &ast.Function{
|
||||||
|
Function: gte,
|
||||||
|
Name: ">=",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
"!": &ast.Function{
|
||||||
|
Function: not,
|
||||||
|
Name: "!",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 1,
|
||||||
|
},
|
||||||
|
|
||||||
"+": &ast.Function{
|
"+": &ast.Function{
|
||||||
Function: add,
|
Function: add,
|
||||||
Name: "add",
|
Name: "add",
|
||||||
|
|
@ -67,6 +167,27 @@ func GenFuncTable() ast.FuncTable {
|
||||||
Args: -1,
|
Args: -1,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"cd": &ast.Function{
|
||||||
|
Function: cd,
|
||||||
|
Name: "changedir",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
"concat": &ast.Function{
|
||||||
|
Function: concat,
|
||||||
|
Name:"concatenate",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: -1,
|
||||||
|
},
|
||||||
|
|
||||||
|
"print": &ast.Function{
|
||||||
|
Function:print_str,
|
||||||
|
Name: "print",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 1,
|
||||||
|
},
|
||||||
|
|
||||||
"l": &ast.Function{
|
"l": &ast.Function{
|
||||||
Function: call,
|
Function: call,
|
||||||
Name: "call",
|
Name: "call",
|
||||||
|
|
@ -88,13 +209,6 @@ func GenFuncTable() ast.FuncTable {
|
||||||
Args: 0,
|
Args: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
"cd": &ast.Function{
|
|
||||||
Function: cd,
|
|
||||||
Name: "changedir",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
"$": &ast.Function{
|
"$": &ast.Function{
|
||||||
Function: read_cmd,
|
Function: read_cmd,
|
||||||
Name: "read cmd",
|
Name: "read cmd",
|
||||||
|
|
@ -102,33 +216,64 @@ func GenFuncTable() ast.FuncTable {
|
||||||
Args: -1,
|
Args: -1,
|
||||||
},
|
},
|
||||||
|
|
||||||
"concat": &ast.Function{
|
|
||||||
Function: concat,
|
|
||||||
Name:"concatenate",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: -1,
|
|
||||||
},
|
|
||||||
|
|
||||||
"print": &ast.Function{
|
|
||||||
Function:print_str,
|
|
||||||
Name: "print",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
"exit": &ast.Function{
|
|
||||||
Function: exit_shell,
|
|
||||||
Name: "exit",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
"?": &ast.Function{
|
"?": &ast.Function{
|
||||||
Function: get_exit,
|
Function: get_exit,
|
||||||
Name:"get exit code",
|
Name:"get exit code",
|
||||||
TimesCalled: 0,
|
TimesCalled: 0,
|
||||||
Args: 0,
|
Args: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
USE NATIVE KILL COMMAND.
|
||||||
|
"kill": &ast.Function{
|
||||||
|
Function: kill,
|
||||||
|
Name: "kill job",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 1,
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
|
||||||
|
"jobs": &ast.Function{
|
||||||
|
Function: jobs,
|
||||||
|
Name: "list jobs",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
"info": &ast.Function{
|
||||||
|
Function: sh_info,
|
||||||
|
Name: "Shell Info",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
"fexists": &ast.Function{
|
||||||
|
Function: fexists,
|
||||||
|
Name: "file exists",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
"fread": &ast.Function{
|
||||||
|
Function: fread,
|
||||||
|
Name: "read file",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
"fwrite": &ast.Function{
|
||||||
|
Function: fwrite,
|
||||||
|
Name: "write file",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
"fappend": &ast.Function{
|
||||||
|
Function: fappend,
|
||||||
|
Name:"append to file",
|
||||||
|
TimesCalled: 0,
|
||||||
|
Args: 2,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return stdlib
|
return stdlib
|
||||||
|
|
@ -138,3 +283,57 @@ func exit_shell(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
return nil // I hope execution doesnt get here
|
return nil // I hope execution doesnt get here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sh_info(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
switch in.Tag {
|
||||||
|
case ast.BOOL:
|
||||||
|
fmt.Printf("BOOL LITERAL\nValue: %s\n", in.Value())
|
||||||
|
case ast.STRING:
|
||||||
|
fmt.Printf("STRING LITERAL \nValue: %s\n", in.Value())
|
||||||
|
case ast.NUMBER:
|
||||||
|
fmt.Printf("NUMBER LITERAL \nValue: %s\n", in.Value())
|
||||||
|
case ast.LIST:
|
||||||
|
fmt.Printf("LIST \nString Value: %s, AST:\n", in.String())
|
||||||
|
ast.PrintSExprsIndividually(in)
|
||||||
|
case ast.SYMBOL:
|
||||||
|
repr := ast.GetVar(in.Value(), vt)
|
||||||
|
if repr != nil {
|
||||||
|
fmt.Printf("VARIABLE\nTYPE: %s\nVALUE: %s\n", ast.GetTagAsStr(repr.Tag), repr.Value())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
funct := ast.GetFunction(in.Value(), ft)
|
||||||
|
if funct != nil {
|
||||||
|
fmt.Printf("FUNCTION\nNAME: %s\nTIMES CALLED: %s\nNUM ARGS: %d\n", funct.Name, funct.TimesCalled, funct.Args)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("UNKNOWN SYMBOL\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func eval(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
return in.Eval(ft, vt, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func input(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
in = in.Eval(ft, vt, false)
|
||||||
|
if in.Tag != ast.STRING && in.Tag != ast.NUMBER {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"argument to input must be a string or number",
|
||||||
|
"input")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt := in.Value()
|
||||||
|
var output string
|
||||||
|
|
||||||
|
fmt.Printf(prompt)
|
||||||
|
fmt.Scanln(&output)
|
||||||
|
|
||||||
|
ret := &ast.Token{Tag: ast.STRING}
|
||||||
|
ret.Set(output)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func concat(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
func concat(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
|
in = in.Eval(ft, vt, false)
|
||||||
|
|
||||||
var res string
|
var res string
|
||||||
for i := in; i != nil; i = i.Next {
|
for i := in; i != nil; i = i.Next {
|
||||||
if i.Tag == ast.LIST {
|
if i.Tag == ast.LIST {
|
||||||
|
|
@ -32,13 +34,15 @@ func concat(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
res += i.Inner.(string)
|
res += i.Value()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.STRING, Inner: res}
|
t := &ast.Token{Tag: ast.STRING}
|
||||||
|
t.Set(res)
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func print_str(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
func print_str(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
||||||
fmt.Println(in.Inner.(string))
|
fmt.Println(in.Eval(ft, vt, false))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
stdlib/vars.go
Normal file
39
stdlib/vars.go
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
/* SHS: Syntactically Homogeneous Shell
|
||||||
|
* Copyright (C) 2019 Aidan Hahn
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package stdlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/whom/shs/ast"
|
||||||
|
"gitlab.com/whom/shs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func export(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token {
|
||||||
|
name := input
|
||||||
|
|
||||||
|
form := name.Next.Eval(funcs, vars, false)
|
||||||
|
if name.Tag != ast.SYMBOL {
|
||||||
|
log.Log(log.ERR,
|
||||||
|
"first arg should be a symbol",
|
||||||
|
"export")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.SetVar(name.Value(), form, vars)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue