new eval.go

This commit is contained in:
Aidan 2020-06-29 00:06:53 -07:00
parent 89d6a1013b
commit 2a2e5b4527
No known key found for this signature in database
GPG key ID: 327711E983899316
15 changed files with 382 additions and 1215 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -33,7 +33,7 @@ func expand(input *ast.Token, vars ast.VarTable, funcs ast.FuncTable) *ast.Token
return input
}
return input.Inner.(*ast.Token)
return input.Expand()
}
/* 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
if input.Tag != ast.LIST {
// TODO: position??
return input
r := &ast.Token{Tag: ast.LIST}
r.Direct(input)
return r
}
// deref inner first
i := src.Inner.(*ast.Token)
i := src.Expand()
iter := &i
if *iter == nil {
src.Inner = input.Next
src.Direct(input.Next)
src.Next = nil
} else {

View file

@ -39,162 +39,12 @@ func GenFuncTable() ast.FuncTable {
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{
Function: exit_shell,
Name: "exit",
TimesCalled: 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

View file

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