new eval.go
This commit is contained in:
parent
89d6a1013b
commit
2a2e5b4527
15 changed files with 382 additions and 1215 deletions
140
ast/eval.go
140
ast/eval.go
|
|
@ -17,88 +17,96 @@
|
||||||
|
|
||||||
package ast
|
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
|
||||||
var reduce func(*Token) *Token
|
* (true when first elem of a list)
|
||||||
reduce = func(t_ *Token) *Token {
|
*/
|
||||||
var unwrap bool
|
func (in *Token) Eval(funcs FuncTable, vars VarTable) *Token {
|
||||||
|
if in == nil {
|
||||||
if t_.Next != nil {
|
|
||||||
t_.Next = reduce(t_.Next)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (t_.Tag) {
|
|
||||||
case SYMBOL:
|
|
||||||
maybeToken := GetVar(t_.Inner.(string), vars)
|
|
||||||
if maybeToken != nil {
|
|
||||||
tok, _ := maybeToken.Eval(funcs, vars)
|
|
||||||
tok.Next = t_.Next
|
|
||||||
return tok
|
|
||||||
}
|
|
||||||
|
|
||||||
case LIST:
|
|
||||||
t_.Inner, unwrap = t_.Inner.(*Token).Eval(funcs, vars)
|
|
||||||
if unwrap {
|
|
||||||
next := t_.Next
|
|
||||||
t_ = t_.Inner.(*Token)
|
|
||||||
if t_ == nil {
|
|
||||||
log.Log(log.DEBUG, "nil Inner on list unwrap", "eval")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
i := &t_
|
var res *Token
|
||||||
for (*i).Next != nil {
|
|
||||||
i = &((*i).Next)
|
|
||||||
}
|
|
||||||
|
|
||||||
(*i).Next = next
|
switch in.Tag {
|
||||||
}
|
case BOOL, NUMBER, STRING:
|
||||||
}
|
res = in
|
||||||
|
|
||||||
return t_
|
case SYMBOL:
|
||||||
}
|
res = GetVar(in.Value(), vars)
|
||||||
|
if res == nil {
|
||||||
|
res = in
|
||||||
|
|
||||||
ret := reduce(t)
|
if GetFunction(in.Value(), funcs) == nil {
|
||||||
if ret == nil {
|
log.Log(log.ERR,
|
||||||
log.Log(log.INFO, "reduce returned nil", "eval")
|
"undefined symbol: "+in.Value(),
|
||||||
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")
|
"eval")
|
||||||
return ret, false
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.Next = in.Next
|
||||||
|
|
||||||
|
|
||||||
|
case LIST:
|
||||||
|
inner := in.Expand()
|
||||||
|
if inner == nil {
|
||||||
|
res = in
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// see the use of CallFunction below
|
if inner.Tag != SYMBOL {
|
||||||
ret = &Token{Next: ret}
|
in.Direct(inner.Eval(funcs, vars))
|
||||||
|
res = in
|
||||||
|
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{inner: inner}
|
||||||
|
}
|
||||||
|
res = funct.CallFunction(inner.Next, vars, funcs).Eval(funcs, vars)
|
||||||
|
res.Append(in.Next)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
log.Log(log.DEBUG,
|
log.Log(log.ERR,
|
||||||
"could not find definition for symbol " + ret.Inner.(string),
|
"undefined function "+inner.Value()+" called",
|
||||||
"eval")
|
"eval")
|
||||||
return ret, false
|
return nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (*f).CallFunction(ret.Next, vars, funcs), true
|
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)
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
||||||
185
ast/lex.go
Normal file
185
ast/lex.go
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
/* 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 {
|
||||||
|
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 {
|
||||||
|
(*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, 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]"
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
253
ast/token.go
253
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 (
|
||||||
|
|
@ -35,166 +32,116 @@ 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 {
|
/* Print function which is better suited for repl.
|
||||||
if len(input) == 0 {
|
* 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++
|
|
||||||
} else {
|
|
||||||
return false
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns a tag in text
|
||||||
|
*/
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,13 @@ func GetVar(arg string, vt VarTable) *Token {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this could be much more optimal
|
// TODO: this could be much more optimal
|
||||||
|
// TODO: Make sure variables are evaluated before being set
|
||||||
|
// 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)
|
||||||
|
|
@ -91,7 +93,7 @@ func GetVarFromTables(arg string, library []VarTable) *Token {
|
||||||
func InitVarTable(table VarTable) {
|
func InitVarTable(table VarTable) {
|
||||||
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 {
|
||||||
|
|
|
||||||
|
|
@ -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:], " ")))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,6 @@ func setLogLvl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ast.CallExecutablesFromUndefFuncCalls = true
|
|
||||||
|
|
||||||
debug := os.Getenv("SH_DEBUG_MODE")
|
debug := os.Getenv("SH_DEBUG_MODE")
|
||||||
hist := os.Getenv("SH_HIST_FILE")
|
hist := os.Getenv("SH_HIST_FILE")
|
||||||
prompt := os.Getenv("SHS_SH_PROMPT")
|
prompt := os.Getenv("SHS_SH_PROMPT")
|
||||||
|
|
@ -57,8 +55,10 @@ func main() {
|
||||||
|
|
||||||
funcs = stdlib.GenFuncTable()
|
funcs = stdlib.GenFuncTable()
|
||||||
vars = &map[string]*ast.Token{}
|
vars = &map[string]*ast.Token{}
|
||||||
|
|
||||||
ast.InitVarTable(vars)
|
ast.InitVarTable(vars)
|
||||||
ast.SyncTablesWithOSEnviron = true
|
ast.SyncTablesWithOSEnviron = true
|
||||||
|
ast.ExecWhenFuncUndef = false
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
@ -96,12 +96,8 @@ func main() {
|
||||||
ast.PrintSExprsIndividually(userInput)
|
ast.PrintSExprsIndividually(userInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, unwrap := userInput.Eval(funcs, vars)
|
result := userInput.Eval(funcs, vars)
|
||||||
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() + " ")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
# SHS STDLIB
|
|
||||||
|
|
||||||
In here are definitions for builtin functions that can be used from the repl. If you are looking for a way to extend this shell to fit your use case you are in the right spot.
|
|
||||||
|
|
||||||
## Datatypes
|
|
||||||
|
|
||||||
Within the AST package you can see [a number of important definitions.]("https://git.callpipe.com/aidan/shs/-/blob/master/ast/func_table.go") Worth noting are FuncTable, Operation, and Function.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Operation func(*Token, VarTable, FuncTable) *Token
|
|
||||||
|
|
||||||
type Function struct {
|
|
||||||
function Operation
|
|
||||||
name string
|
|
||||||
timesCalled int
|
|
||||||
args int
|
|
||||||
}
|
|
||||||
|
|
||||||
type FuncTable map[string]*Function
|
|
||||||
```
|
|
||||||
|
|
||||||
## The Standard Library
|
|
||||||
|
|
||||||
The standard library is loaded during the init step of the repl (or interpreter if you have embedded one). Alternatively, it can be loaded into your application by importing `git.callpipe.com/shs/stdlib`. In order to get a table of all available functions the `GenFuncTable` function is called, returning a struct of type `ast.FuncTable`. Any functions in the standard lib must be accounted for here in order for them to be available at repl or interpreter. Each Operation operates on a list of [Tokens](https://git.callpipe.com/aidan/shs/-/blob/master/ast/token.go). Every function should adhere to the idea that it takes in a list of Tokens and returns a list of Tokens.
|
|
||||||
|
|
||||||
## Working with Tokens
|
|
||||||
[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.
|
|
||||||
## Adding a function
|
|
||||||
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.
|
|
||||||
3. *Add your `Function` to the `FuncTable`.* Make sure your `Operations`s get added to the table generated in `GenFuncTable`.
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
- You can use the Log module to add helpful output to your functions.
|
|
||||||
- Try not to clutter the output with unnessesary ERR level logging.
|
|
||||||
- Make sure you properly set the Next element of any Token you swap into a list
|
|
||||||
- Make sure you properly set the Next element of the previous Token as you swap a token into a list
|
|
||||||
|
|
||||||
## License
|
|
||||||
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/
|
|
||||||
198
stdlib/arith.go
198
stdlib/arith.go
|
|
@ -1,198 +0,0 @@
|
||||||
/* 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 (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"gitlab.com/whom/shs/ast"
|
|
||||||
"gitlab.com/whom/shs/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PKG WIDE TODO: BIGNUM SYSTEM
|
|
||||||
// then write actually optimal routines once it is in place
|
|
||||||
// perhaps we simply write out arithmetic routines that operate on the strings
|
|
||||||
// then we need not worry about storage length.
|
|
||||||
|
|
||||||
func add(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
|
||||||
var res float64
|
|
||||||
|
|
||||||
for i := in; i != nil; i = i.Next {
|
|
||||||
if i.Tag != ast.NUMBER {
|
|
||||||
log.Log(log.ERR, "Non-number given to ADD", "add")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
token := i.Inner.(string)
|
|
||||||
isFloat := false
|
|
||||||
for _, char := range token {
|
|
||||||
if char == '.' {
|
|
||||||
isFloat = true
|
|
||||||
n, err := strconv.ParseFloat(token, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, "Err parsing number: " + err.Error(), "add")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
res += n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isFloat {
|
|
||||||
n, err := strconv.ParseInt(token, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, "Err parsing number: " + err.Error(), "add")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
res += float64(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.NUMBER, Inner: fmt.Sprintf("%f", res)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sub(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
|
||||||
var res float64
|
|
||||||
var sub float64
|
|
||||||
|
|
||||||
for i := in; i != nil; i = i.Next {
|
|
||||||
if i.Tag != ast.NUMBER {
|
|
||||||
log.Log(log.ERR, "Non-number given to SUB", "sub")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
token := i.Inner.(string)
|
|
||||||
isFloat := false
|
|
||||||
var inner float64
|
|
||||||
for _, char := range token {
|
|
||||||
if char == '.' {
|
|
||||||
isFloat = true
|
|
||||||
n, err := strconv.ParseFloat(token, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, "Err parsing number: " + err.Error(), "sub")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inner = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isFloat {
|
|
||||||
n, err := strconv.ParseInt(token, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, "Err parsing number: " + err.Error(), "sub")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inner = float64(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
if i.Next != nil {
|
|
||||||
sub += inner
|
|
||||||
} else {
|
|
||||||
res = inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.NUMBER, Inner: fmt.Sprintf("%f", res - sub)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mult(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
|
||||||
res := 1.0
|
|
||||||
|
|
||||||
for i := in; i != nil; i = i.Next {
|
|
||||||
if i.Tag != ast.NUMBER {
|
|
||||||
log.Log(log.ERR, "Non-number given to MULT", "mult")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
token := i.Inner.(string)
|
|
||||||
isFloat := false
|
|
||||||
for _, char := range token {
|
|
||||||
if char == '.' {
|
|
||||||
isFloat = true
|
|
||||||
n, err := strconv.ParseFloat(token, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, "Err parsing number: " + err.Error(), "mult")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
res *= n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isFloat {
|
|
||||||
n, err := strconv.ParseInt(token, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, "Err parsing number: " + err.Error(), "mult")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
res *= float64(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.NUMBER, Inner: fmt.Sprintf("%f", res)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func div(in *ast.Token, a ast.VarTable, f ast.FuncTable) *ast.Token {
|
|
||||||
var res float64
|
|
||||||
|
|
||||||
for i := in; i != nil; i = i.Next {
|
|
||||||
inner := 0.0
|
|
||||||
|
|
||||||
if i.Tag != ast.NUMBER {
|
|
||||||
log.Log(log.ERR, "Non-number given to DIV", "div")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
token := i.Inner.(string)
|
|
||||||
isFloat := false
|
|
||||||
for _, char := range token {
|
|
||||||
if char == '.' {
|
|
||||||
isFloat = true
|
|
||||||
n, err := strconv.ParseFloat(token, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, "Err parsing number: " + err.Error(), "div")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inner = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isFloat {
|
|
||||||
n, err := strconv.ParseInt(token, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, "Err parsing number: " + err.Error(), "div")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inner = float64(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == in {
|
|
||||||
res = inner
|
|
||||||
} else {
|
|
||||||
res = res / inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.NUMBER, Inner: fmt.Sprintf("%f", res)}
|
|
||||||
}
|
|
||||||
155
stdlib/bool.go
155
stdlib/bool.go
|
|
@ -1,155 +0,0 @@
|
||||||
/* 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// lt, gt
|
|
||||||
|
|
||||||
func not(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|
||||||
if in.Tag != ast.BOOL {
|
|
||||||
log.Log(log.ERR, "non-bool argument to 'not'", "not")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
out := "T"
|
|
||||||
if in.Inner.(string) == "T" {
|
|
||||||
out = "F"
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.BOOL, Inner: out}
|
|
||||||
}
|
|
||||||
|
|
||||||
func eq(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|
||||||
out := "T"
|
|
||||||
second := in.Next
|
|
||||||
|
|
||||||
if in.Tag != second.Tag {
|
|
||||||
out = "F"
|
|
||||||
} 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.Inner.(*ast.Token),
|
|
||||||
r_iter.Inner.(*ast.Token))
|
|
||||||
if diff {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if l_iter.Inner.(string) != r_iter.Inner.(string) {
|
|
||||||
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.Inner.(*ast.Token), second.Inner.(*ast.Token)) {
|
|
||||||
out = "F"
|
|
||||||
}
|
|
||||||
|
|
||||||
case ast.NUMBER, ast.STRING, ast.BOOL:
|
|
||||||
if in.Inner.(string) != second.Inner.(string) {
|
|
||||||
out = "F"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.BOOL, Inner: out}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lt(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|
||||||
out := "T"
|
|
||||||
|
|
||||||
second := in.Next
|
|
||||||
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.Inner.(string), 10, 64)
|
|
||||||
r, _ := strconv.ParseInt(second.Inner.(string), 10, 64)
|
|
||||||
|
|
||||||
if l >= r {
|
|
||||||
out = "F"
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.BOOL, Inner: out}
|
|
||||||
}
|
|
||||||
|
|
||||||
func gt(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|
||||||
out := "T"
|
|
||||||
|
|
||||||
second := in.Next
|
|
||||||
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.Inner.(string), 10, 64)
|
|
||||||
r, _ := strconv.ParseInt(second.Inner.(string), 10, 64)
|
|
||||||
|
|
||||||
if l <= r {
|
|
||||||
out = "F"
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.BOOL, Inner: out}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
280
stdlib/call.go
280
stdlib/call.go
|
|
@ -1,280 +0,0 @@
|
||||||
/* 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 (
|
|
||||||
"os"
|
|
||||||
"fmt"
|
|
||||||
"bytes"
|
|
||||||
"strconv"
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
|
||||||
"os/signal"
|
|
||||||
"gitlab.com/whom/shs/ast"
|
|
||||||
"gitlab.com/whom/shs/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var bgProcs = make([]*exec.Cmd, 0)
|
|
||||||
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 {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := exec.LookPath(in.Inner.(string))
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, "Couldnt exec " + in.Inner.(string) + ", file not found", "call")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{}
|
|
||||||
for i := in.Next; i != nil; i = i.Next {
|
|
||||||
if i.Tag == ast.LIST {
|
|
||||||
log.Log(log.ERR, "Couldnt exec " + path + ", element in arguments is a list", "call")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, i.Inner.(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if len(args) > 0 {
|
|
||||||
cmd = exec.Command(path, args...)
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command(path)
|
|
||||||
}
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
|
|
||||||
signalChan := make(chan os.Signal, 2)
|
|
||||||
signal.Notify(signalChan, sigs...)
|
|
||||||
go func() {
|
|
||||||
sig := <-signalChan
|
|
||||||
cmd.Process.Signal(sig)
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = cmd.Run()
|
|
||||||
close(signalChan)
|
|
||||||
signal.Reset(sigs...)
|
|
||||||
if err != nil {
|
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
|
||||||
LastExitCode = exitError.ExitCode()
|
|
||||||
} else {
|
|
||||||
log.Log(log.ERR, "Execution step returned unparsable error: " + err.Error(), "call")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func bgcall(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := exec.LookPath(in.Inner.(string))
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, "Couldnt exec " + in.Inner.(string) + ", file not found", "call")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{}
|
|
||||||
for i := in.Next; i != nil; i = i.Next {
|
|
||||||
if i.Tag == ast.LIST {
|
|
||||||
log.Log(log.ERR, "Couldnt exec " + path + ", element in arguments is a list", "call")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, i.Inner.(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if len(args) > 0 {
|
|
||||||
cmd = exec.Command(path, args...)
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command(path)
|
|
||||||
}
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
bgProcs = append(bgProcs, cmd)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
cmd.Start()
|
|
||||||
cmd.Process.Signal(syscall.SIGTSTP)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fg(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|
||||||
if len(bgProcs) < 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := bgProcs[0]
|
|
||||||
bgProcs = bgProcs[1:]
|
|
||||||
|
|
||||||
signalChan := make(chan os.Signal, 2)
|
|
||||||
signal.Notify(signalChan, sigs...)
|
|
||||||
go func() {
|
|
||||||
sig := <-signalChan
|
|
||||||
cmd.Process.Signal(sig)
|
|
||||||
}()
|
|
||||||
|
|
||||||
cmd.Process.Signal(syscall.SIGCONT)
|
|
||||||
err := cmd.Wait()
|
|
||||||
close(signalChan)
|
|
||||||
signal.Reset(sigs...)
|
|
||||||
if err != nil {
|
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
|
||||||
LastExitCode = exitError.ExitCode()
|
|
||||||
} else {
|
|
||||||
log.Log(log.ERR, "Execution step returned error: " + err.Error(), "call")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
Inner: fmt.Sprintf("Total: %d", len(bgProcs)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
ptr := ret.Inner.(*ast.Token)
|
|
||||||
iter := &ptr
|
|
||||||
for i := 0; i < len(bgProcs); i += 1 {
|
|
||||||
(*iter).Next = &ast.Token{
|
|
||||||
Tag: ast.STRING,
|
|
||||||
Inner: 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 {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var out bytes.Buffer
|
|
||||||
path, err := exec.LookPath(in.Inner.(string))
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, "Couldnt exec " + in.Inner.(string) + ", file not found", "call")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{}
|
|
||||||
for i := in.Next; i != nil; i = i.Next {
|
|
||||||
if i.Tag == ast.LIST {
|
|
||||||
log.Log(log.ERR, "Couldnt exec " + path + ", element in arguments is a list", "call")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, i.Inner.(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if len(args) > 0 {
|
|
||||||
cmd = exec.Command(path, args...)
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command(path)
|
|
||||||
}
|
|
||||||
cmd.Stdout = &out
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
|
|
||||||
signalChan := make(chan os.Signal, 2)
|
|
||||||
signal.Notify(signalChan, sigs...)
|
|
||||||
go func() {
|
|
||||||
sig := <-signalChan
|
|
||||||
cmd.Process.Signal(sig)
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = cmd.Run()
|
|
||||||
close(signalChan)
|
|
||||||
signal.Reset(sigs...)
|
|
||||||
if err != nil {
|
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
|
||||||
LastExitCode = exitError.ExitCode()
|
|
||||||
} else {
|
|
||||||
log.Log(log.ERR, "Execution step returned error: " + err.Error(), "$")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.STRING, Inner: out.String()}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func kill(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|
||||||
if in.Tag == ast.LIST {
|
|
||||||
log.Log(log.ERR, "non-number argument to kill function", "kill")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pid, err := strconv.ParseInt(in.Inner.(string), 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
/* 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 (
|
|
||||||
"os"
|
|
||||||
"gitlab.com/whom/shs/ast"
|
|
||||||
"gitlab.com/whom/shs/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func cd(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|
||||||
if in.Tag == ast.LIST {
|
|
||||||
log.Log(log.ERR, "Couldnt change dir to a list", "cd")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := os.Chdir(in.Inner.(string))
|
|
||||||
if err != nil {
|
|
||||||
log.Log(log.ERR, err.Error(), "cd")
|
|
||||||
}
|
|
||||||
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.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 {
|
||||||
|
|
|
||||||
150
stdlib/stdlib.go
150
stdlib/stdlib.go
|
|
@ -39,162 +39,12 @@ func GenFuncTable() ast.FuncTable {
|
||||||
Args: -1,
|
Args: -1,
|
||||||
},
|
},
|
||||||
|
|
||||||
"+": &ast.Function{
|
|
||||||
Function: add,
|
|
||||||
Name: "add",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: -1,
|
|
||||||
},
|
|
||||||
|
|
||||||
"-": &ast.Function{
|
|
||||||
Function: sub,
|
|
||||||
Name: "sub",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: -1,
|
|
||||||
},
|
|
||||||
|
|
||||||
"*": &ast.Function{
|
|
||||||
Function: mult,
|
|
||||||
Name: "mult",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: -1,
|
|
||||||
},
|
|
||||||
|
|
||||||
"/": &ast.Function{
|
|
||||||
Function: div,
|
|
||||||
Name: "div",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: -1,
|
|
||||||
},
|
|
||||||
|
|
||||||
"l": &ast.Function{
|
|
||||||
Function: call,
|
|
||||||
Name: "call",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: -1,
|
|
||||||
},
|
|
||||||
|
|
||||||
"bg": &ast.Function{
|
|
||||||
Function: bgcall,
|
|
||||||
Name: "background call",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: -1,
|
|
||||||
},
|
|
||||||
|
|
||||||
"fg": &ast.Function{
|
|
||||||
Function: fg,
|
|
||||||
Name: "foreground",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
"cd": &ast.Function{
|
|
||||||
Function: cd,
|
|
||||||
Name: "changedir",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
"$": &ast.Function{
|
|
||||||
Function: read_cmd,
|
|
||||||
Name: "read cmd",
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
|
|
||||||
"exit": &ast.Function{
|
"exit": &ast.Function{
|
||||||
Function: exit_shell,
|
Function: exit_shell,
|
||||||
Name: "exit",
|
Name: "exit",
|
||||||
TimesCalled: 0,
|
TimesCalled: 0,
|
||||||
Args: 0,
|
Args: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
"?": &ast.Function{
|
|
||||||
Function: get_exit,
|
|
||||||
Name:"get exit code",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
USE NATIVE KILL COMMAND.
|
|
||||||
"kill": &ast.Function{
|
|
||||||
Function: kill,
|
|
||||||
Name: "kill job",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: 1,
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
"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,
|
|
||||||
},
|
|
||||||
|
|
||||||
"jobs": &ast.Function{
|
|
||||||
Function: jobs,
|
|
||||||
Name: "list jobs",
|
|
||||||
TimesCalled: 0,
|
|
||||||
Args: 0,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return stdlib
|
return stdlib
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
/* 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 (
|
|
||||||
"fmt"
|
|
||||||
"gitlab.com/whom/shs/ast"
|
|
||||||
"gitlab.com/whom/shs/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func concat(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|
||||||
var res string
|
|
||||||
for i := in; i != nil; i = i.Next {
|
|
||||||
if i.Tag == ast.LIST {
|
|
||||||
log.Log(log.ERR, "Not concatenating list", "conc")
|
|
||||||
log.Log(log.DEBUG, "Try using the expand operator (...)", "conc")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
res += i.Inner.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.Token{Tag: ast.STRING, Inner: res}
|
|
||||||
}
|
|
||||||
|
|
||||||
func print_str(in *ast.Token, vt ast.VarTable, ft ast.FuncTable) *ast.Token {
|
|
||||||
fmt.Println(in)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue