From ad9a9f08cfb12850a1051f8b3230bac8557e8f9e Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Mon, 5 Jun 2017 18:01:56 -0300 Subject: [PATCH 01/27] add 'var' keyword. Broke eveeeerything... Signed-off-by: Tiago Natel de Moura --- ast/node.go | 64 +++++++++++++++++++++++++++++++++++ ast/node_fmt.go | 3 ++ ast/nodetype_string.go | 4 +-- internal/sh/shell.go | 52 +++++++++++++++++++++------- internal/sh/shell_test.go | 18 +++++----- internal/sh/shell_var_test.go | 17 ++++++++++ nash.go | 2 +- parser/parse.go | 45 ++++++++++++++++++++++++ parser/parse_test.go | 25 ++++++++++++++ scanner/lex_test.go | 13 +++++++ token/token.go | 2 ++ 11 files changed, 220 insertions(+), 25 deletions(-) create mode 100644 internal/sh/shell_var_test.go diff --git a/ast/node.go b/ast/node.go index 0eb62b40..e3303dc3 100644 --- a/ast/node.go +++ b/ast/node.go @@ -239,6 +239,24 @@ type ( elseTree *Tree } + // VarAssignDeclNode is a "var" declaration to assign values + VarAssignDeclNode struct { + NodeType + token.FileInfo + egalitarian + + Assign *AssignNode + } + + // VarExecAssignDeclNode is a var declaration to assign output of fn/cmd + VarExecAssignDeclNode struct { + NodeType + token.FileInfo + egalitarian + + ExecAssign *ExecAssignNode + } + // FnArgNode represents function arguments FnArgNode struct { NodeType @@ -386,6 +404,12 @@ const ( NodeFnArg + // NodeVarAssignDecl is the type for var declaration of values + NodeVarAssignDecl + + // NodeVarExecAssignDecl + NodeVarExecAssignDecl + // NodeFnDecl is the type for function declaration NodeFnDecl @@ -1143,6 +1167,46 @@ func (a *FnArgNode) IsEqual(other Node) bool { return true } +func NewVarAssignDecl(info token.FileInfo, assignNode *AssignNode) *VarAssignDeclNode { + return &VarAssignDeclNode{ + NodeType: NodeVarAssignDecl, + Assign: assignNode, + } +} + +func (n *VarAssignDeclNode) IsEqual(other Node) bool { + if !n.equal(n, other) { + return false + } + + o, ok := other.(*VarAssignDeclNode) + if !ok { + return false + } + + return n.Assign.IsEqual(o.Assign) +} + +func NewVarExecAssignDecl(info token.FileInfo, assignNode *ExecAssignNode) *VarExecAssignDeclNode { + return &VarExecAssignDeclNode{ + NodeType: NodeVarExecAssignDecl, + ExecAssign: assignNode, + } +} + +func (n *VarExecAssignDeclNode) IsEqual(other Node) bool { + if !n.equal(n, other) { + return false + } + + o, ok := other.(*VarExecAssignDeclNode) + if !ok { + return false + } + + return n.ExecAssign.IsEqual(o.ExecAssign) +} + // NewFnDeclNode creates a new function declaration func NewFnDeclNode(info token.FileInfo, name string) *FnDeclNode { return &FnDeclNode{ diff --git a/ast/node_fmt.go b/ast/node_fmt.go index 7c735130..66395794 100644 --- a/ast/node_fmt.go +++ b/ast/node_fmt.go @@ -563,6 +563,9 @@ func (n *IfNode) String() string { return ifStr } +func (n *VarAssignDeclNode) String() string { return "var " + n.Assign.String() } +func (n *VarExecAssignDeclNode) String() string { return "var " + n.ExecAssign.String() } + // String returns the string representation of function declaration func (n *FnDeclNode) String() string { fnStr := "fn" diff --git a/ast/nodetype_string.go b/ast/nodetype_string.go index 82ac6cda..54684fd2 100644 --- a/ast/nodetype_string.go +++ b/ast/nodetype_string.go @@ -4,9 +4,9 @@ package ast import "fmt" -const _NodeType_name = "NodeSetenvNodeBlockNodeNameNodeAssignNodeExecAssignNodeImportexecBeginNodeCommandNodePipeNodeRedirectNodeFnInvexecEndexpressionBeginNodeStringExprNodeIntExprNodeVarExprNodeListExprNodeIndexExprNodeConcatExprexpressionEndNodeStringNodeRforkNodeRforkFlagsNodeIfNodeCommentNodeFnArgNodeFnDeclNodeReturnNodeBindFnNodeDumpNodeFor" +const _NodeType_name = "NodeSetenvNodeBlockNodeNameNodeAssignNodeExecAssignNodeImportexecBeginNodeCommandNodePipeNodeRedirectNodeFnInvexecEndexpressionBeginNodeStringExprNodeIntExprNodeVarExprNodeListExprNodeIndexExprNodeConcatExprexpressionEndNodeStringNodeRforkNodeRforkFlagsNodeIfNodeCommentNodeFnArgNodeVarAssignDeclNodeVarExecAssignDeclNodeFnDeclNodeReturnNodeBindFnNodeDumpNodeFor" -var _NodeType_index = [...]uint16{0, 10, 19, 27, 37, 51, 61, 70, 81, 89, 101, 110, 117, 132, 146, 157, 168, 180, 193, 207, 220, 230, 239, 253, 259, 270, 279, 289, 299, 309, 317, 324} +var _NodeType_index = [...]uint16{0, 10, 19, 27, 37, 51, 61, 70, 81, 89, 101, 110, 117, 132, 146, 157, 168, 180, 193, 207, 220, 230, 239, 253, 259, 270, 279, 296, 317, 327, 337, 347, 355, 362} func (i NodeType) String() string { i -= 1 diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 5eba17e3..7109c33b 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -316,13 +316,13 @@ func (shell *Shell) SetEnviron(processEnv []string) { } } -func (shell *Shell) Getvar(name string) (sh.Obj, bool) { +func (shell *Shell) Getvar(name string, local bool) (sh.Obj, bool) { if value, ok := shell.vars[name]; ok { return value, ok } - if shell.parent != nil { - return shell.parent.Getvar(name) + if !local && shell.parent != nil { + return shell.parent.Getvar(name, false) } return nil, false @@ -579,7 +579,7 @@ func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj) error { finalObj := value if name.Index != nil { - if list, ok := shell.Getvar(name.Ident); ok { + if list, ok := shell.Getvar(name.Ident, false); ok { index, err := shell.evalIndex(name.Index) if err != nil { @@ -687,8 +687,10 @@ func (shell *Shell) executeNode(node ast.Node, builtin bool) ([]sh.Obj, error) { // ignore case ast.NodeSetenv: err = shell.executeSetenv(node.(*ast.SetenvNode)) + case ast.NodeVarAssignDecl: + err = shell.executeVarAssign(node.(*ast.VarAssignDeclNode)) case ast.NodeAssign: - err = shell.executeAssignment(node.(*ast.AssignNode)) + err = shell.executeAssignment(node.(*ast.AssignNode), false) case ast.NodeExecAssign: err = shell.executeExecAssign(node.(*ast.ExecAssignNode)) case ast.NodeCommand: @@ -1546,7 +1548,7 @@ func (shell *Shell) evalVariable(a ast.Expr) (sh.Obj, error) { vexpr := a.(*ast.VarExpr) varName := vexpr.Name - if value, ok = shell.Getvar(varName[1:]); !ok { + if value, ok = shell.Getvar(varName[1:], false); !ok { return nil, errors.NewEvalError(shell.filename, a, "Variable %s not set on shell %s", varName, shell.name) } @@ -1569,7 +1571,7 @@ func (shell *Shell) evalArgVariable(a ast.Expr) ([]sh.Obj, error) { } vexpr := a.(*ast.VarExpr) - if value, ok = shell.Getvar(vexpr.Name[1:]); !ok { + if value, ok = shell.Getvar(vexpr.Name[1:], false); !ok { return nil, errors.NewEvalError(shell.filename, a, "Variable %s not set on shell %s", vexpr.Name, shell.name) @@ -1741,7 +1743,7 @@ func (shell *Shell) executeSetenv(v *ast.SetenvNode) error { if assign != nil { switch assign.Type() { case ast.NodeAssign: - err = shell.executeAssignment(assign.(*ast.AssignNode)) + err = shell.executeAssignment(assign.(*ast.AssignNode), false) case ast.NodeExecAssign: err = shell.executeExecAssign(assign.(*ast.ExecAssignNode)) default: @@ -1755,7 +1757,7 @@ func (shell *Shell) executeSetenv(v *ast.SetenvNode) error { } } - if varValue, ok = shell.Getvar(v.Name); !ok { + if varValue, ok = shell.Getvar(v.Name, false); !ok { return fmt.Errorf("Variable '%s' not set on shell %s", v.Name, shell.name) } @@ -1921,30 +1923,56 @@ func (shell *Shell) executeExecAssign(v *ast.ExecAssignNode) error { assign) } -func (shell *Shell) executeAssignment(v *ast.AssignNode) error { +func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { + return shell.executeAssignment(v.Assign, true) +} + +func (shell *Shell) executeAssignment(v *ast.AssignNode, newDecl bool) error { if len(v.Names) != len(v.Values) { return errors.NewEvalError(shell.filename, v, "Invalid multiple assignment. Different amount of variables and values", ) } + var ( + someNotExists bool + existentVars []string + ) + for i := 0; i < len(v.Names); i++ { name := v.Names[i] value := v.Values[i] - obj, err := shell.evalExpr(value) + _, varExists := shell.Getvar(name.Ident, true) + if !varExists { + someNotExists = true + } else { + existentVars = append(existentVars, name.Ident) + } + if !newDecl && !varExists { + return errors.NewEvalError(shell.filename, + name, "Variable '%s' is not initialized. Use 'var %s = %s'", + name, name, value) + } + + obj, err := shell.evalExpr(value) if err != nil { return err } err = shell.setvar(name, obj) - if err != nil { return err } } + if newDecl && !someNotExists { + return errors.NewEvalError(shell.filename, + v, "Cannot redeclare variables: %s. At least one must be new.", + strings.Join(existentVars, ", ")) + } + return nil } diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index a9ca2085..e4b8aabb 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -722,10 +722,10 @@ func TestExecuteCd(t *testing.T) { "", "", }, { - "test cd into $var", + "test cd into $val", ` - var="/" - cd $var + val="/" + cd $val pwd`, "/\n", "", @@ -733,8 +733,8 @@ func TestExecuteCd(t *testing.T) { }, { "test error", - `var=("val1" "val2" "val3") - cd $var + `val=("val1" "val2" "val3") + cd $val pwd`, "", "", ":2:12: lvalue is not comparable: (val1 val2 val3) -> ListType.", @@ -2088,8 +2088,7 @@ func TestExecuteErrorSuppressionAll(t *testing.T) { return } - scode, ok := shell.Getvar("status") - + scode, ok := shell.Getvar("status", false) if !ok || scode.Type() != sh.StringType || scode.String() != strconv.Itoa(ENotFound) { t.Errorf("Invalid status code %s", scode.String()) return @@ -2102,8 +2101,7 @@ func TestExecuteErrorSuppressionAll(t *testing.T) { return } - scode, ok = shell.Getvar("status") - + scode, ok = shell.Getvar("status", false) if !ok || scode.Type() != sh.StringType || scode.String() != "0" { t.Errorf("Invalid status code %s", scode) return @@ -2123,7 +2121,7 @@ func TestExecuteErrorSuppressionAll(t *testing.T) { return } - scode, ok = shell.Getvar("status") + scode, ok = shell.Getvar("status", false) if !ok || scode.Type() != sh.StringType || scode.String() != "255|127" { t.Errorf("Invalid status code %s", scode) diff --git a/internal/sh/shell_var_test.go b/internal/sh/shell_var_test.go new file mode 100644 index 00000000..2f7a0695 --- /dev/null +++ b/internal/sh/shell_var_test.go @@ -0,0 +1,17 @@ +package sh + +import "testing" + +func TestVarAssign(t *testing.T) { + for _, test := range []execTestCase{ + { + desc: "simple var", + execStr: `var a = "1"; echo -n $a`, + expectedStdout: "1", + }, + } { + t.Run(test.desc, func(t *testing.T) { + testExec(t, test) + }) + } +} diff --git a/nash.go b/nash.go index 3c0e33f7..76a53c9f 100644 --- a/nash.go +++ b/nash.go @@ -182,7 +182,7 @@ func (nash *Shell) Setvar(name string, value sh.Obj) { // Getvar retrieves a variable from nash session func (nash *Shell) Getvar(name string) (sh.Obj, bool) { - return nash.interp.Getvar(name) + return nash.interp.Getvar(name, false) } func args2Nash(args []string) string { diff --git a/parser/parse.go b/parser/parse.go index 90e74e5f..5bafd9dd 100644 --- a/parser/parse.go +++ b/parser/parse.go @@ -41,6 +41,7 @@ func NewParser(name, content string) *Parser { token.For: p.parseFor, token.If: p.parseIf, token.Fn: p.parseFnDecl, + token.Var: p.parseVar, token.Return: p.parseReturn, token.Import: p.parseImport, token.SetEnv: p.parseSetenv, @@ -1073,6 +1074,50 @@ func (p *Parser) parseFnArgs() ([]*ast.FnArgNode, error) { return args, nil } +func (p *Parser) parseVar(it scanner.Token) (ast.Node, error) { + var varTok = it + + it = p.next() + next := p.peek() + + if it.Type() != token.Ident { + return nil, newParserError(it, p.name, + "Unexpected token %v. Expected IDENT", + next, + ) + } + + if !isAssignment(next.Type()) { + return nil, newParserError(next, p.name, + "Unexpected token %v. Expected '=' or ','", + next, + ) + } + + assign, err := p.parseAssignment(it) + if err != nil { + return nil, err + } + + switch assign.Type() { + case ast.NodeAssign: + return ast.NewVarAssignDecl( + varTok.FileInfo, + assign.(*ast.AssignNode), + ), nil + case ast.NodeExecAssign: + return ast.NewVarExecAssignDecl( + varTok.FileInfo, + assign.(*ast.ExecAssignNode), + ), nil + } + + return nil, newParserError(next, p.name, + "Unexpected token %v. Expected ASSIGN or EXECASSIGN", + next, + ) +} + func (p *Parser) parseFnDecl(it scanner.Token) (ast.Node, error) { var n *ast.FnDeclNode diff --git a/parser/parse_test.go b/parser/parse_test.go index 7c3698db..1f408776 100644 --- a/parser/parse_test.go +++ b/parser/parse_test.go @@ -227,6 +227,31 @@ func TestBasicAssignment(t *testing.T) { } } +func TestVarAssignment(t *testing.T) { + expected := ast.NewTree("var assignment") + ln := ast.NewBlockNode(token.NewFileInfo(1, 0)) + varAssign := ast.NewVarAssignDecl(token.NewFileInfo(1, 0), + ast.NewSingleAssignNode(token.NewFileInfo(1, 4), + ast.NewNameNode(token.NewFileInfo(1, 4), "test", nil), + ast.NewStringExpr(token.NewFileInfo(1, 12), "hello", true)), + ) + ln.Push(varAssign) + expected.Root = ln + + parserTest("var assignment", `var test = "hello"`, expected, t, true) + + for _, test := range []string{ + "var test=hello", + "var", + "var test", + "var test = false", + "var test = -1", + `var test = "1", "2"`, + } { + parserTestFail(t, test) + } +} + func TestParseMultipleAssign(t *testing.T) { one := ast.NewNameNode(token.NewFileInfo(1, 0), "one", nil) two := ast.NewNameNode(token.NewFileInfo(1, 5), "two", nil) diff --git a/scanner/lex_test.go b/scanner/lex_test.go index 56f9fc2f..04fa23e3 100644 --- a/scanner/lex_test.go +++ b/scanner/lex_test.go @@ -1905,3 +1905,16 @@ func TestLexerVarArgs(t *testing.T) { testTable("test literal expansion", `print("%s:%s:%s", ("a" "b" "c") ...)`, expected, t) } + +func TestLexerVar(t *testing.T) { + expected := []Token{ + {typ: token.Var, val: "var"}, + {typ: token.Ident, val: "a"}, + {typ: token.Assign, val: "="}, + {typ: token.String, val: "hello world"}, + {typ: token.Semicolon, val: ";"}, + {typ: token.EOF}, + } + + testTable("test simple var decl", `var a = "hello world"`, expected, t) +} diff --git a/token/token.go b/token/token.go index 5daecc7e..cfb225d0 100644 --- a/token/token.go +++ b/token/token.go @@ -66,6 +66,7 @@ const ( For Rfork Fn + Var keyword_end ) @@ -116,6 +117,7 @@ var tokens = [...]string{ For: "for", Rfork: "rfork", Fn: "fn", + Var: "var", } var keywords map[string]Token From 866f2a51720dbd4fe452f36db65b4f69e9c02815 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Tue, 6 Jun 2017 08:33:28 -0300 Subject: [PATCH 02/27] fix tests Signed-off-by: Tiago Natel de Moura --- internal/sh/builtin/append_test.go | 4 +- internal/sh/builtin/print_test.go | 8 +- internal/sh/builtin/testdata/exit.sh | 2 +- internal/sh/builtin/testdata/split.sh | 4 +- internal/sh/builtin/testdata/splitfunc.sh | 4 +- internal/sh/shell.go | 36 ++++--- internal/sh/shell_linux_test.go | 2 +- internal/sh/shell_test.go | 121 ++++++++++------------ nash.go | 2 +- 9 files changed, 94 insertions(+), 89 deletions(-) diff --git a/internal/sh/builtin/append_test.go b/internal/sh/builtin/append_test.go index dbfb5893..03a2ac61 100644 --- a/internal/sh/builtin/append_test.go +++ b/internal/sh/builtin/append_test.go @@ -63,7 +63,7 @@ func TestAppend(t *testing.T) { }, { name: "simple append", - code: `a = () + code: `var a = () a <= append($a, "hello") a <= append($a, "world") echo -n $a...`, @@ -73,7 +73,7 @@ func TestAppend(t *testing.T) { }, { name: "append is for lists", - code: `a = "something" + code: `var a = "something" a <= append($a, "other") echo -n $a...`, expectedErr: ":2:8: append expects a " + diff --git a/internal/sh/builtin/print_test.go b/internal/sh/builtin/print_test.go index 923b3a8c..e875fddf 100644 --- a/internal/sh/builtin/print_test.go +++ b/internal/sh/builtin/print_test.go @@ -26,7 +26,7 @@ func TestPrint(t *testing.T) { }, "fmtlist": { script: ` - list = ("1" "2" "3") + var list = ("1" "2" "3") print("%s:%s", "list", $list) `, output: "list:1 2 3", @@ -47,21 +47,21 @@ func TestPrint(t *testing.T) { }, "listonly": { script: ` - list = ("1" "2" "3") + var list = ("1" "2" "3") print($list) `, output: "1 2 3", }, "listoflists": { script: ` - list = (("1" "2" "3") ("4" "5" "6")) + var list = (("1" "2" "3") ("4" "5" "6")) print("%s:%s", "listoflists", $list) `, output: "listoflists:1 2 3 4 5 6", }, "listasfmt": { script: ` - list = ("%s" "%s") + var list = ("%s" "%s") print($list, "1", "2") `, output: "1 2", diff --git a/internal/sh/builtin/testdata/exit.sh b/internal/sh/builtin/testdata/exit.sh index 20f1f1cb..c13795f5 100755 --- a/internal/sh/builtin/testdata/exit.sh +++ b/internal/sh/builtin/testdata/exit.sh @@ -1,4 +1,4 @@ #!/usr/bin/env nash -status = $ARGS[1] +var status = $ARGS[1] exit($status) diff --git a/internal/sh/builtin/testdata/split.sh b/internal/sh/builtin/testdata/split.sh index 57a5fed0..78f20969 100644 --- a/internal/sh/builtin/testdata/split.sh +++ b/internal/sh/builtin/testdata/split.sh @@ -1,7 +1,7 @@ #!/usr/bin/env nash -word = $ARGS[1] -sep = $ARGS[2] +var word = $ARGS[1] +var sep = $ARGS[2] output <= split($word, $sep) for o in $output { echo $o diff --git a/internal/sh/builtin/testdata/splitfunc.sh b/internal/sh/builtin/testdata/splitfunc.sh index 2ad5660e..2d05a0f7 100644 --- a/internal/sh/builtin/testdata/splitfunc.sh +++ b/internal/sh/builtin/testdata/splitfunc.sh @@ -1,7 +1,7 @@ #!/usr/bin/env nash -word = $ARGS[1] -sep =$ARGS[2] +var word = $ARGS[1] +var sep =$ARGS[2] fn splitter(char) { if $char == $sep { diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 7109c33b..3439088a 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -28,7 +28,15 @@ const ( defPrompt = "\033[31mλ>\033[0m " ) +const ( + varNewDecl varOpt = iota + varSet + varOff +) + type ( + varOpt int + // Env is the environment map of lists Env map[string]sh.Obj Var Env @@ -690,7 +698,7 @@ func (shell *Shell) executeNode(node ast.Node, builtin bool) ([]sh.Obj, error) { case ast.NodeVarAssignDecl: err = shell.executeVarAssign(node.(*ast.VarAssignDeclNode)) case ast.NodeAssign: - err = shell.executeAssignment(node.(*ast.AssignNode), false) + err = shell.executeAssignment(node.(*ast.AssignNode), varSet) case ast.NodeExecAssign: err = shell.executeExecAssign(node.(*ast.ExecAssignNode)) case ast.NodeCommand: @@ -1743,7 +1751,7 @@ func (shell *Shell) executeSetenv(v *ast.SetenvNode) error { if assign != nil { switch assign.Type() { case ast.NodeAssign: - err = shell.executeAssignment(assign.(*ast.AssignNode), false) + err = shell.executeAssignment(assign.(*ast.AssignNode), varOff) case ast.NodeExecAssign: err = shell.executeExecAssign(assign.(*ast.ExecAssignNode)) default: @@ -1924,10 +1932,10 @@ func (shell *Shell) executeExecAssign(v *ast.ExecAssignNode) error { } func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { - return shell.executeAssignment(v.Assign, true) + return shell.executeAssignment(v.Assign, varNewDecl) } -func (shell *Shell) executeAssignment(v *ast.AssignNode, newDecl bool) error { +func (shell *Shell) executeAssignment(v *ast.AssignNode, opt varOpt) error { if len(v.Names) != len(v.Values) { return errors.NewEvalError(shell.filename, v, "Invalid multiple assignment. Different amount of variables and values", @@ -1943,17 +1951,21 @@ func (shell *Shell) executeAssignment(v *ast.AssignNode, newDecl bool) error { name := v.Names[i] value := v.Values[i] - _, varExists := shell.Getvar(name.Ident, true) - if !varExists { + _, varLocalExists := shell.Getvar(name.Ident, true) + if !varLocalExists { someNotExists = true } else { existentVars = append(existentVars, name.Ident) } - if !newDecl && !varExists { - return errors.NewEvalError(shell.filename, - name, "Variable '%s' is not initialized. Use 'var %s = %s'", - name, name, value) + if opt == varSet && !varLocalExists { + // look into the parent scopes + _, varParentExists := shell.Getvar(name.Ident, false) + if !varParentExists { + return errors.NewEvalError(shell.filename, + name, "Variable '%s' is not initialized. Use 'var %s = %s'", + name, name, value) + } } obj, err := shell.evalExpr(value) @@ -1967,9 +1979,9 @@ func (shell *Shell) executeAssignment(v *ast.AssignNode, newDecl bool) error { } } - if newDecl && !someNotExists { + if opt == varNewDecl && !someNotExists { return errors.NewEvalError(shell.filename, - v, "Cannot redeclare variables: %s. At least one must be new.", + v, "Cannot redeclare variables in current block: %s. At least one must be new.", strings.Join(existentVars, ", ")) } diff --git a/internal/sh/shell_linux_test.go b/internal/sh/shell_linux_test.go index f216e8d6..2cf133fb 100644 --- a/internal/sh/shell_linux_test.go +++ b/internal/sh/shell_linux_test.go @@ -92,7 +92,7 @@ func TestExecuteRforkEnvVars(t *testing.T) { sh.SetNashdPath(nashdPath) - err = sh.Exec("test env", `abra = "cadabra" + err = sh.Exec("test env", `var abra = "cadabra" setenv abra rfork up { echo $abra diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index e4b8aabb..adebdd5a 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -215,27 +215,27 @@ func TestExecuteAssignment(t *testing.T) { for _, test := range []execTestCase{ { // wrong assignment "wrong assignment", - `name=i4k`, + `var name=i4k`, "", "", - "wrong assignment:1:5: Unexpected token IDENT. Expecting VARIABLE, STRING or (", + "wrong assignment:1:9: Unexpected token IDENT. Expecting VARIABLE, STRING or (", }, { "assignment", - `name="i4k" + `var name="i4k" echo $name`, "i4k\n", "", "", }, { "list assignment", - `name=(honda civic) + `var name=(honda civic) echo -n $name`, "honda civic", "", "", }, { "list of lists", - `l = ( + `var l = ( (name Archlinux) (arch amd64) (kernel 4.7.1) @@ -252,7 +252,7 @@ kernel 4.7.1`, }, { "list assignment", - `l = (0 1 2 3) + `var l = (0 1 2 3) l[0] = "666" echo -n $l`, `666 1 2 3`, @@ -261,8 +261,8 @@ kernel 4.7.1`, }, { "list assignment", - `l = (0 1 2 3) - a = "2" + `var l = (0 1 2 3) + var a = "2" l[$a] = "666" echo -n $l`, `0 1 666 3`, @@ -278,7 +278,7 @@ func TestExecuteMultipleAssignment(t *testing.T) { for _, test := range []execTestCase{ { desc: "multiple assignment", - execStr: `_1, _2 = "1", "2" + execStr: `var _1, _2 = "1", "2" echo -n $_1 $_2`, expectedStdout: "1 2", expectedStderr: "", @@ -286,7 +286,7 @@ func TestExecuteMultipleAssignment(t *testing.T) { }, { desc: "multiple assignment", - execStr: `_1, _2, _3 = "1", "2", "3" + execStr: `var _1, _2, _3 = "1", "2", "3" echo -n $_1 $_2 $_3`, expectedStdout: "1 2 3", expectedStderr: "", @@ -294,7 +294,7 @@ func TestExecuteMultipleAssignment(t *testing.T) { }, { desc: "multiple assignment", - execStr: `_1, _2 = (), () + execStr: `var _1, _2 = (), () echo -n $_1 $_2`, expectedStdout: "", expectedStderr: "", @@ -302,7 +302,7 @@ func TestExecuteMultipleAssignment(t *testing.T) { }, { desc: "multiple assignment", - execStr: `_1, _2 = (1 2 3 4 5), (6 7 8 9 10) + execStr: `var _1, _2 = (1 2 3 4 5), (6 7 8 9 10) echo -n $_1 $_2`, expectedStdout: "1 2 3 4 5 6 7 8 9 10", expectedStderr: "", @@ -310,7 +310,7 @@ func TestExecuteMultipleAssignment(t *testing.T) { }, { desc: "multiple assignment", - execStr: `_1, _2, _3, _4, _5, _6, _7, _8, _9, _10 = "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" + execStr: `var _1, _2, _3, _4, _5, _6, _7, _8, _9, _10 = "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" echo -n $_1 $_2 $_3 $_4 $_5 $_6 $_7 $_8 $_9 $_10`, expectedStdout: "1 2 3 4 5 6 7 8 9 10", expectedStderr: "", @@ -318,7 +318,7 @@ func TestExecuteMultipleAssignment(t *testing.T) { }, { desc: "multiple assignment", - execStr: `_1, _2 = (a b c), "d" + execStr: `var _1, _2 = (a b c), "d" echo -n $_1 $_2`, expectedStdout: "a b c d", expectedStderr: "", @@ -328,7 +328,7 @@ func TestExecuteMultipleAssignment(t *testing.T) { desc: "multiple assignment", execStr: `fn a() { echo -n "a" } fn b() { echo -n "b" } - _a, _b = $a, $b + var _a, _b = $a, $b $_a(); $_b()`, expectedStdout: "ab", expectedStderr: "", @@ -369,7 +369,7 @@ func TestExecuteCmdAssignment(t *testing.T) { }, { "list assignment", - `l = (0 1 2 3) + `var l = (0 1 2 3) l[0] <= echo -n 666 echo -n $l`, `666 1 2 3`, @@ -378,8 +378,8 @@ func TestExecuteCmdAssignment(t *testing.T) { }, { "list assignment", - `l = (0 1 2 3) - a = "2" + `var l = (0 1 2 3) + var a = "2" l[$a] <= echo -n "666" echo -n $l`, `0 1 666 3`, @@ -425,7 +425,7 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { }, { "list assignment", - `l = (0 1 2 3) + `var l = (0 1 2 3) l[0], err <= echo -n 666 if $err == "0" { echo -n $l @@ -436,8 +436,8 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { }, { desc: "list assignment", - execStr: `l = (0 1 2 3) - a = "2" + execStr: `var l = (0 1 2 3) + var a = "2" l[$a], err <= echo -n "666" if $err == "0" { echo -n $l @@ -507,7 +507,7 @@ func TestExecuteCmdAssignmentIFSDontWork(t *testing.T) { for _, test := range []execTestCase{ { "ifs", - `IFS = (" ") + `var IFS = (" ") range <= echo 1 2 3 4 5 6 7 8 9 10 for i in $range { @@ -518,7 +518,7 @@ for i in $range { }, { "ifs", - `IFS = (";") + `var IFS = (";") range <= echo "1;2;3;4;5;6;7;8;9;10" for i in $range { @@ -529,7 +529,7 @@ for i in $range { }, { "ifs", - `IFS = (" " ";") + `var IFS = (" " ";") range <= echo "1;2;3;4;5;6 7;8;9;10" for i in $range { @@ -540,7 +540,7 @@ for i in $range { }, { "ifs", - `IFS = (" " "-") + `var IFS = (" " "-") range <= echo "1;2;3;4;5;6;7-8;9;10" for i in $range { @@ -601,7 +601,7 @@ func TestExecuteRedirection(t *testing.T) { // Test redirection to variable err = shell.Exec("redirect", ` - location = "`+path+`" + var location = "`+path+`" echo -n "hello world" > $location `) @@ -623,7 +623,7 @@ func TestExecuteRedirection(t *testing.T) { // Test redirection to concat err = shell.Exec("redirect", fmt.Sprintf(` location = "%s" -a = ".2" +var a = ".2" echo -n "hello world" > $location+$a `, path)) if err != nil { @@ -676,7 +676,7 @@ func TestExecuteSetenv(t *testing.T) { for _, test := range []execTestCase{ { "test setenv basic", - `test = "hello" + `var test = "hello" setenv test ` + nashdPath + ` -c "echo $test"`, "hello\n", "", "", @@ -724,7 +724,7 @@ func TestExecuteCd(t *testing.T) { { "test cd into $val", ` - val="/" + var val="/" cd $val pwd`, "/\n", @@ -733,7 +733,7 @@ func TestExecuteCd(t *testing.T) { }, { "test error", - `val=("val1" "val2" "val3") + `var val=("val1" "val2" "val3") cd $val pwd`, "", "", @@ -757,8 +757,7 @@ func TestExecuteImport(t *testing.T) { shell.SetNashdPath(nashdPath) shell.SetStdout(&out) - err = ioutil.WriteFile("/tmp/test.sh", []byte(`TESTE="teste"`), 0644) - + err = ioutil.WriteFile("/tmp/test.sh", []byte(`var TESTE="teste"`), 0644) if err != nil { t.Error(err) return @@ -767,7 +766,6 @@ func TestExecuteImport(t *testing.T) { err = shell.Exec("test import", `import /tmp/test.sh echo $TESTE `) - if err != nil { t.Error(err) return @@ -783,7 +781,6 @@ func TestExecuteIfEqual(t *testing.T) { var out bytes.Buffer shell, err := NewShell() - if err != nil { t.Error(err) return @@ -796,7 +793,6 @@ func TestExecuteIfEqual(t *testing.T) { if "" == "" { echo "empty string works" }`) - if err != nil { t.Error(err) return @@ -813,7 +809,6 @@ func TestExecuteIfEqual(t *testing.T) { if "i4k" == "_i4k_" { echo "do not print" }`) - if err != nil { t.Error(err) return @@ -829,7 +824,6 @@ func TestExecuteIfElse(t *testing.T) { var out bytes.Buffer shell, err := NewShell() - if err != nil { t.Error(err) return @@ -844,7 +838,6 @@ func TestExecuteIfElse(t *testing.T) { } else { echo "nop" }`) - if err != nil { t.Error(err) return @@ -983,7 +976,7 @@ echo -n $integers // Test fn scope err = shell.Exec("test fn inv", ` -OUTSIDE = "some value" +var OUTSIDE = "some value" fn getOUTSIDE() { return $OUTSIDE @@ -1005,7 +998,7 @@ echo -n $val err = shell.Exec("test fn inv", ` fn notset() { - INSIDE = "camshaft" + var INSIDE = "camshaft" } notset() @@ -1020,9 +1013,9 @@ echo -n $INSIDE out.Reset() // test variables shadow the global ones - err = shell.Exec("test shadow", `path="AAA" -fn test(path) { -echo -n $path + err = shell.Exec("test shadow", `var _path="AAA" +fn test(_path) { +echo -n $_path } test("BBB") `) @@ -1035,11 +1028,11 @@ echo -n $path out.Reset() err = shell.Exec("test shadow", ` -fn test(path) { -echo -n $path +fn test(_path) { +echo -n $_path } -path="AAA" +_path="AAA" test("BBB") `) @@ -1050,9 +1043,9 @@ path="AAA" out.Reset() err = shell.Exec("test fn list arg", ` - ids_luns = () - id = "1" - lun = "lunar" + var ids_luns = () + var id = "1" + var lun = "lunar" ids_luns <= append($ids_luns, ($id $lun)) print(len($ids_luns))`) if err != nil { @@ -1535,7 +1528,7 @@ echo -n $res`, { "ret from for", `fn test() { - values = (0 1 2 3 4 5 6 7 8 9) + var values = (0 1 2 3 4 5 6 7 8 9) for i in $values { if $i == "5" { @@ -1644,7 +1637,7 @@ func TestExecuteDump(t *testing.T) { return } - err = shell.Exec("", `TEST = "some value"`) + err = shell.Exec("", `var TEST = "some value"`) if err != nil { t.Error(err) @@ -1739,7 +1732,7 @@ func TestExecuteDumpVariable(t *testing.T) { os.RemoveAll(tempDir) }() - err = shell.Exec("", `dumpFile = "`+dumpFile+`"`) + err = shell.Exec("", `var dumpFile = "`+dumpFile+`"`) if err != nil { t.Error(err) @@ -1792,9 +1785,9 @@ func TestExecuteConcat(t *testing.T) { shell.SetStdout(&out) - err = shell.Exec("", `a = "A" -b = "B" -c = $a + $b + "C" + err = shell.Exec("", `var a = "A" +var b = "B" +var c = $a + $b + "C" echo -n $c`) if err != nil { @@ -1809,7 +1802,7 @@ echo -n $c`) out.Reset() - err = shell.Exec("concat indexed var", `tag = (Name some) + err = shell.Exec("concat indexed var", `var tag = (Name some) echo -n "Key="+$tag[0]+",Value="+$tag[1]`) if err != nil { @@ -1839,7 +1832,7 @@ func TestExecuteFor(t *testing.T) { shell.SetStdout(&out) - err = shell.Exec("simple loop", `files = (/etc/passwd /etc/shells) + err = shell.Exec("simple loop", `var files = (/etc/passwd /etc/shells) for f in $files { echo $f echo "loop" @@ -1931,7 +1924,7 @@ func TestExecuteVariableIndexing(t *testing.T) { shell.SetNashdPath(nashdPath) shell.SetStdout(&out) - err = shell.Exec("indexing", `list = ("1" "2" "3") + err = shell.Exec("indexing", `var list = ("1" "2" "3") echo -n $list[0]`) if err != nil { @@ -1949,7 +1942,7 @@ func TestExecuteVariableIndexing(t *testing.T) { out.Reset() - err = shell.Exec("indexing", `i = "0" + err = shell.Exec("indexing", `var i = "0" echo -n $list[$i]`) if err != nil { @@ -1998,7 +1991,7 @@ for i in $seq { out.Reset() - err = shell.Exec("indexing", `a = ("0") + err = shell.Exec("indexing", `var a = ("0") echo -n $list[$a[0]]`) if err != nil { @@ -2064,7 +2057,7 @@ func TestExecuteInterruptDoesNotCancelLoop(t *testing.T) { time.Sleep(time.Second * 1) - err = shell.Exec("interrupting loop", `seq = (1 2 3 4 5) + err = shell.Exec("interrupting loop", `var seq = (1 2 3 4 5) for i in $seq {}`) if err != nil { @@ -2341,7 +2334,7 @@ println("%s%s%s%s%s%s%s%s%s%s", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10 { desc: "passing list to var arg fn", execStr: `fn puts(arg...) { for a in $arg { echo $a } } - a = ("1" "2" "3" "4" "5") + var a = ("1" "2" "3" "4" "5") puts($a...)`, expectedErr: "", expectedStdout: "1\n2\n3\n4\n5\n", @@ -2350,7 +2343,7 @@ println("%s%s%s%s%s%s%s%s%s%s", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10 { desc: "passing empty list to var arg fn", execStr: `fn puts(arg...) { for a in $arg { echo $a } } - a = () + var a = () puts($a...)`, expectedErr: "", expectedStdout: "", @@ -2358,7 +2351,7 @@ println("%s%s%s%s%s%s%s%s%s%s", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10 }, { desc: "... expansion", - execStr: `args = ("plan9" "from" "outer" "space") + execStr: `var args = ("plan9" "from" "outer" "space") print("%s %s %s %s", $args...)`, expectedStdout: "plan9 from outer space", }, diff --git a/nash.go b/nash.go index 76a53c9f..320921eb 100644 --- a/nash.go +++ b/nash.go @@ -130,7 +130,7 @@ func (nash *Shell) ExecuteString(path, content string) error { // and passes as arguments to the script the given args slice. func (nash *Shell) ExecFile(path string, args ...string) error { if len(args) > 0 { - err := nash.ExecuteString("setting args", `ARGS = `+args2Nash(args)) + err := nash.ExecuteString("setting args", `var ARGS = `+args2Nash(args)) if err != nil { return fmt.Errorf("Failed to set nash arguments: %s", err.Error()) } From f8318256b99ccd6abe4358b735d6de50eff8a472 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Wed, 7 Jun 2017 22:26:00 -0300 Subject: [PATCH 03/27] fix more tests Signed-off-by: Tiago Natel de Moura --- internal/sh/builtin/format_test.go | 10 +++++----- internal/sh/builtin/len_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/sh/builtin/format_test.go b/internal/sh/builtin/format_test.go index c562ff22..084a9978 100644 --- a/internal/sh/builtin/format_test.go +++ b/internal/sh/builtin/format_test.go @@ -30,7 +30,7 @@ func TestFormat(t *testing.T) { "ncallsWithVarsRegressionTest": { script: ` fn formatstuff() { - b = "world" + var b = "world" r <= format("hello%s", $b) s <= format("hackthe%s", $b) echo $r @@ -50,7 +50,7 @@ func TestFormat(t *testing.T) { }, "fmtlist": { script: ` - list = ("1" "2" "3") + var list = ("1" "2" "3") r <= format("%s:%s", "list", $list) echo $r `, @@ -74,7 +74,7 @@ func TestFormat(t *testing.T) { }, "listonly": { script: ` - list = ("1" "2" "3") + var list = ("1" "2" "3") r <= format($list) echo $r `, @@ -82,7 +82,7 @@ func TestFormat(t *testing.T) { }, "listoflists": { script: ` - list = (("1" "2" "3") ("4" "5" "6")) + var list = (("1" "2" "3") ("4" "5" "6")) r <= format("%s:%s", "listoflists", $list) echo $r `, @@ -90,7 +90,7 @@ func TestFormat(t *testing.T) { }, "listasfmt": { script: ` - list = ("%s" "%s") + var list = ("%s" "%s") r <= format($list, "1", "2") echo $r `, diff --git a/internal/sh/builtin/len_test.go b/internal/sh/builtin/len_test.go index ed3316ed..11cf4dc9 100644 --- a/internal/sh/builtin/len_test.go +++ b/internal/sh/builtin/len_test.go @@ -21,7 +21,7 @@ func TestLen(t *testing.T) { err = sh.Exec( "test len", - `a = (1 2 3 4 5 6 7 8 9 0) + `var a = (1 2 3 4 5 6 7 8 9 0) len_a <= len($a) echo -n $len_a`, ) @@ -42,7 +42,7 @@ func TestLen(t *testing.T) { err = sh.Exec( "test len fail", - `a = "test" + `var a = "test" l <= len($a) echo -n $l `, From 20c2c4e098e5746fdd746e2570ce800563edfb38 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Wed, 7 Jun 2017 22:49:06 -0300 Subject: [PATCH 04/27] fix more and more tests Signed-off-by: Tiago Natel de Moura --- internal/sh/builtin/len_test.go | 5 ++--- internal/sh/functions_test.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/sh/builtin/len_test.go b/internal/sh/builtin/len_test.go index 11cf4dc9..be84c434 100644 --- a/internal/sh/builtin/len_test.go +++ b/internal/sh/builtin/len_test.go @@ -9,7 +9,6 @@ import ( func TestLen(t *testing.T) { sh, err := sh.NewShell() - if err != nil { t.Error(err) return @@ -42,14 +41,14 @@ func TestLen(t *testing.T) { err = sh.Exec( "test len fail", - `var a = "test" + `a = "test" l <= len($a) echo -n $l `, ) if err != nil { - t.Errorf("Must fail... Len only should work= with lists") + t.Error(err) return } diff --git a/internal/sh/functions_test.go b/internal/sh/functions_test.go index 66bfa183..9119a1e3 100644 --- a/internal/sh/functions_test.go +++ b/internal/sh/functions_test.go @@ -25,7 +25,7 @@ func TestFunctionsClosures(t *testing.T) { desc: "eachCallCreatesNewVar", execStr: ` fn func() { - a = () + var a = () fn add(elem) { a <= append($a, $elem) print("a:%s,",$a) From 2610e1d8eb1afaa93dc079a56333babe9c739e4a Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sun, 11 Jun 2017 09:42:29 -0300 Subject: [PATCH 05/27] improve var implementation Signed-off-by: Tiago Natel de Moura --- internal/sh/fncall.go | 8 +- internal/sh/shell.go | 206 ++++++++++++++++++++++++---------- internal/sh/shell_var_test.go | 25 ++++- nash.go | 4 +- 4 files changed, 179 insertions(+), 64 deletions(-) diff --git a/internal/sh/fncall.go b/internal/sh/fncall.go index 8c26fa06..53303814 100644 --- a/internal/sh/fncall.go +++ b/internal/sh/fncall.go @@ -112,7 +112,7 @@ func (fn *UserFn) SetArgs(args []sh.Obj) error { // and user supplied no argument... // then only initialize the variadic variable to // empty list - fn.subshell.Setvar(fn.argNames[0].Name, sh.NewListObj([]sh.Obj{})) + fn.subshell.Setvar(fn.argNames[0].Name, sh.NewListObj([]sh.Obj{}), true) return nil } } @@ -130,9 +130,9 @@ func (fn *UserFn) SetArgs(args []sh.Obj) error { valist = append(valist, arg) } valistarg := sh.NewListObj(valist) - fn.subshell.Setvar(argName, valistarg) + fn.subshell.Setvar(argName, valistarg, true) } else { - fn.subshell.Setvar(argName, arg) + fn.subshell.Setvar(argName, arg, true) } } @@ -142,7 +142,7 @@ func (fn *UserFn) SetArgs(args []sh.Obj) error { if !last.IsVariadic { return errors.NewError("internal error: optional arguments only for variadic parameter") } - fn.subshell.Setvar(last.Name, sh.NewListObj([]sh.Obj{})) + fn.subshell.Setvar(last.Name, sh.NewListObj([]sh.Obj{}), true) } return nil diff --git a/internal/sh/shell.go b/internal/sh/shell.go index c816e603..c51937c6 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -184,7 +184,7 @@ func (shell *Shell) initEnv(processEnv []string) error { argv := sh.NewListObj(largs) shell.Setenv("argv", argv) - shell.Setvar("argv", argv) + shell.Setvar("argv", argv, true) for _, penv := range processEnv { var value sh.Obj @@ -199,20 +199,20 @@ func (shell *Shell) initEnv(processEnv []string) error { value = sh.NewStrObj(strings.Join(p[1:], "=")) - shell.Setvar(p[0], value) shell.Setenv(p[0], value) + shell.Setvar(p[0], value, true) } } pidVal := sh.NewStrObj(strconv.Itoa(os.Getpid())) shell.Setenv("PID", pidVal) - shell.Setvar("PID", pidVal) + shell.Setvar("PID", pidVal, true) if _, ok := shell.Getenv("SHELL"); !ok { shellVal := sh.NewStrObj(nashdAutoDiscover()) shell.Setenv("SHELL", shellVal) - shell.Setvar("SHELL", shellVal) + shell.Setvar("SHELL", shellVal, true) } cwd, err := os.Getwd() @@ -223,7 +223,7 @@ func (shell *Shell) initEnv(processEnv []string) error { cwdObj := sh.NewStrObj(cwd) shell.Setenv("PWD", cwdObj) - shell.Setvar("PWD", cwdObj) + shell.Setvar("PWD", cwdObj, true) return nil } @@ -291,7 +291,7 @@ func (shell *Shell) Setenv(name string, value sh.Obj) { return } - shell.Setvar(name, value) + shell.Setvar(name, value, true) shell.env[name] = value os.Setenv(name, value.String()) @@ -307,7 +307,7 @@ func (shell *Shell) SetEnviron(processEnv []string) { if len(p) == 2 { value = sh.NewStrObj(p[1]) - shell.Setvar(p[0], value) + shell.Setvar(p[0], value, true) shell.Setenv(p[0], value) } } @@ -358,8 +358,18 @@ func (shell *Shell) Getbindfn(cmdName string) (sh.FnDef, bool) { return nil, false } -func (shell *Shell) Setvar(name string, value sh.Obj) { - shell.vars[name] = value +func (shell *Shell) Setvar(name string, value sh.Obj, local bool) bool { + _, ok := shell.vars[name] + if ok || local { + shell.vars[name] = value + return true + } + + if shell.parent != nil { + return shell.parent.Setvar(name, value, false) + } + + return false } func (shell *Shell) IsFn() bool { return shell.isFn } @@ -419,7 +429,7 @@ func (shell *Shell) String() string { func (shell *Shell) setupBuiltin() { for name, constructor := range builtin.Constructors() { fnDef := newBuiltinFnDef(name, shell, constructor) - shell.Setvar(name, sh.NewFnObj(fnDef)) + _ = shell.Setvar(name, sh.NewFnObj(fnDef), true) } } @@ -447,7 +457,7 @@ func (shell *Shell) setup() error { if shell.env["PROMPT"] == nil { pobj := sh.NewStrObj(defPrompt) shell.Setenv("PROMPT", pobj) - shell.Setvar("PROMPT", pobj) + shell.Setvar("PROMPT", pobj, true) } shell.setupBuiltin() @@ -542,7 +552,9 @@ func (shell *Shell) ExecFile(path string) error { return shell.Exec(path, string(content)) } -func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj) error { +// setVar tries to set value into variable name. It looks for the variable +// first in the current scope and then in the parents if not found. +func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj, local bool) error { finalObj := value if name.Index != nil { @@ -577,7 +589,11 @@ func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj) error { } } - shell.Setvar(name.Ident, finalObj) + if !shell.Setvar(name.Ident, finalObj, local) { + return errors.NewEvalError(shell.filename, + name, "Variable '%s' is not initialized. Use 'var %s = '", + name, name) + } return nil } @@ -657,7 +673,7 @@ func (shell *Shell) executeNode(node ast.Node) ([]sh.Obj, error) { case ast.NodeVarAssignDecl: err = shell.executeVarAssign(node.(*ast.VarAssignDeclNode)) case ast.NodeAssign: - err = shell.executeAssignment(node.(*ast.AssignNode), varSet) + err = shell.executeAssignment(node.(*ast.AssignNode)) case ast.NodeExecAssign: err = shell.executeExecAssign(node.(*ast.ExecAssignNode)) case ast.NodeCommand: @@ -694,7 +710,7 @@ func (shell *Shell) executeNode(node ast.Node) ([]sh.Obj, error) { } if status != nil { - shell.Setvar("status", status) + shell.Setvar("status", status, true) } return objs, err @@ -1705,13 +1721,25 @@ func (shell *Shell) executeSetenv(v *ast.SetenvNode) error { ok bool assign = v.Assignment() err error + envNames []string ) if assign != nil { switch assign.Type() { case ast.NodeAssign: - err = shell.executeAssignment(assign.(*ast.AssignNode), varOff) + setAssign := assign.(*ast.AssignNode) + + for i := 0; i < len(setAssign.Names); i++ { + name := setAssign.Names[i] + value := setAssign.Values[i] + err = shell.initVar(name, value) + if err != nil { + return err + } + envNames = append(envNames, name.Ident) + } case ast.NodeExecAssign: + // TODO(i4k): get all assign names to setenv after evaluated err = shell.executeExecAssign(assign.(*ast.ExecAssignNode)) default: err = errors.NewEvalError(shell.filename, @@ -1724,11 +1752,27 @@ func (shell *Shell) executeSetenv(v *ast.SetenvNode) error { } } - if varValue, ok = shell.Getvar(v.Name, false); !ok { - return fmt.Errorf("Variable '%s' not set on shell %s", v.Name, shell.name) - } + if len(envNames) == 0 { + if varValue, ok = shell.Getvar(v.Name, false); !ok { + return errors.NewEvalError(shell.filename, + v, "Variable '%s' not set on shell %s", v.Name, + shell.name, + ) + } + + shell.Setenv(v.Name, varValue) + } else { + for _, name := range envNames { + if varValue, ok = shell.Getvar(name, false); !ok { + return errors.NewEvalError(shell.filename, + v, "Variable '%s' not set on shell %s", name, + shell.name, + ) + } - shell.Setenv(v.Name, varValue) + shell.Setenv(v.Name, varValue) + } + } return nil } @@ -1807,9 +1851,8 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node) error { output, status, cmdErr := shell.execCmdOutput(cmd, false) // compatibility mode - shell.Setvar("status", status) - err := shell.setvar(assign.Names[0], sh.NewStrObj(output)) - + shell.Setvar("status", status, true) + err := shell.setvar(assign.Names[0], sh.NewStrObj(output), true) // TODO(i4k): handle 'var' if err != nil { return err } @@ -1824,15 +1867,12 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node) error { } output, status, cmdErr := shell.execCmdOutput(cmd, true) - - err := shell.setvar(assign.Names[0], sh.NewStrObj(output)) - + err := shell.setvar(assign.Names[0], sh.NewStrObj(output), true) // TODO if err != nil { return err } - err = shell.setvar(assign.Names[1], status) - + err = shell.setvar(assign.Names[1], status, true) // TODO if err != nil { return err } @@ -1867,7 +1907,8 @@ func (shell *Shell) executeExecAssignFn(v ast.Node) error { } for i := 0; i < len(assign.Names); i++ { - if err := shell.setvar(assign.Names[i], fnValues[i]); err != nil { + // TODO + if err := shell.setvar(assign.Names[i], fnValues[i], true); err != nil { return err } } @@ -1890,58 +1931,109 @@ func (shell *Shell) executeExecAssign(v *ast.ExecAssignNode) error { assign) } -func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { - return shell.executeAssignment(v.Assign, varNewDecl) +func (shell *Shell) initVar(name *ast.NameNode, value ast.Expr) error { + obj, err := shell.evalExpr(value) + if err != nil { + return err + } + return shell.setvar(name, obj, true) } -func (shell *Shell) executeAssignment(v *ast.AssignNode, opt varOpt) error { - if len(v.Names) != len(v.Values) { +func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { + assign := v.Assign + if len(assign.Names) != len(assign.Values) { return errors.NewEvalError(shell.filename, - v, "Invalid multiple assignment. Different amount of variables and values", + assign, "Invalid multiple assignment. Different amount of variables and values: %s", + assign, ) } var ( - someNotExists bool - existentVars []string - ) + initVarNames []*ast.NameNode + initVarValues []ast.Expr - for i := 0; i < len(v.Names); i++ { - name := v.Names[i] - value := v.Values[i] + setVarNames []*ast.NameNode + setVarValues []ast.Expr + ) + for i := 0; i < len(assign.Names); i++ { + name := assign.Names[i] + value := assign.Values[i] _, varLocalExists := shell.Getvar(name.Ident, true) if !varLocalExists { - someNotExists = true + initVarNames = append(initVarNames, name) + initVarValues = append(initVarValues, value) } else { - existentVars = append(existentVars, name.Ident) + setVarNames = append(setVarNames, name) + setVarValues = append(setVarValues, value) } + } - if opt == varSet && !varLocalExists { - // look into the parent scopes - _, varParentExists := shell.Getvar(name.Ident, false) - if !varParentExists { - return errors.NewEvalError(shell.filename, - name, "Variable '%s' is not initialized. Use 'var %s = %s'", - name, name, value) - } + // the 'var' keyword requires that at least one of the + // variables in the assignment do not exists. + if len(initVarNames) == 0 { + varNames := []string{} + for _, n := range setVarNames { + varNames = append(varNames, n.String()) } + return errors.NewEvalError(shell.filename, + assign, "Cannot redeclare variables (%s) in current block, at least one of them must be new", + strings.Join(varNames, ", ")) + } - obj, err := shell.evalExpr(value) + // initialize variables + // set their values in the current scope + for i := 0; i < len(initVarNames); i++ { + name := initVarNames[i] + value := initVarValues[i] + + err := shell.initVar(name, value) if err != nil { return err } + } - err = shell.setvar(name, obj) + // set the rest of the variables + // but in the scope where they reside + for i := 0; i < len(setVarNames); i++ { + name := initVarNames[i] + value := initVarValues[i] + + obj, err := shell.evalExpr(value) + if err == nil { + return err + } + + err = shell.setvar(name, obj, false) if err != nil { return err } } - if opt == varNewDecl && !someNotExists { + return nil +} + +func (shell *Shell) executeAssignment(v *ast.AssignNode) error { + if len(v.Names) != len(v.Values) { return errors.NewEvalError(shell.filename, - v, "Cannot redeclare variables in current block: %s. At least one must be new.", - strings.Join(existentVars, ", ")) + v, "Invalid multiple assignment. Different amount of variables and values: %s", + v, + ) + } + + for i := 0; i < len(v.Names); i++ { + name := v.Names[i] + value := v.Values[i] + + obj, err := shell.evalExpr(value) + if err != nil { + return err + } + + err = shell.setvar(name, obj, false) + if err != nil { + return err + } } return nil @@ -2200,7 +2292,7 @@ func (shell *Shell) executeFor(n *ast.ForNode) ([]sh.Obj, error) { objlist := obj.(*sh.ListObj) for _, val := range objlist.List() { - shell.Setvar(id, val) + shell.Setvar(id, val, true) objs, err := shell.executeTree(n.Tree(), false) @@ -2251,7 +2343,7 @@ func (shell *Shell) executeFnDecl(n *ast.FnDeclNode) error { return err } - shell.Setvar(n.Name(), sh.NewFnObj(fnDef)) + shell.Setvar(n.Name(), sh.NewFnObj(fnDef), true) shell.logf("Function %s declared on '%s'", n.Name(), shell.name) return nil } diff --git a/internal/sh/shell_var_test.go b/internal/sh/shell_var_test.go index 2f7a0695..a4f1c247 100644 --- a/internal/sh/shell_var_test.go +++ b/internal/sh/shell_var_test.go @@ -5,10 +5,33 @@ import "testing" func TestVarAssign(t *testing.T) { for _, test := range []execTestCase{ { - desc: "simple var", + desc: "simple init", execStr: `var a = "1"; echo -n $a`, expectedStdout: "1", }, + { + desc: "variable does not exists", + execStr: `a = "1"; echo -n $a`, + expectedErr: `:1:0: Variable 'a' is not initialized. Use 'var a = '`, + }, + { + desc: "variable already initialized", + execStr: `var a = "1"; var a = "2"; echo -n $a`, + expectedErr: `:1:17: Cannot redeclare variables (a) in current block, at least one of them must be new`, + }, + { + desc: "variable set", + execStr: `var a = "1"; a = "2"; echo -n $a`, + expectedStdout: "2", + }, + { + desc: "global variable set", + execStr: `var global = "1" + fn somefunc() { global = "2" } + somefunc() + echo -n $global`, + expectedStdout: "2", + }, } { t.Run(test.desc, func(t *testing.T) { testExec(t, test) diff --git a/nash.go b/nash.go index 4d05e752..923a4ccf 100644 --- a/nash.go +++ b/nash.go @@ -50,7 +50,7 @@ func (nash *Shell) SetInteractive(b bool) { func (nash *Shell) SetDotDir(path string) { obj := sh.NewStrObj(path) nash.interp.Setenv("NASHPATH", obj) - nash.interp.Setvar("NASHPATH", obj) + nash.interp.Setvar("NASHPATH", obj, true) } // DotDir returns the value of the NASHPATH environment variable @@ -183,7 +183,7 @@ func (nash *Shell) Stderr() io.Writer { return nash.interp.Stderr() } // Setvar sets or updates the variable in the nash session func (nash *Shell) Setvar(name string, value sh.Obj) { - nash.interp.Setvar(name, value) + nash.interp.Setvar(name, value, true) } // Getvar retrieves a variable from nash session From 3420eb8b67ae995b6ae7665a8ea350ced4477896 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sun, 11 Jun 2017 11:43:32 -0300 Subject: [PATCH 06/27] add 'var' to exec assign and fix tests Signed-off-by: Tiago Natel de Moura --- README.md | 12 +- internal/sh/builtin/append_test.go | 4 +- internal/sh/builtin/format_test.go | 24 ++-- internal/sh/builtin/glob_test.go | 12 +- internal/sh/builtin/len_test.go | 4 +- internal/sh/builtin/testdata/split.sh | 2 +- internal/sh/builtin/testdata/splitfunc.sh | 3 +- internal/sh/functions_test.go | 26 ++--- internal/sh/shell.go | 131 ++++++++++++---------- internal/sh/shell_test.go | 76 ++++++------- 10 files changed, 154 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index 22f28cf8..ca80b9dc 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ # nash -[![Join the chat at https://gitter.im/NeowayLabs/nash](https://badges.gitter.im/NeowayLabs/nash.svg)](https://gitter.im/NeowayLabs/nash?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GoDoc](https://godoc.org/github.com/NeowayLabs/nash?status.svg)](https://godoc.org/github.com/NeowayLabs/nash) +[![Join the chat at https://gitter.im/NeowayLabs/nash](https://badges.gitter.im/NeowayLabs/nash.svg)](https://gitter.im/NeowayLabs/nash?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GoDoc](https://godoc.org/github.com/NeowayLabs/nash?status.svg)](https://godoc.org/github.com/NeowayLabs/nash) [![Build Status](https://travis-ci.org/NeowayLabs/nash.svg?branch=master)](https://travis-ci.org/NeowayLabs/nash) [![Go Report Card](https://goreportcard.com/badge/github.com/NeowayLabs/nash)](https://goreportcard.com/report/github.com/NeowayLabs/nash) Nash is a system shell, inspired by plan9 `rc`, that makes it easy to create reliable and safe scripts taking advantages of operating systems namespaces (on linux and plan9) in an idiomatic way. @@ -94,7 +94,7 @@ supports output redirection to tcp, udp and unix network protocols. To assign command output to a variable exists the '<=' operator. See the example below: ```sh -fullpath <= realpath $path | xargs -n echo +var fullpath <= realpath $path | xargs -n echo echo $fullpath ``` The symbol '<=' redirects the stdout of the command or function invocation in the @@ -104,8 +104,8 @@ If you want the command output splited into an array, then you'll need to store it in a temporary variable and then use the builtin `split` function. ```sh -out <= find . -files <= split($out, "\n") +var out <= find . +var files <= split($out, "\n") for f in $files { echo "File: " + $f @@ -119,7 +119,7 @@ enclosing every variable with quotes before executing the command. Then the following example do the right thing: ```sh -fullname = "John Nash" +var fullname = "John Nash" ./ci-register --name $fullname --option somevalue ``` On bash you need to enclose the `$fullname` variable in quotes to avoid problems. @@ -173,7 +173,7 @@ Long commands can be split in multiple lines: λ> (aws ec2 attach-internet-gateway --internet-gateway-id $igwid --vpc-id $vpcid) -λ> instanceId <= ( +λ> var instanceId <= ( aws ec2 run-instances --image-id ami-xxxxxxxx --count 1 diff --git a/internal/sh/builtin/append_test.go b/internal/sh/builtin/append_test.go index 03a2ac61..6c46318d 100644 --- a/internal/sh/builtin/append_test.go +++ b/internal/sh/builtin/append_test.go @@ -83,7 +83,7 @@ func TestAppend(t *testing.T) { }, { name: "var args", - code: `a <= append((), "1", "2", "3", "4", "5", "6") + code: `var a <= append((), "1", "2", "3", "4", "5", "6") echo -n $a...`, expectedErr: "", expectedStdout: "1 2 3 4 5 6", @@ -91,7 +91,7 @@ func TestAppend(t *testing.T) { }, { name: "append of lists", - code: `a <= append((), (), ()) + code: `var a <= append((), (), ()) if len($a) != "2" { print("wrong") } else if len($a[0]) != "0" { diff --git a/internal/sh/builtin/format_test.go b/internal/sh/builtin/format_test.go index 084a9978..e4a7281c 100644 --- a/internal/sh/builtin/format_test.go +++ b/internal/sh/builtin/format_test.go @@ -11,7 +11,7 @@ func TestFormat(t *testing.T) { tests := map[string]formatDesc{ "textonly": { script: ` - r <= format("helloworld") + var r <= format("helloworld") echo $r `, output: "helloworld\n", @@ -19,7 +19,7 @@ func TestFormat(t *testing.T) { "ncallsRegressionTest": { script: ` fn formatstuff() { - r <= format("hello%s", "world") + var r <= format("hello%s", "world") echo $r } formatstuff() @@ -31,8 +31,8 @@ func TestFormat(t *testing.T) { script: ` fn formatstuff() { var b = "world" - r <= format("hello%s", $b) - s <= format("hackthe%s", $b) + var r <= format("hello%s", $b) + var s <= format("hackthe%s", $b) echo $r echo $s } @@ -43,7 +43,7 @@ func TestFormat(t *testing.T) { }, "fmtstring": { script: ` - r <= format("%s:%s", "hello", "world") + var r <= format("%s:%s", "hello", "world") echo $r `, output: "hello:world\n", @@ -51,7 +51,7 @@ func TestFormat(t *testing.T) { "fmtlist": { script: ` var list = ("1" "2" "3") - r <= format("%s:%s", "list", $list) + var r <= format("%s:%s", "list", $list) echo $r `, output: "list:1 2 3\n", @@ -59,7 +59,7 @@ func TestFormat(t *testing.T) { "funconly": { script: ` fn func() {} - r <= format($func) + var r <= format($func) echo $r `, output: "\n", @@ -67,7 +67,7 @@ func TestFormat(t *testing.T) { "funcfmt": { script: ` fn func() {} - r <= format("calling:%s", $func) + var r <= format("calling:%s", $func) echo $r `, output: "calling:\n", @@ -75,7 +75,7 @@ func TestFormat(t *testing.T) { "listonly": { script: ` var list = ("1" "2" "3") - r <= format($list) + var r <= format($list) echo $r `, output: "1 2 3\n", @@ -83,7 +83,7 @@ func TestFormat(t *testing.T) { "listoflists": { script: ` var list = (("1" "2" "3") ("4" "5" "6")) - r <= format("%s:%s", "listoflists", $list) + var r <= format("%s:%s", "listoflists", $list) echo $r `, output: "listoflists:1 2 3 4 5 6\n", @@ -91,14 +91,14 @@ func TestFormat(t *testing.T) { "listasfmt": { script: ` var list = ("%s" "%s") - r <= format($list, "1", "2") + var r <= format($list, "1", "2") echo $r `, output: "1 2\n", }, "invalidFmt": { script: ` - r <= format("%d%s", "invalid") + var r <= format("%d%s", "invalid") echo $r `, output: "%!d(string=invalid)%!s(MISSING)\n", diff --git a/internal/sh/builtin/glob_test.go b/internal/sh/builtin/glob_test.go index 64898125..e630c0d1 100644 --- a/internal/sh/builtin/glob_test.go +++ b/internal/sh/builtin/glob_test.go @@ -35,7 +35,7 @@ func TestGlobNoResult(t *testing.T) { pattern := dir + "/*.la" out := execSuccess(t, fmt.Sprintf(` - res <= glob("%s") + var res <= glob("%s") print($res) `, pattern)) @@ -53,7 +53,7 @@ func TestGlobOneResult(t *testing.T) { pattern := dir + "/*.go" out := execSuccess(t, fmt.Sprintf(` - res <= glob("%s") + var res <= glob("%s") print($res) `, pattern)) @@ -75,7 +75,7 @@ func TestGlobMultipleResults(t *testing.T) { pattern := dir + "/*.h" out := execSuccess(t, fmt.Sprintf(` - res <= glob("%s") + var res <= glob("%s") print($res) `, pattern)) @@ -103,7 +103,7 @@ func TestGlobMultipleResults(t *testing.T) { func TestGlobNoParamError(t *testing.T) { execFailure(t, ` - res <= glob() + var res <= glob() print($res) `) } @@ -111,14 +111,14 @@ func TestGlobNoParamError(t *testing.T) { func TestGlobWrongType(t *testing.T) { execFailure(t, ` param = ("hi") - res <= glob($param) + var res <= glob($param) print($res) `) } func TestGlobInvalidPatternError(t *testing.T) { execFailure(t, ` - res <= glob("*[.go") + var res <= glob("*[.go") print($res) `) } diff --git a/internal/sh/builtin/len_test.go b/internal/sh/builtin/len_test.go index be84c434..2cb81f7b 100644 --- a/internal/sh/builtin/len_test.go +++ b/internal/sh/builtin/len_test.go @@ -21,7 +21,7 @@ func TestLen(t *testing.T) { err = sh.Exec( "test len", `var a = (1 2 3 4 5 6 7 8 9 0) - len_a <= len($a) + var len_a <= len($a) echo -n $len_a`, ) @@ -42,7 +42,7 @@ func TestLen(t *testing.T) { err = sh.Exec( "test len fail", `a = "test" - l <= len($a) + var l <= len($a) echo -n $l `, ) diff --git a/internal/sh/builtin/testdata/split.sh b/internal/sh/builtin/testdata/split.sh index 78f20969..ae0d7d3c 100644 --- a/internal/sh/builtin/testdata/split.sh +++ b/internal/sh/builtin/testdata/split.sh @@ -2,7 +2,7 @@ var word = $ARGS[1] var sep = $ARGS[2] -output <= split($word, $sep) +var output <= split($word, $sep) for o in $output { echo $o } diff --git a/internal/sh/builtin/testdata/splitfunc.sh b/internal/sh/builtin/testdata/splitfunc.sh index 2d05a0f7..6a5b473a 100644 --- a/internal/sh/builtin/testdata/splitfunc.sh +++ b/internal/sh/builtin/testdata/splitfunc.sh @@ -10,8 +10,7 @@ fn splitter(char) { return "1" } -output <= split($word, $splitter) - +var output <= split($word, $splitter) for o in $output { echo $o } diff --git a/internal/sh/functions_test.go b/internal/sh/functions_test.go index 9119a1e3..57020097 100644 --- a/internal/sh/functions_test.go +++ b/internal/sh/functions_test.go @@ -14,8 +14,8 @@ func TestFunctionsClosures(t *testing.T) { return $closure } - x <= func("1") - y <= func("2") + var x <= func("1") + var y <= func("2") $x() $y() `, @@ -33,27 +33,27 @@ func TestFunctionsClosures(t *testing.T) { return $add } - add <= func() + var add <= func() $add("1") $add("3") $add("5") `, - expectedStdout: "a:1,a:3,a:5,", + expectedStdout: "a:1,a:1 3,a:1 3 5,", }, { desc: "adder example", execStr: ` fn makeAdder(x) { fn add(y) { - ret <= expr $x "+" $y + var ret <= expr $x "+" $y return $ret } return $add } -add1 <= makeAdder("1") -add5 <= makeAdder("5") -add1000 <= makeAdder("1000") +var add1 <= makeAdder("1") +var add5 <= makeAdder("5") +var add1000 <= makeAdder("1000") print("%s\n", add5("5")) print("%s\n", add5("10")) @@ -82,9 +82,9 @@ print("%s\n", add1("10")) return $log } -info <= getlogger("[info] ") -error <= getlogger("[error] ") -warn <= getlogger("[warn] ") +var info <= getlogger("[info] ") +var error <= getlogger("[error] ") +var warn <= getlogger("[warn] ") $info("nuke initialized successfully") $warn("temperature above anormal circunstances: %s°", "870") @@ -130,8 +130,8 @@ func TestFunctionsStateless(t *testing.T) { { desc: "functions have no shared state", execStr: `fn iter(first, last, func) { - sequence <= seq $first $last - range <= split($sequence, "\n") + var sequence <= seq $first $last + var range <= split($sequence, "\n") for i in $range { $func($i) } diff --git a/internal/sh/shell.go b/internal/sh/shell.go index c51937c6..36ee5147 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -73,6 +73,8 @@ type ( *sync.Mutex } + onSetvarSuccess func(name *ast.NameNode, val sh.Obj) error + errIgnore struct { *errors.NashError } @@ -672,10 +674,12 @@ func (shell *Shell) executeNode(node ast.Node) ([]sh.Obj, error) { err = shell.executeSetenv(node.(*ast.SetenvNode)) case ast.NodeVarAssignDecl: err = shell.executeVarAssign(node.(*ast.VarAssignDeclNode)) + case ast.NodeVarExecAssignDecl: + err = shell.executeVarExecAssign(node.(*ast.VarExecAssignDeclNode)) case ast.NodeAssign: err = shell.executeAssignment(node.(*ast.AssignNode)) case ast.NodeExecAssign: - err = shell.executeExecAssign(node.(*ast.ExecAssignNode)) + err = shell.executeExecAssign(node.(*ast.ExecAssignNode), false) case ast.NodeCommand: status, err = shell.executeCommand(node.(*ast.CommandNode)) case ast.NodePipe: @@ -1715,65 +1719,74 @@ func (shell *Shell) evalExpr(expr ast.Expr) (sh.Obj, error) { expr, "Failed to eval expression: %+v", expr) } +func (shell *Shell) executeSetenvAssign(assign *ast.AssignNode) error { + for i := 0; i < len(assign.Names); i++ { + name := assign.Names[i] + value := assign.Values[i] + err := shell.initVar(name, value) + if err != nil { + return err + } + obj, ok := shell.Getvar(name.Ident, true) + if !ok { + return errors.NewEvalError(shell.filename, + assign, + "internal error: Setenv not setting local variable '%s'", + name.Ident, + ) + } + shell.Setenv(name.Ident, obj) + } + return nil +} + +func (shell *Shell) executeSetenvExec(assign *ast.ExecAssignNode) error { + err := shell.executeExecAssign(assign, true) + if err != nil { + return err + } + for i := 0; i < len(assign.Names); i++ { + name := assign.Names[i] + obj, ok := shell.Getvar(name.Ident, true) + if !ok { + return errors.NewEvalError(shell.filename, + assign, + "internal error: Setenv not setting local variable '%s'", + name.Ident, + ) + } + shell.Setenv(name.Ident, obj) + } + return nil +} + func (shell *Shell) executeSetenv(v *ast.SetenvNode) error { var ( varValue sh.Obj ok bool assign = v.Assignment() - err error - envNames []string ) if assign != nil { switch assign.Type() { case ast.NodeAssign: - setAssign := assign.(*ast.AssignNode) - - for i := 0; i < len(setAssign.Names); i++ { - name := setAssign.Names[i] - value := setAssign.Values[i] - err = shell.initVar(name, value) - if err != nil { - return err - } - envNames = append(envNames, name.Ident) - } + return shell.executeSetenvAssign(assign.(*ast.AssignNode)) case ast.NodeExecAssign: - // TODO(i4k): get all assign names to setenv after evaluated - err = shell.executeExecAssign(assign.(*ast.ExecAssignNode)) - default: - err = errors.NewEvalError(shell.filename, - v, "Failed to eval setenv, invalid assignment type: %+v", - assign) - } - - if err != nil { - return err + return shell.executeSetenvExec(assign.(*ast.ExecAssignNode)) } + return errors.NewEvalError(shell.filename, + v, "Failed to eval setenv, invalid assignment type: %+v", + assign) } - if len(envNames) == 0 { - if varValue, ok = shell.Getvar(v.Name, false); !ok { - return errors.NewEvalError(shell.filename, - v, "Variable '%s' not set on shell %s", v.Name, - shell.name, - ) - } - - shell.Setenv(v.Name, varValue) - } else { - for _, name := range envNames { - if varValue, ok = shell.Getvar(name, false); !ok { - return errors.NewEvalError(shell.filename, - v, "Variable '%s' not set on shell %s", name, - shell.name, - ) - } - - shell.Setenv(v.Name, varValue) - } + varValue, ok = shell.Getvar(v.Name, false) + if !ok { + return errors.NewEvalError(shell.filename, + v, "Variable '%s' not set on shell %s", v.Name, + shell.name, + ) } - + shell.Setenv(v.Name, varValue) return nil } @@ -1843,7 +1856,7 @@ func (shell *Shell) execCmdOutput(cmd ast.Node, ignoreError bool) (string, sh.Ob return string(output), status, err } -func (shell *Shell) executeExecAssignCmd(v ast.Node) error { +func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { assign := v.(*ast.ExecAssignNode) cmd := assign.Command() @@ -1852,7 +1865,7 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node) error { // compatibility mode shell.Setvar("status", status, true) - err := shell.setvar(assign.Names[0], sh.NewStrObj(output), true) // TODO(i4k): handle 'var' + err := shell.setvar(assign.Names[0], sh.NewStrObj(output), local) if err != nil { return err } @@ -1867,12 +1880,12 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node) error { } output, status, cmdErr := shell.execCmdOutput(cmd, true) - err := shell.setvar(assign.Names[0], sh.NewStrObj(output), true) // TODO + err := shell.setvar(assign.Names[0], sh.NewStrObj(output), local) if err != nil { return err } - err = shell.setvar(assign.Names[1], status, true) // TODO + err = shell.setvar(assign.Names[1], status, true) // status is always local if err != nil { return err } @@ -1880,15 +1893,13 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node) error { return cmdErr } -func (shell *Shell) executeExecAssignFn(v ast.Node) error { +func (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode, local bool) error { var ( err error fnValues []sh.Obj ) - assign := v.(*ast.ExecAssignNode) cmd := assign.Command() - if cmd.Type() != ast.NodeFnInv { return errors.NewEvalError(shell.filename, cmd, "Invalid node type (%v). Expected function call", @@ -1902,13 +1913,13 @@ func (shell *Shell) executeExecAssignFn(v ast.Node) error { if len(fnValues) != len(assign.Names) { return errors.NewEvalError(shell.filename, - v, "Functions returns %d objects, but statement expects %d", + assign, "Functions returns %d objects, but statement expects %d", len(fnValues), len(assign.Names)) } for i := 0; i < len(assign.Names); i++ { - // TODO - if err := shell.setvar(assign.Names[i], fnValues[i], true); err != nil { + err := shell.setvar(assign.Names[i], fnValues[i], local) + if err != nil { return err } } @@ -1916,14 +1927,14 @@ func (shell *Shell) executeExecAssignFn(v ast.Node) error { return nil } -func (shell *Shell) executeExecAssign(v *ast.ExecAssignNode) error { +func (shell *Shell) executeExecAssign(v *ast.ExecAssignNode, local bool) error { assign := v.Command() if assign.Type() == ast.NodeFnInv { - return shell.executeExecAssignFn(v) + return shell.executeExecAssignFn(v, local) } else if assign.Type() == ast.NodeCommand || assign.Type() == ast.NodePipe { - return shell.executeExecAssignCmd(v) + return shell.executeExecAssignCmd(v, local) } return errors.NewEvalError(shell.filename, @@ -2013,6 +2024,10 @@ func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { return nil } +func (shell *Shell) executeVarExecAssign(v *ast.VarExecAssignDeclNode) error { + return shell.executeExecAssign(v.ExecAssign, true) +} + func (shell *Shell) executeAssignment(v *ast.AssignNode) error { if len(v.Names) != len(v.Values) { return errors.NewEvalError(shell.filename, diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 93bc5e87..7f856107 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -196,8 +196,8 @@ func TestExecuteCommand(t *testing.T) { }, { desc: "local command", - execStr: `echopath <= which echo -path <= dirname $echopath + execStr: `var echopath <= which echo +var path <= dirname $echopath chdir($path) ./echo -n hello`, expectedStdout: "hello", @@ -341,29 +341,29 @@ func TestExecuteCmdAssignment(t *testing.T) { for _, test := range []execTestCase{ { "cmd assignment", - `name <= echo -n i4k + `var name <= echo -n i4k echo -n $name`, "i4k", "", "", }, { "list cmd assignment", - `name <= echo "honda civic" + `var name <= echo "honda civic" echo -n $name`, "honda civic", "", "", }, { "wrong cmd assignment", - `name <= ""`, - "", "", "wrong cmd assignment:1:9: Invalid token STRING. Expected command or function invocation", + `var name <= ""`, + "", "", "wrong cmd assignment:1:13: Invalid token STRING. Expected command or function invocation", }, { "fn must return value", `fn e() {} - v <= e()`, + var v <= e()`, "", "", - ":2:25: Functions returns 0 objects, but statement expects 1", + ":2:29: Functions returns 0 objects, but statement expects 1", }, { "list assignment", @@ -393,7 +393,7 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { for _, test := range []execTestCase{ { "cmd assignment", - `name, err <= echo -n i4k + `var name, err <= echo -n i4k if $err == "0" { echo -n $name }`, @@ -402,7 +402,7 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { }, { "list cmd assignment", - `name, err2 <= echo "honda civic" + `var name, err2 <= echo "honda civic" if $err2 == "0" { echo -n $name }`, @@ -410,21 +410,21 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { }, { "wrong cmd assignment", - `name, err <= ""`, - "", "", "wrong cmd assignment:1:14: Invalid token STRING. Expected command or function invocation", + `var name, err <= ""`, + "", "", "wrong cmd assignment:1:18: Invalid token STRING. Expected command or function invocation", }, { "fn must return value", `fn e() {} - v, err <= e()`, + var v, err <= e()`, "", "", - ":2:25: Functions returns 0 objects, but statement expects 2", + ":2:29: Functions returns 0 objects, but statement expects 2", }, { "list assignment", `var l = (0 1 2 3) - l[0], err <= echo -n 666 + var l[0], err <= echo -n 666 if $err == "0" { echo -n $l }`, @@ -436,7 +436,7 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { desc: "list assignment", execStr: `var l = (0 1 2 3) var a = "2" - l[$a], err <= echo -n "666" + var l[$a], err <= echo -n "666" if $err == "0" { echo -n $l }`, @@ -446,14 +446,14 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { }, { desc: "cmd assignment works with 1 or 2 variables", - execStr: "out, err1, err2 <= echo something", + execStr: "var out, err1, err2 <= echo something", expectedStdout: "", expectedStderr: "", - expectedErr: ":1:0: multiple assignment of commands requires two variable names, but got 3", + expectedErr: ":1:4: multiple assignment of commands requires two variable names, but got 3", }, { desc: "ignore error", - execStr: `out, _ <= cat /file-not-found/test >[2=] + execStr: `var out, _ <= cat /file-not-found/test >[2=] echo -n $out`, expectedStdout: "", expectedStderr: "", @@ -461,7 +461,7 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { }, { desc: "exec without '-' and getting status still fails", - execStr: `out <= cat /file-not-found/test >[2=] + execStr: `var out <= cat /file-not-found/test >[2=] echo $out`, expectedStdout: "", expectedStderr: "", @@ -469,7 +469,7 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { }, { desc: "check status", - execStr: `out, status <= cat /file-not-found/test >[2=] + execStr: `var out, status <= cat /file-not-found/test >[2=] if $status == "0" { echo -n "must fail.. sniff" } else if $status == "1" { @@ -488,7 +488,7 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { return "1", "2" } - a, b <= fun() + var a, b <= fun() echo -n $a $b`, expectedStdout: "1 2", expectedStderr: "", @@ -506,7 +506,7 @@ func TestExecuteCmdAssignmentIFSDontWork(t *testing.T) { { "ifs", `var IFS = (" ") -range <= echo 1 2 3 4 5 6 7 8 9 10 +var range <= echo 1 2 3 4 5 6 7 8 9 10 for i in $range { echo "i = " + $i @@ -517,7 +517,7 @@ for i in $range { { "ifs", `var IFS = (";") -range <= echo "1;2;3;4;5;6;7;8;9;10" +var range <= echo "1;2;3;4;5;6;7;8;9;10" for i in $range { echo "i = " + $i @@ -528,7 +528,7 @@ for i in $range { { "ifs", `var IFS = (" " ";") -range <= echo "1;2;3;4;5;6 7;8;9;10" +var range <= echo "1;2;3;4;5;6 7;8;9;10" for i in $range { echo "i = " + $i @@ -539,7 +539,7 @@ for i in $range { { "ifs", `var IFS = (" " "-") -range <= echo "1;2;3;4;5;6;7-8;9;10" +var range <= echo "1;2;3;4;5;6;7-8;9;10" for i in $range { echo "i = " + $i @@ -956,7 +956,7 @@ fn getints() { return ("1" "2" "3" "4" "5" "6" "7" "8" "9" "0") } -integers <= getints() +var integers <= getints() echo -n $integers `) @@ -980,7 +980,7 @@ fn getOUTSIDE() { return $OUTSIDE } -val <= getOUTSIDE() +var val <= getOUTSIDE() echo -n $val `) @@ -1044,7 +1044,7 @@ _path="AAA" var ids_luns = () var id = "1" var lun = "lunar" - ids_luns <= append($ids_luns, ($id $lun)) + var ids_luns <= append($ids_luns, ($id $lun)) print(len($ids_luns))`) if err != nil { t.Error(err) @@ -1105,12 +1105,12 @@ fn _getints() { } fn getints() { - values <= _getints() + var values <= _getints() return $values } -integers <= getints() +var integers <= getints() echo -n $integers `) @@ -1519,7 +1519,7 @@ test()`, return "0" } -res <= test() +var res <= test() echo -n $res`, "1", "", "", }, @@ -1536,7 +1536,7 @@ echo -n $res`, return "0" } -a <= test() +var a <= test() echo -n $a`, "5", "", "", }, @@ -1552,7 +1552,7 @@ echo -n $a`, # never happen return "bleh" } -a <= test() +var a <= test() echo -n $a`, "1", "", "", }, @@ -1560,7 +1560,7 @@ echo -n $a`, "test returning funcall", `fn a() { return "1" } fn b() { return a() } - c <= b() + var c <= b() echo -n $c`, "1", "", "", }, @@ -1958,8 +1958,8 @@ echo -n $list[$i]`) out.Reset() - err = shell.Exec("indexing", `tmp <= seq 0 2 -seq <= split($tmp, "\n") + err = shell.Exec("indexing", `var tmp <= seq 0 2 +var seq <= split($tmp, "\n") for i in $seq { echo -n $list[$i] @@ -2216,7 +2216,7 @@ func TestExecuteMultilineCmdAssign(t *testing.T) { shell.SetStdout(&out) - err = shell.Exec("test", `val <= (echo -n + err = shell.Exec("test", `var val <= (echo -n hello world) From 8407ab8bef74aa00a4221f76bf270268bf847343 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sun, 11 Jun 2017 11:48:34 -0300 Subject: [PATCH 07/27] update reference docs Signed-off-by: Tiago Natel de Moura --- docs/reference.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index 4e1b2b3a..eacc73b1 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -29,7 +29,7 @@ when it is executed, like: ```nash echo echo "acessing individual parameter" -somearg = $ARGS[0] +var somearg = $ARGS[0] echo $somearg echo ``` @@ -42,8 +42,8 @@ To branch you can use **if** statement, it requires a boolean expression, like the comparison operator: ```nash -a = "nash" -echo $a +var a = "nash" +echo -n $a if $a == "nash" { a = "rocks" } From 7a400452ac3a4581e4e810d4f500bfb98c2a6813 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Tue, 13 Jun 2017 17:47:26 -0300 Subject: [PATCH 08/27] style Signed-off-by: Tiago Natel de Moura --- parser/parse.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/parser/parse.go b/parser/parse.go index 5bafd9dd..f7719bf1 100644 --- a/parser/parse.go +++ b/parser/parse.go @@ -1144,7 +1144,6 @@ func (p *Parser) parseFnDecl(it scanner.Token) (ast.Node, error) { } it = p.next() - if it.Type() != token.LBrace { return nil, newParserError(it, p.name, "Unexpected token %v. Expected '{'", it) @@ -1153,7 +1152,6 @@ func (p *Parser) parseFnDecl(it scanner.Token) (ast.Node, error) { p.openblocks++ tree := ast.NewTree(fmt.Sprintf("fn %s body", n.Name())) - r, err := p.parseBlock(it.Line(), it.Column()) if err != nil { @@ -1161,9 +1159,7 @@ func (p *Parser) parseFnDecl(it scanner.Token) (ast.Node, error) { } tree.Root = r - n.SetTree(tree) - return n, nil } From bde47a0908b10121dff80a9a5da317845c4d5fbe Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Fri, 16 Jun 2017 14:23:27 -0300 Subject: [PATCH 09/27] add single, double and triple assignment for commands Signed-off-by: Tiago Natel de Moura --- internal/sh/builtin/exit_test.go | 3 ++ internal/sh/builtin/testdata/exit.sh | 3 +- internal/sh/shell.go | 58 ++++++++++++++++++---------- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/internal/sh/builtin/exit_test.go b/internal/sh/builtin/exit_test.go index 108f1664..d0534f77 100644 --- a/internal/sh/builtin/exit_test.go +++ b/internal/sh/builtin/exit_test.go @@ -1,6 +1,7 @@ package builtin_test import ( + "os" "os/exec" "testing" ) @@ -41,6 +42,8 @@ func TestExit(t *testing.T) { for name, desc := range tests { t.Run(name, func(t *testing.T) { cmd := exec.Command(projectnash, desc.script, desc.status) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr // to know why scripts were failing err := cmd.Run() if err == nil { if desc.status == "0" { diff --git a/internal/sh/builtin/testdata/exit.sh b/internal/sh/builtin/testdata/exit.sh index c13795f5..930821c9 100755 --- a/internal/sh/builtin/testdata/exit.sh +++ b/internal/sh/builtin/testdata/exit.sh @@ -1,4 +1,3 @@ #!/usr/bin/env nash -var status = $ARGS[1] -exit($status) +exit($ARGS[1]) diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 36ee5147..9e438095 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -1375,7 +1375,6 @@ func (shell *Shell) executeCommand(c *ast.CommandNode) (sh.Obj, error) { cmdError: statusObj := sh.NewStrObj(getErrStatus(err, status)) - if ignoreError { return statusObj, newErrIgnore(err.Error()) } @@ -1829,9 +1828,7 @@ func (shell *Shell) execCmdOutput(cmd ast.Node, ignoreError bool) (string, sh.Ob } bkStdout := shell.stdout - shell.SetStdout(&varOut) - defer shell.SetStdout(bkStdout) if cmd.Type() == ast.NodeCommand { @@ -1841,7 +1838,6 @@ func (shell *Shell) execCmdOutput(cmd ast.Node, ignoreError bool) (string, sh.Ob } output := varOut.Bytes() - if len(output) > 0 && output[len(output)-1] == '\n' { // remove the trailing new line // Why? because it's what user wants in 99.99% of times... @@ -1860,17 +1856,50 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { assign := v.(*ast.ExecAssignNode) cmd := assign.Command() + // Only getting command output + // In this case the script must abort in case of errors if len(assign.Names) == 1 { - output, status, cmdErr := shell.execCmdOutput(cmd, false) + outstr, errstr, status, cmdErr := shell.execCmdOutput(cmd, false) + shell.stderr.Write(errstr) // flush stderr + + err := shell.setvar(assign.Names[0], sh.NewStrObj(outstr), local) + + if cmdErr != nil { + return cmdErr + } + + if status != 0 { + return errors.NewEvalError(shell.filename, v, + "exit status: %d", status) + } + if err != nil { + return err + } + + return nil + } + + // Only getting stdout and exit status + if len(assign.Names) == 2 { + outstr, errstr, status, cmdErr := shell.execCmdOutput(cmd, true) + shell.stderr.Write(errstr) // flush stderr - // compatibility mode - shell.Setvar("status", status, true) err := shell.setvar(assign.Names[0], sh.NewStrObj(output), local) + + if cmdErr != nil { + return cmdErr + } + + if err != nil { + return err + } + + err = shell.setvar(assign.Names[1], sh.NewStrObj(status), false) if err != nil { return err } - return cmdErr + return nil } if len(assign.Names) != 2 { @@ -1879,18 +1908,6 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { len(assign.Names)) } - output, status, cmdErr := shell.execCmdOutput(cmd, true) - err := shell.setvar(assign.Names[0], sh.NewStrObj(output), local) - if err != nil { - return err - } - - err = shell.setvar(assign.Names[1], status, true) // status is always local - if err != nil { - return err - } - - return cmdErr } func (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode, local bool) error { @@ -2167,7 +2184,6 @@ func (shell *Shell) executeFnInv(n *ast.FnInvNode) ([]sh.Obj, error) { } fn := fnDef.Build() - args, err := shell.evalArgExprs(n.Args()) if err != nil { return nil, err From c5de5bba9ef2bdb1d3e45cb5ee69a7e943023835 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Fri, 16 Jun 2017 14:41:36 -0300 Subject: [PATCH 10/27] add single, double and triple assignment for commands Signed-off-by: Tiago Natel de Moura --- internal/sh/shell.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 9e438095..a0c48567 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -1856,12 +1856,12 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { assign := v.(*ast.ExecAssignNode) cmd := assign.Command() + outstr, errstr, status, cmdErr := shell.execCmdOutput(cmd, false) + // Only getting command output // In this case the script must abort in case of errors if len(assign.Names) == 1 { - outstr, errstr, status, cmdErr := shell.execCmdOutput(cmd, false) shell.stderr.Write(errstr) // flush stderr - err := shell.setvar(assign.Names[0], sh.NewStrObj(outstr), local) if cmdErr != nil { @@ -1881,9 +1881,7 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { // Only getting stdout and exit status if len(assign.Names) == 2 { - outstr, errstr, status, cmdErr := shell.execCmdOutput(cmd, true) shell.stderr.Write(errstr) // flush stderr - err := shell.setvar(assign.Names[0], sh.NewStrObj(output), local) if cmdErr != nil { @@ -1902,12 +1900,23 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { return nil } - if len(assign.Names) != 2 { - return errors.NewEvalError(shell.filename, - v, "multiple assignment of commands requires two variable names, but got %d", - len(assign.Names)) + if len(assign.Names) == 3 { + err1 := shell.setvar(assign.Names[0], sh.NewStrObj(outstr), local) + err2 := shell.setvar(assign.Names[1], sh.NewStrObj(errstr), local) + err3 := shell.setvar(assign.Names[2], sh.NewStrObj(status), local) + errs := []error{cmdErr, err1, err2, err3} + + for _, e := range errs { + if e != nil { + return e + } + } + return nil } + return errors.NewEvalError(shell.filename, + v, "multiple assignment of commands requires between one and three variable names, but got %d", + len(assign.Names)) } func (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode, local bool) error { From 85a9faeb7faa55cf7637ec68ebd4efaf0d037634 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Fri, 16 Jun 2017 20:51:15 -0300 Subject: [PATCH 11/27] fix implementation Signed-off-by: Tiago Natel de Moura --- internal/sh/shell.go | 65 ++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 702ea854..bdce4239 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -1808,22 +1808,26 @@ func (shell *Shell) concatElements(expr *ast.ConcatExpr) (string, error) { return value, nil } -func (shell *Shell) execCmdOutput(cmd ast.Node, ignoreError bool) (string, sh.Obj, error) { +func (shell *Shell) execCmdOutput(cmd ast.Node, ignoreError bool) ([]byte, []byte, sh.Obj, error) { var ( - varOut bytes.Buffer - err error - status sh.Obj + outBuf, errBuf bytes.Buffer + err error + status sh.Obj ) if cmd.Type() != ast.NodeCommand && cmd.Type() != ast.NodePipe { - return "", nil, errors.NewEvalError(shell.filename, + return nil, nil, nil, errors.NewEvalError(shell.filename, cmd, "Invalid node type (%v). Expected command or pipe", cmd) } - bkStdout := shell.stdout - shell.SetStdout(&varOut) - defer shell.SetStdout(bkStdout) + bkStdout, bkStderr := shell.stdout, shell.stderr + shell.SetStdout(&outBuf) + shell.SetStderr(&errBuf) + defer func() { + shell.SetStdout(bkStdout) + shell.SetStderr(bkStderr) + }() if cmd.Type() == ast.NodeCommand { status, err = shell.executeCommand(cmd.(*ast.CommandNode)) @@ -1831,41 +1835,42 @@ func (shell *Shell) execCmdOutput(cmd ast.Node, ignoreError bool) (string, sh.Ob status, err = shell.executePipe(cmd.(*ast.PipeNode)) } - output := varOut.Bytes() - if len(output) > 0 && output[len(output)-1] == '\n' { - // remove the trailing new line - // Why? because it's what user wants in 99.99% of times... + outb := outBuf.Bytes() + errb := errBuf.Bytes() + + trimnl := func(data []byte) []byte { + if len(data) > 0 && data[len(data)-1] == '\n' { + // remove the trailing new line + // Why? because it's what user wants in 99.99% of times... - output = output[0 : len(output)-1] + data = data[0 : len(data)-1] + } + return data[:] } if ignoreError { err = nil } - return string(output), status, err + return trimnl(outb), trimnl(errb), status, err } func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { assign := v.(*ast.ExecAssignNode) cmd := assign.Command() - outstr, errstr, status, cmdErr := shell.execCmdOutput(cmd, false) + outbuf, errbuf, status, cmdErr := shell.execCmdOutput(cmd, false) // Only getting command output // In this case the script must abort in case of errors if len(assign.Names) == 1 { - shell.stderr.Write(errstr) // flush stderr - err := shell.setvar(assign.Names[0], sh.NewStrObj(outstr), local) + shell.stderr.Write(errbuf) // flush stderr + err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), local) if cmdErr != nil { return cmdErr } - if status != 0 { - return errors.NewEvalError(shell.filename, v, - "exit status: %d", status) - } if err != nil { return err } @@ -1875,18 +1880,14 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { // Only getting stdout and exit status if len(assign.Names) == 2 { - shell.stderr.Write(errstr) // flush stderr - err := shell.setvar(assign.Names[0], sh.NewStrObj(output), local) - - if cmdErr != nil { - return cmdErr - } + shell.stderr.Write(errbuf) // flush stderr + err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), local) if err != nil { return err } - err = shell.setvar(assign.Names[1], sh.NewStrObj(status), false) + err = shell.setvar(assign.Names[1], status, local) if err != nil { return err } @@ -1895,10 +1896,10 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { } if len(assign.Names) == 3 { - err1 := shell.setvar(assign.Names[0], sh.NewStrObj(outstr), local) - err2 := shell.setvar(assign.Names[1], sh.NewStrObj(errstr), local) - err3 := shell.setvar(assign.Names[2], sh.NewStrObj(status), local) - errs := []error{cmdErr, err1, err2, err3} + err1 := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), local) + err2 := shell.setvar(assign.Names[1], sh.NewStrObj(string(errbuf)), local) + err3 := shell.setvar(assign.Names[2], status, local) + errs := []error{err1, err2, err3} for _, e := range errs { if e != nil { From 48daacc6656e92efcc646e0a8463745209be9036 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Fri, 16 Jun 2017 23:04:22 -0300 Subject: [PATCH 12/27] add sieve test example Signed-off-by: Tiago Natel de Moura --- internal/sh/shell_test.go | 21 +++++++++--- testfiles/sieve.sh | 70 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 testfiles/sieve.sh diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index a2e2b150..ac077ac2 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -44,11 +44,10 @@ func init() { os.Setenv("NASHPATH", "/tmp/.nash") } -func testExecuteFile(t *testing.T, path, expected string) { +func testExecuteFile(t *testing.T, path, expected string, before string) { var out bytes.Buffer shell, err := NewShell() - if err != nil { t.Error(err) return @@ -57,6 +56,10 @@ func testExecuteFile(t *testing.T, path, expected string) { shell.SetNashdPath(nashdPath) shell.SetStdout(&out) + if before != "" { + shell.Exec("", before) + } + err = shell.ExecFile(path) if err != nil { @@ -153,14 +156,22 @@ func TestInitEnv(t *testing.T) { func TestExecuteFile(t *testing.T) { type fileTests struct { - path string - expected string + path string + expected string + execBefore string } for _, ftest := range []fileTests{ {path: "/ex1.sh", expected: "hello world\n"}, + {path: "/sieve.sh", expected: "\n", execBefore: `var ARGS = ("" "0")`}, + {path: "/sieve.sh", expected: "\n", execBefore: `var ARGS = ("" "1")`}, + {path: "/sieve.sh", expected: "2 \n", execBefore: `var ARGS = ("" "2")`}, + {path: "/sieve.sh", expected: "2 3 \n", execBefore: `var ARGS = ("" "3")`}, + {path: "/sieve.sh", expected: "2 3 \n", execBefore: `var ARGS = ("" "4")`}, + {path: "/sieve.sh", expected: "2 3 5 \n", execBefore: `var ARGS = ("" "5")`}, + {path: "/sieve.sh", expected: "2 3 5 7 \n", execBefore: `var ARGS = ("" "10")`}, } { - testExecuteFile(t, testDir+ftest.path, ftest.expected) + testExecuteFile(t, testDir+ftest.path, ftest.expected, ftest.execBefore) } } diff --git a/testfiles/sieve.sh b/testfiles/sieve.sh new file mode 100644 index 00000000..fd36aa42 --- /dev/null +++ b/testfiles/sieve.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env nash + +# Sieve of Erathostenes + +fn lt(a, b) { + var _, st <= test $a -lt $b + + return $st +} + +fn gt(a, b) { + var _, st <= test $a -gt $b + + return $st +} + +fn gte(a, b) { + var _, st <= test $a -ge $b + + return $st +} + +fn range(start, end) { + var values, _ <= seq $start $end + var list <= split($values, "\n") + + return $list +} + +fn sieve(n) { + if lt($n, "2") == "0" { + return () + } + if $n == "2" { + return ("2") + } + + var tries = ("0" "0") + + for i in range("2", $n) { + tries <= append($tries, "1") + } + for i in range("2", $n) { + if $tries[$i] == "1" { + for j in range("0", $n) { + var k, _ <= expr $i * $i "+" "(" $j * $i ")" + + if gt($k, $n) != "0" { + tries[$k] = "0" + } + } + } + } + + var primes = () + + for i in range("2", $n) { + if $tries[$i] == "1" { + primes <= append($primes, $i) + } + } + + return $primes +} + +for p in sieve($ARGS[1]) { + print("%s ", $p) +} + +print("\n") From 0f2358b911de569b6ed8ac8708cd0e1607fa80f1 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 17 Jun 2017 20:35:01 -0300 Subject: [PATCH 13/27] add support for concating funcalls Signed-off-by: Tiago Natel de Moura --- internal/sh/shell.go | 21 +++++- internal/sh/shell_test.go | 124 ++++++++++++++++++++++++---------- parser/parse.go | 136 +++++++++++++++++++++++--------------- testfiles/sieve.sh | 45 +++++++++++-- 4 files changed, 232 insertions(+), 94 deletions(-) diff --git a/internal/sh/shell.go b/internal/sh/shell.go index bdce4239..e875daa2 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -631,13 +631,32 @@ func (shell *Shell) evalConcat(path ast.Expr) (string, error) { pathStr += strval.Str() case ast.NodeStringExpr: str, ok := part.(*ast.StringExpr) - if !ok { return "", errors.NewEvalError(shell.filename, part, "Failed to eval string: %s", part) } pathStr += str.Value() + case ast.NodeFnInv: + fnNode := part.(*ast.FnInvNode) + result, err := shell.executeFnInv(fnNode) + if err != nil { + return "", err + } + + if len(result) == 0 || len(result) > 1 { + return "", errors.NewEvalError(shell.filename, part, + "Function '%s' used in string concat but returns %d values.", + fnNode.Name) + } + obj := result[0] + if obj.Type() != sh.StringType { + return "", errors.NewEvalError(shell.filename, part, + "Function '%s' used in concat but returns a '%s'", obj.Type()) + } + + str := obj.(*sh.StrObj) + pathStr += str.Str() case ast.NodeListExpr: return "", errors.NewEvalError(shell.filename, part, "Concat of lists is not allowed: %+v", part.String()) diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index ac077ac2..6fe9535d 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -787,45 +787,99 @@ func TestExecuteImport(t *testing.T) { } func TestExecuteIfEqual(t *testing.T) { - var out bytes.Buffer - - shell, err := NewShell() - if err != nil { - t.Error(err) - return - } - - shell.SetNashdPath(nashdPath) - shell.SetStdout(&out) - - err = shell.Exec("test if equal", ` + for _, test := range []execTestCase{ + { + desc: "if equal", + execStr: ` if "" == "" { echo "empty string works" - }`) - if err != nil { - t.Error(err) - return - } - - if strings.TrimSpace(string(out.Bytes())) != "empty string works" { - t.Errorf("Must be empty. '%s' != 'empty string works'", string(out.Bytes())) - return - } - - out.Reset() - - err = shell.Exec("test if equal 2", ` + }`, + expectedStdout: "empty string works\n", + expectedStderr: "", + expectedErr: "", + }, + { + desc: "if equal", + execStr: ` if "i4k" == "_i4k_" { echo "do not print" - }`) - if err != nil { - t.Error(err) - return - } - - if strings.TrimSpace(string(out.Bytes())) != "" { - t.Errorf("Error: '%s' != ''", strings.TrimSpace(string(out.Bytes()))) - return + }`, + expectedStdout: "", + expectedStderr: "", + expectedErr: "", + }, + { + desc: "if lvalue concat", + execStr: ` + if "i4"+"k" == "i4k" { + echo -n "ok" + }`, + expectedStdout: "ok", + expectedStderr: "", + expectedErr: "", + }, + { + desc: "if lvalue concat", + execStr: `var name = "something" + if $name+"k" == "somethingk" { + echo -n "ok" + }`, + expectedStdout: "ok", + expectedStderr: "", + expectedErr: "", + }, + { + desc: "if lvalue concat", + execStr: `var name = "something" + if $name+"k"+"k" == "somethingkk" { + echo -n "ok" + }`, + expectedStdout: "ok", + expectedStderr: "", + expectedErr: "", + }, + { + desc: "if rvalue concat", + execStr: ` + if "i4k" == "i4"+"k" { + echo -n "ok" + }`, + expectedStdout: "ok", + expectedStderr: "", + expectedErr: "", + }, + { + desc: "if lvalue funcall", + execStr: `var a = () + if len($a) == "0" { + echo -n "ok" + }`, + expectedStdout: "ok", + expectedStderr: "", + expectedErr: "", + }, + { + desc: "if rvalue funcall", + execStr: `var a = ("1") + if "1" == len($a) { + echo -n "ok" + }`, + expectedStdout: "ok", + expectedStderr: "", + expectedErr: "", + }, + { + desc: "if lvalue funcall with concat", + execStr: `var a = () + if len($a)+"1" == "01" { + echo -n "ok" + }`, + expectedStdout: "ok", + expectedStderr: "", + expectedErr: "", + }, + } { + testExec(t, test) } } diff --git a/parser/parse.go b/parser/parse.go index 1bd78d95..aee93dd8 100644 --- a/parser/parse.go +++ b/parser/parse.go @@ -27,6 +27,13 @@ type ( } parserFn func(tok scanner.Token) (ast.Node, error) + + exprConfig struct { + allowArg bool + allowVariadic bool + allowFuncall bool + allowConcat bool + } ) // NewParser creates a new parser @@ -384,7 +391,12 @@ cmdLoop: break cmdLoop case isValidArgument(it): - arg, err := p.getArgument(nil, true, true, true) + arg, err := p.getArgument(nil, exprConfig{ + allowConcat: true, + allowArg: true, + allowVariadic: true, + allowFuncall: false, + }) if err != nil { return nil, err @@ -533,7 +545,12 @@ func (p *Parser) parseRedirection(it scanner.Token) (*ast.RedirectNode, error) { return nil, newParserError(it, p.name, "Unexpected token %v. Expecting STRING or ARG or VARIABLE", it) } - arg, err := p.getArgument(nil, true, true, false) + arg, err := p.getArgument(nil, exprConfig{ + allowConcat: true, + allowArg: true, + allowVariadic: false, + allowFuncall: false, + }) if err != nil { return nil, err } @@ -610,10 +627,11 @@ func (p *Parser) parseSetenv(it scanner.Token) (ast.Node, error) { return setenv, nil } -func (p *Parser) getArgument(tok *scanner.Token, allowArg, allowConcat, allowVariadic bool) (ast.Expr, error) { +func (p *Parser) getArgument(tok *scanner.Token, cfg exprConfig) (ast.Expr, error) { var ( - err error - it scanner.Token + err error + it scanner.Token + isFuncall bool ) if tok != nil { @@ -630,25 +648,40 @@ func (p *Parser) getArgument(tok *scanner.Token, allowArg, allowConcat, allowVar var arg ast.Expr if firstToken.Type() == token.Variable { - arg, err = p.parseVariable(&firstToken, allowVariadic) // makes "echo $list" == "echo $list..." + next := p.peek() - if err != nil { - return nil, err + if cfg.allowFuncall && next.Type() == token.LParen { + arg, err = p.parseFnInv(firstToken, false) + isFuncall = true + } else { + // makes "echo $list" == "echo $list..." + arg, err = p.parseVariable(&firstToken, cfg.allowVariadic) } } else if firstToken.Type() == token.String { arg = ast.NewStringExpr(firstToken.FileInfo, firstToken.Value(), true) } else { // Arg, Ident, Number, Dotdotdot, etc - arg = ast.NewStringExpr(firstToken.FileInfo, firstToken.Value(), false) + + next := p.peek() + + if cfg.allowFuncall && next.Type() == token.LParen { + arg, err = p.parseFnInv(firstToken, false) + isFuncall = true + } else { + arg = ast.NewStringExpr(firstToken.FileInfo, firstToken.Value(), false) + } } - it = p.peek() + if err != nil { + return nil, err + } - if it.Type() == token.Plus && allowConcat { + it = p.peek() + if it.Type() == token.Plus && cfg.allowConcat { return p.getConcatArg(arg) } - if (firstToken.Type() == token.Arg || firstToken.Type() == token.Ident) && !allowArg { + if (firstToken.Type() == token.Arg || firstToken.Type() == token.Ident) && (!cfg.allowArg && !isFuncall) { return nil, newParserError(it, p.name, "Unquoted string not allowed at pos %d (%s)", it.FileInfo, it.Value()) } @@ -669,7 +702,12 @@ hasConcat: if it.Type() == token.Plus { p.ignore() - arg, err := p.getArgument(nil, true, false, false) + arg, err := p.getArgument(nil, exprConfig{ + allowArg: false, + allowConcat: false, + allowFuncall: true, + allowVariadic: false, + }) if err != nil { return nil, err } @@ -779,7 +817,12 @@ func (p *Parser) parseList(tok *scanner.Token) (ast.Node, error) { if it.Type() == token.LParen { arg, err = p.parseList(nil) } else { - arg, err = p.getArgument(nil, true, true, false) + arg, err = p.getArgument(nil, exprConfig{ + allowArg: true, + allowConcat: true, + allowFuncall: false, + allowVariadic: false, + }) } if err != nil { @@ -823,7 +866,12 @@ func (p *Parser) parseAssignValues(names []*ast.NameNode) (ast.Node, error) { ) if it.Type() == token.Variable || it.Type() == token.String { - value, err = p.getArgument(nil, false, true, false) + value, err = p.getArgument(nil, exprConfig{ + allowArg: false, + allowFuncall: true, + allowVariadic: false, + allowConcat: true, + }) } else if it.Type() == token.LParen { // list value, err = p.parseList(nil) } else { @@ -939,36 +987,24 @@ func (p *Parser) parseRfork(it scanner.Token) (ast.Node, error) { } func (p *Parser) parseIfExpr() (ast.Node, error) { - var ( - arg ast.Node - err error - ) - it := p.peek() - if it.Type() != token.Ident && it.Type() != token.String && it.Type() != token.Variable { return nil, newParserError(it, p.name, "if requires lhs/rhs of type string, variable or function invocation. Found %v", it) } - if it.Type() == token.String { - p.next() - arg = ast.NewStringExpr(it.FileInfo, it.Value(), true) - } else if it.Type() == token.Ident { - p.next() - arg, err = p.parseFnInv(it, false) - } else { - arg, err = p.parseVariable(nil, false) - } - - return arg, err + return p.getArgument(nil, exprConfig{ + allowArg: false, + allowVariadic: false, + allowFuncall: true, + allowConcat: true, + }) } func (p *Parser) parseIf(it scanner.Token) (ast.Node, error) { n := ast.NewIfNode(it.FileInfo) lvalue, err := p.parseIfExpr() - if err != nil { return nil, err } @@ -1181,14 +1217,14 @@ func (p *Parser) parseFnInv(ident scanner.Token, allowSemicolon bool) (ast.Node, for { it = p.next() next := p.peek() - if isFuncall(it.Type(), next.Type()) { - funcall, err := p.parseFnInv(it, false) - if err != nil { - return nil, err - } - n.AddArg(funcall) - } else if it.Type() == token.String || it.Type() == token.Variable { - arg, err := p.getArgument(&it, false, true, true) + if isFuncall(it.Type(), next.Type()) || + isValidArgument(it) { + arg, err := p.getArgument(&it, exprConfig{ + allowArg: false, + allowFuncall: true, + allowConcat: true, + allowVariadic: true, + }) if err != nil { return nil, err } @@ -1203,17 +1239,6 @@ func (p *Parser) parseFnInv(ident scanner.Token, allowSemicolon bool) (ast.Node, } else if it.Type() == token.RParen { // p.next() break - } else if it.Type() == token.Ident { - if next.Type() == token.LParen { - arg, err := p.parseFnInv(it, false) - if err != nil { - return nil, err - } - - n.AddArg(arg) - } else { - goto parseError - } } else if it.Type() == token.EOF { goto parseError } @@ -1389,7 +1414,12 @@ func (p *Parser) parseReturn(retTok scanner.Token) (ast.Node, error) { returnExprs = append(returnExprs, arg) } else { - arg, err := p.getArgument(nil, false, true, false) + arg, err := p.getArgument(nil, exprConfig{ + allowArg: false, + allowConcat: true, + allowFuncall: true, + allowVariadic: false, + }) if err != nil { return nil, err } diff --git a/testfiles/sieve.sh b/testfiles/sieve.sh index fd36aa42..fe9467ef 100644 --- a/testfiles/sieve.sh +++ b/testfiles/sieve.sh @@ -14,12 +14,18 @@ fn gt(a, b) { return $st } -fn gte(a, b) { - var _, st <= test $a -ge $b +fn le(a, b) { + var _, st <= test $a -le $b return $st } +fn sqrt(n) { + var v, _ <= expr $n * $n + + return $v +} + fn range(start, end) { var values, _ <= seq $start $end var list <= split($values, "\n") @@ -27,6 +33,30 @@ fn range(start, end) { return $list } +fn xrange(start, condfn) { + var out = () + + if $condfn($start) == "0" { + out = ($start) + } else { + return () + } + + var next = $start + + for { + next, _ <= expr $next "+" 1 + + if $condfn($next) == "0" { + out <= append($out, $next) + } else { + return $out + } + } + + unreachable +} + fn sieve(n) { if lt($n, "2") == "0" { return () @@ -40,7 +70,12 @@ fn sieve(n) { for i in range("2", $n) { tries <= append($tries, "1") } - for i in range("2", $n) { + + fn untilSqrtRoot(v) { + return le(sqrt($v), $n) + } + + for i in xrange("2", $untilSqrtRoot) { if $tries[$i] == "1" { for j in range("0", $n) { var k, _ <= expr $i * $i "+" "(" $j * $i ")" @@ -63,8 +98,8 @@ fn sieve(n) { return $primes } -for p in sieve($ARGS[1]) { - print("%s ", $p) +for prime in sieve($ARGS[1]) { + print("%s ", $prime) } print("\n") From bc546f6089a1edf817d8944d001bf3108ec116b9 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 17 Jun 2017 21:22:22 -0300 Subject: [PATCH 14/27] add fibonacci in testfiles for tests Signed-off-by: Tiago Natel de Moura --- internal/sh/shell_test.go | 6 ++++ testfiles/fibonacci.sh | 64 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100755 testfiles/fibonacci.sh diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 6fe9535d..2b849567 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -163,6 +163,7 @@ func TestExecuteFile(t *testing.T) { for _, ftest := range []fileTests{ {path: "/ex1.sh", expected: "hello world\n"}, + {path: "/sieve.sh", expected: "\n", execBefore: `var ARGS = ("" "0")`}, {path: "/sieve.sh", expected: "\n", execBefore: `var ARGS = ("" "1")`}, {path: "/sieve.sh", expected: "2 \n", execBefore: `var ARGS = ("" "2")`}, @@ -170,6 +171,11 @@ func TestExecuteFile(t *testing.T) { {path: "/sieve.sh", expected: "2 3 \n", execBefore: `var ARGS = ("" "4")`}, {path: "/sieve.sh", expected: "2 3 5 \n", execBefore: `var ARGS = ("" "5")`}, {path: "/sieve.sh", expected: "2 3 5 7 \n", execBefore: `var ARGS = ("" "10")`}, + + {path: "/fibonacci.sh", expected: "1 \n", execBefore: `var ARGS = ("" "1")`}, + {path: "/fibonacci.sh", expected: "1 2 \n", execBefore: `var ARGS = ("" "2")`}, + {path: "/fibonacci.sh", expected: "1 2 3 \n", execBefore: `var ARGS = ("" "3")`}, + {path: "/fibonacci.sh", expected: "1 2 3 5 8 \n", execBefore: `var ARGS = ("" "5")`}, } { testExecuteFile(t, testDir+ftest.path, ftest.expected, ftest.execBefore) } diff --git a/testfiles/fibonacci.sh b/testfiles/fibonacci.sh new file mode 100755 index 00000000..107c84c9 --- /dev/null +++ b/testfiles/fibonacci.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env nash + +# Recursive fibonacci implementation to find the value +# at index n in the sequence. + +# Some times: +# λ> time ./testfiles/fibonacci.sh 1 +# 1 +# 0.00u 0.01s 0.01r ./testfiles/fibonacci.sh 1 +# λ> time ./testfiles/fibonacci.sh 2 +# 1 2 +# 0.01u 0.01s 0.02r ./testfiles/fibonacci.sh 2 +# λ> time ./testfiles/fibonacci.sh 3 +# 1 2 3 +# 0.01u 0.03s 0.03r ./testfiles/fibonacci.sh 3 +# λ> time ./testfiles/fibonacci.sh 4 +# 1 2 3 5 +# 0.04u 0.04s 0.07r ./testfiles/fibonacci.sh 4 +# λ> time ./testfiles/fibonacci.sh 5 +# 1 2 3 5 8 +# 0.09u 0.07s 0.13r ./testfiles/fibonacci.sh 5 +# λ> time ./testfiles/fibonacci.sh 10 +# 1 2 3 5 8 13 21 34 55 89 +# 1.31u 1.18s 2.03r ./testfiles/fibonacci.sh 10 +# λ> time ./testfiles/fibonacci.sh 15 +# 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 +# 15.01u 13.49s 22.55r ./testfiles/fibonacci.sh 15 +# λ> time ./testfiles/fibonacci.sh 20 +# 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 +# 169.27u 155.50s 265.19r ./testfiles/fibonacci.sh 20 + +# a is lower or equal than b? +fn lte(a, b) { + var _, st <= test $a -le $b + + return $st +} + +fn fib(n) { + if lte($n, "1") == "0" { + return "1" + } + + var a, _ <= expr $n - 1 + var b, _ <= expr $n - 2 + var _a <= fib($a) + var _b <= fib($b) + var ret, _ <= expr $_a "+" $_b + + return $ret +} + +fn range(start, end) { + var seq, _ <= seq $start $end + var lst <= split($seq, "\n") + + return $lst +} + +for i in range("1", $ARGS[1]) { + print("%s ", fib($i)) +} + +print("\n") From 4d1dc07b31abfb4c46e9c7013d09a1dbd32ba001 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 17 Jun 2017 22:06:29 -0300 Subject: [PATCH 15/27] fix tests of removing status variable Signed-off-by: Tiago Natel de Moura --- internal/sh/shell.go | 13 ++++--------- internal/sh/shell_test.go | 25 ------------------------- testfiles/sieve.sh | 1 + 3 files changed, 5 insertions(+), 34 deletions(-) diff --git a/internal/sh/shell.go b/internal/sh/shell.go index e875daa2..0abdae55 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -671,9 +671,8 @@ func (shell *Shell) evalConcat(path ast.Expr) (string, error) { func (shell *Shell) executeNode(node ast.Node) ([]sh.Obj, error) { var ( - objs []sh.Obj - err error - status sh.Obj = nil + objs []sh.Obj + err error ) shell.logf("Executing node: %v\n", node) @@ -694,9 +693,9 @@ func (shell *Shell) executeNode(node ast.Node) ([]sh.Obj, error) { case ast.NodeExecAssign: err = shell.executeExecAssign(node.(*ast.ExecAssignNode), false) case ast.NodeCommand: - status, err = shell.executeCommand(node.(*ast.CommandNode)) + _, err = shell.executeCommand(node.(*ast.CommandNode)) case ast.NodePipe: - status, err = shell.executePipe(node.(*ast.PipeNode)) + _, err = shell.executePipe(node.(*ast.PipeNode)) case ast.NodeRfork: err = shell.executeRfork(node.(*ast.RforkNode)) case ast.NodeIf: @@ -726,10 +725,6 @@ func (shell *Shell) executeNode(node ast.Node) ([]sh.Obj, error) { "invalid node: %v.", node.Type()) } - if status != nil { - shell.Setvar("status", status, true) - } - return objs, err } diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 2b849567..f1e64b80 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -7,12 +7,9 @@ import ( "net" "os" "os/exec" - "strconv" "strings" "testing" "time" - - "github.com/NeowayLabs/nash/sh" ) type execTestCase struct { @@ -2150,45 +2147,23 @@ func TestExecuteErrorSuppressionAll(t *testing.T) { return } - scode, ok := shell.Getvar("status", false) - if !ok || scode.Type() != sh.StringType || scode.String() != strconv.Itoa(ENotFound) { - t.Errorf("Invalid status code %s", scode.String()) - return - } - err = shell.Exec("-input-", `echo works >[1=]`) - if err != nil { t.Error(err) return } - scode, ok = shell.Getvar("status", false) - if !ok || scode.Type() != sh.StringType || scode.String() != "0" { - t.Errorf("Invalid status code %s", scode) - return - } - err = shell.Exec("-input-", `echo works | cmd-does-not-exists`) - if err == nil { t.Errorf("Must fail") return } expectedError := `:1:11: not started|exec: "cmd-does-not-exists": executable file not found in $PATH` - if err.Error() != expectedError { t.Errorf("Unexpected error: %s", err.Error()) return } - - scode, ok = shell.Getvar("status", false) - - if !ok || scode.Type() != sh.StringType || scode.String() != "255|127" { - t.Errorf("Invalid status code %s", scode) - return - } } func TestExecuteGracefullyError(t *testing.T) { diff --git a/testfiles/sieve.sh b/testfiles/sieve.sh index fe9467ef..25252d45 100644 --- a/testfiles/sieve.sh +++ b/testfiles/sieve.sh @@ -78,6 +78,7 @@ fn sieve(n) { for i in xrange("2", $untilSqrtRoot) { if $tries[$i] == "1" { for j in range("0", $n) { + # arithmetic seems cryptic without integers =( var k, _ <= expr $i * $i "+" "(" $j * $i ")" if gt($k, $n) != "0" { From 09c4c35dda713f25798e2c36499b25d048cb25e4 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 17 Jun 2017 23:05:19 -0300 Subject: [PATCH 16/27] set _ in global shell, improve setvar/getvar readability Signed-off-by: Tiago Natel de Moura --- internal/sh/shell.go | 85 ++++++++++++++++++++------------------- internal/sh/shell_test.go | 66 +++++++++++++++++------------- 2 files changed, 82 insertions(+), 69 deletions(-) diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 0abdae55..079b069d 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -29,9 +29,8 @@ const ( ) const ( - varNewDecl varOpt = iota - varSet - varOff + Local = true + Global = false ) type ( @@ -180,7 +179,7 @@ func (shell *Shell) initEnv(processEnv []string) error { argv := sh.NewListObj(largs) shell.Setenv("argv", argv) - shell.Setvar("argv", argv, true) + shell.Setvar("argv", argv, Local) for _, penv := range processEnv { var value sh.Obj @@ -196,19 +195,19 @@ func (shell *Shell) initEnv(processEnv []string) error { value = sh.NewStrObj(strings.Join(p[1:], "=")) shell.Setenv(p[0], value) - shell.Setvar(p[0], value, true) + shell.Setvar(p[0], value, Local) } } pidVal := sh.NewStrObj(strconv.Itoa(os.Getpid())) shell.Setenv("PID", pidVal) - shell.Setvar("PID", pidVal, true) + shell.Setvar("PID", pidVal, Local) if _, ok := shell.Getenv("SHELL"); !ok { shellVal := sh.NewStrObj(nashdAutoDiscover()) shell.Setenv("SHELL", shellVal) - shell.Setvar("SHELL", shellVal, true) + shell.Setvar("SHELL", shellVal, Local) } cwd, err := os.Getwd() @@ -219,7 +218,7 @@ func (shell *Shell) initEnv(processEnv []string) error { cwdObj := sh.NewStrObj(cwd) shell.Setenv("PWD", cwdObj) - shell.Setvar("PWD", cwdObj, true) + shell.Setvar("PWD", cwdObj, Local) return nil } @@ -287,7 +286,7 @@ func (shell *Shell) Setenv(name string, value sh.Obj) { return } - shell.Setvar(name, value, true) + shell.Setvar(name, value, Local) shell.env[name] = value os.Setenv(name, value.String()) @@ -303,19 +302,19 @@ func (shell *Shell) SetEnviron(processEnv []string) { if len(p) == 2 { value = sh.NewStrObj(p[1]) - shell.Setvar(p[0], value, true) + shell.Setvar(p[0], value, Local) shell.Setenv(p[0], value) } } } -func (shell *Shell) Getvar(name string, local bool) (sh.Obj, bool) { +func (shell *Shell) Getvar(name string, where bool) (sh.Obj, bool) { if value, ok := shell.vars[name]; ok { return value, ok } - if !local && shell.parent != nil { - return shell.parent.Getvar(name, false) + if where == Global && shell.parent != nil { + return shell.parent.Getvar(name, Global) } return nil, false @@ -354,15 +353,15 @@ func (shell *Shell) Getbindfn(cmdName string) (sh.FnDef, bool) { return nil, false } -func (shell *Shell) Setvar(name string, value sh.Obj, local bool) bool { +func (shell *Shell) Setvar(name string, value sh.Obj, where bool) bool { _, ok := shell.vars[name] - if ok || local { + if ok || where == Local { shell.vars[name] = value return true } if shell.parent != nil { - return shell.parent.Setvar(name, value, false) + return shell.parent.Setvar(name, value, Global) } return false @@ -425,7 +424,7 @@ func (shell *Shell) String() string { func (shell *Shell) setupBuiltin() { for name, constructor := range builtin.Constructors() { fnDef := newBuiltinFnDef(name, shell, constructor) - _ = shell.Setvar(name, sh.NewFnObj(fnDef), true) + _ = shell.Setvar(name, sh.NewFnObj(fnDef), Local) } } @@ -453,7 +452,12 @@ func (shell *Shell) setup() error { if shell.env["PROMPT"] == nil { pobj := sh.NewStrObj(defPrompt) shell.Setenv("PROMPT", pobj) - shell.Setvar("PROMPT", pobj, true) + shell.Setvar("PROMPT", pobj, Local) + } + + _, ok := shell.Getvar("_", Global) + if !ok { + shell.Setvar("_", sh.NewStrObj(""), Local) } shell.setupBuiltin() @@ -550,11 +554,11 @@ func (shell *Shell) ExecFile(path string) error { // setVar tries to set value into variable name. It looks for the variable // first in the current scope and then in the parents if not found. -func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj, local bool) error { +func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj, where bool) error { finalObj := value if name.Index != nil { - if list, ok := shell.Getvar(name.Ident, false); ok { + if list, ok := shell.Getvar(name.Ident, Global); ok { index, err := shell.evalIndex(name.Index) if err != nil { @@ -585,7 +589,7 @@ func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj, local bool) error { } } - if !shell.Setvar(name.Ident, finalObj, local) { + if !shell.Setvar(name.Ident, finalObj, where) { return errors.NewEvalError(shell.filename, name, "Variable '%s' is not initialized. Use 'var %s = '", name, name) @@ -1542,7 +1546,7 @@ func (shell *Shell) evalVariable(a ast.Expr) (sh.Obj, error) { vexpr := a.(*ast.VarExpr) varName := vexpr.Name - if value, ok = shell.Getvar(varName[1:], false); !ok { + if value, ok = shell.Getvar(varName[1:], Global); !ok { return nil, errors.NewEvalError(shell.filename, a, "Variable %s not set on shell %s", varName, shell.name) } @@ -1565,7 +1569,7 @@ func (shell *Shell) evalArgVariable(a ast.Expr) ([]sh.Obj, error) { } vexpr := a.(*ast.VarExpr) - if value, ok = shell.Getvar(vexpr.Name[1:], false); !ok { + if value, ok = shell.Getvar(vexpr.Name[1:], Global); !ok { return nil, errors.NewEvalError(shell.filename, a, "Variable %s not set on shell %s", vexpr.Name, shell.name) @@ -1734,7 +1738,7 @@ func (shell *Shell) executeSetenvAssign(assign *ast.AssignNode) error { if err != nil { return err } - obj, ok := shell.Getvar(name.Ident, true) + obj, ok := shell.Getvar(name.Ident, Local) if !ok { return errors.NewEvalError(shell.filename, assign, @@ -1754,7 +1758,7 @@ func (shell *Shell) executeSetenvExec(assign *ast.ExecAssignNode) error { } for i := 0; i < len(assign.Names); i++ { name := assign.Names[i] - obj, ok := shell.Getvar(name.Ident, true) + obj, ok := shell.Getvar(name.Ident, Local) if !ok { return errors.NewEvalError(shell.filename, assign, @@ -1786,7 +1790,7 @@ func (shell *Shell) executeSetenv(v *ast.SetenvNode) error { assign) } - varValue, ok = shell.Getvar(v.Name, false) + varValue, ok = shell.Getvar(v.Name, Global) if !ok { return errors.NewEvalError(shell.filename, v, "Variable '%s' not set on shell %s", v.Name, @@ -1879,7 +1883,7 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { // In this case the script must abort in case of errors if len(assign.Names) == 1 { shell.stderr.Write(errbuf) // flush stderr - err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), local) + err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), Local) if cmdErr != nil { return cmdErr @@ -1895,13 +1899,13 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { // Only getting stdout and exit status if len(assign.Names) == 2 { shell.stderr.Write(errbuf) // flush stderr - err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), local) + err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), Local) if err != nil { return err } - err = shell.setvar(assign.Names[1], status, local) + err = shell.setvar(assign.Names[1], status, Local) if err != nil { return err } @@ -1910,9 +1914,9 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { } if len(assign.Names) == 3 { - err1 := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), local) - err2 := shell.setvar(assign.Names[1], sh.NewStrObj(string(errbuf)), local) - err3 := shell.setvar(assign.Names[2], status, local) + err1 := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), Local) + err2 := shell.setvar(assign.Names[1], sh.NewStrObj(string(errbuf)), Local) + err3 := shell.setvar(assign.Names[2], status, Local) errs := []error{err1, err2, err3} for _, e := range errs { @@ -1928,7 +1932,7 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { len(assign.Names)) } -func (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode, local bool) error { +func (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode, where bool) error { var ( err error fnValues []sh.Obj @@ -1953,7 +1957,7 @@ func (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode, local bool) } for i := 0; i < len(assign.Names); i++ { - err := shell.setvar(assign.Names[i], fnValues[i], local) + err := shell.setvar(assign.Names[i], fnValues[i], where) if err != nil { return err } @@ -1962,14 +1966,14 @@ func (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode, local bool) return nil } -func (shell *Shell) executeExecAssign(v *ast.ExecAssignNode, local bool) error { +func (shell *Shell) executeExecAssign(v *ast.ExecAssignNode, where bool) error { assign := v.Command() if assign.Type() == ast.NodeFnInv { - return shell.executeExecAssignFn(v, local) + return shell.executeExecAssignFn(v, where) } else if assign.Type() == ast.NodeCommand || assign.Type() == ast.NodePipe { - return shell.executeExecAssignCmd(v, local) + return shell.executeExecAssignCmd(v, where) } return errors.NewEvalError(shell.filename, @@ -2005,7 +2009,7 @@ func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { for i := 0; i < len(assign.Names); i++ { name := assign.Names[i] value := assign.Values[i] - _, varLocalExists := shell.Getvar(name.Ident, true) + _, varLocalExists := shell.Getvar(name.Ident, Local) if !varLocalExists { initVarNames = append(initVarNames, name) initVarValues = append(initVarValues, value) @@ -2096,7 +2100,6 @@ func (shell *Shell) evalIfArgument(arg ast.Node) (sh.Obj, error) { ) obj, err = shell.evalExpr(arg) - if err != nil { return nil, err } else if obj == nil { @@ -2341,7 +2344,7 @@ func (shell *Shell) executeFor(n *ast.ForNode) ([]sh.Obj, error) { objlist := obj.(*sh.ListObj) for _, val := range objlist.List() { - shell.Setvar(id, val, true) + shell.Setvar(id, val, Local) objs, err := shell.executeTree(n.Tree(), false) @@ -2392,7 +2395,7 @@ func (shell *Shell) executeFnDecl(n *ast.FnDeclNode) error { return err } - shell.Setvar(n.Name(), sh.NewFnObj(fnDef), true) + shell.Setvar(n.Name(), sh.NewFnObj(fnDef), Local) shell.logf("Function %s declared on '%s'", n.Name(), shell.name) return nil } diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index f1e64b80..02b5f197 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -225,29 +225,32 @@ chdir($path) func TestExecuteAssignment(t *testing.T) { for _, test := range []execTestCase{ - { // wrong assignment - "wrong assignment", - `var name=i4k`, - "", "", - "wrong assignment:1:9: Unexpected token IDENT. Expecting VARIABLE, STRING or (", + { + desc: "wrong assignment", + execStr: `var name=i4k`, + expectedStdout: "", + expectedStderr: "", + expectedErr: "wrong assignment:1:9: Unexpected token IDENT. Expecting VARIABLE, STRING or (", }, { - "assignment", - `var name="i4k" + desc: "assignment", + execStr: `var name="i4k" echo $name`, - "i4k\n", "", - "", + expectedStdout: "i4k\n", + expectedStderr: "", + expectedErr: "", }, { - "list assignment", - `var name=(honda civic) + desc: "list assignment", + execStr: `var name=(honda civic) echo -n $name`, - "honda civic", "", - "", + expectedStdout: "honda civic", + expectedStderr: "", + expectedErr: "", }, { - "list of lists", - `var l = ( + desc: "list of lists", + execStr: `var l = ( (name Archlinux) (arch amd64) (kernel 4.7.1) @@ -256,30 +259,37 @@ func TestExecuteAssignment(t *testing.T) { echo $l[0] echo $l[1] echo -n $l[2]`, - `name Archlinux + expectedStdout: `name Archlinux arch amd64 kernel 4.7.1`, - "", - "", + expectedStderr: "", + expectedErr: "", }, { - "list assignment", - `var l = (0 1 2 3) + desc: "list assignment", + execStr: `var l = (0 1 2 3) l[0] = "666" echo -n $l`, - `666 1 2 3`, - "", - "", + expectedStdout: `666 1 2 3`, + expectedStderr: "", + expectedErr: "", }, { - "list assignment", - `var l = (0 1 2 3) + desc: "list assignment", + execStr: `var l = (0 1 2 3) var a = "2" l[$a] = "666" echo -n $l`, - `0 1 666 3`, - "", - "", + expectedStdout: `0 1 666 3`, + expectedStderr: "", + expectedErr: "", + }, + { + desc: "_ always exists", + execStr: `_ = "something"`, + expectedStdout: "", + expectedStderr: "", + expectedErr: "", }, } { testExec(t, test) From 4d328821eeda5fd03cee066ca7a2d71f32f2d999 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sat, 17 Jun 2017 23:07:19 -0300 Subject: [PATCH 17/27] remove unused types Signed-off-by: Tiago Natel de Moura --- internal/sh/shell.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 079b069d..779c7efd 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -34,8 +34,6 @@ const ( ) type ( - varOpt int - // Env is the environment map of lists Env map[string]sh.Obj Var Env @@ -72,8 +70,6 @@ type ( *sync.Mutex } - onSetvarSuccess func(name *ast.NameNode, val sh.Obj) error - errIgnore struct { *errors.NashError } From 6ad6f9d571c1f2e42be398465d75d01c19f5164f Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Sun, 18 Jun 2017 09:09:49 -0300 Subject: [PATCH 18/27] flush stderr earlier when not redirected Signed-off-by: Tiago Natel de Moura --- internal/sh/shell.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 779c7efd..f9178e23 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -1822,7 +1822,7 @@ func (shell *Shell) concatElements(expr *ast.ConcatExpr) (string, error) { return value, nil } -func (shell *Shell) execCmdOutput(cmd ast.Node, ignoreError bool) ([]byte, []byte, sh.Obj, error) { +func (shell *Shell) execCmdOutput(cmd ast.Node, getstderr, ignoreError bool) ([]byte, []byte, sh.Obj, error) { var ( outBuf, errBuf bytes.Buffer err error @@ -1837,7 +1837,9 @@ func (shell *Shell) execCmdOutput(cmd ast.Node, ignoreError bool) ([]byte, []byt bkStdout, bkStderr := shell.stdout, shell.stderr shell.SetStdout(&outBuf) - shell.SetStderr(&errBuf) + if getstderr { + shell.SetStderr(&errBuf) + } defer func() { shell.SetStdout(bkStdout) shell.SetStderr(bkStderr) @@ -1869,16 +1871,14 @@ func (shell *Shell) execCmdOutput(cmd ast.Node, ignoreError bool) ([]byte, []byt return trimnl(outb), trimnl(errb), status, err } -func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { +func (shell *Shell) executeExecAssignCmd(v ast.Node, where bool) error { assign := v.(*ast.ExecAssignNode) cmd := assign.Command() - outbuf, errbuf, status, cmdErr := shell.execCmdOutput(cmd, false) - // Only getting command output // In this case the script must abort in case of errors if len(assign.Names) == 1 { - shell.stderr.Write(errbuf) // flush stderr + outbuf, _, _, cmdErr := shell.execCmdOutput(cmd, false, false) err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), Local) if cmdErr != nil { @@ -1894,7 +1894,7 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { // Only getting stdout and exit status if len(assign.Names) == 2 { - shell.stderr.Write(errbuf) // flush stderr + outbuf, _, status, _ := shell.execCmdOutput(cmd, false, false) err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), Local) if err != nil { @@ -1910,6 +1910,7 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, local bool) error { } if len(assign.Names) == 3 { + outbuf, errbuf, status, _ := shell.execCmdOutput(cmd, true, false) err1 := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), Local) err2 := shell.setvar(assign.Names[1], sh.NewStrObj(string(errbuf)), Local) err3 := shell.setvar(assign.Names[2], status, Local) From b939a692214ce49e923d4d37573dab622ebcdc97 Mon Sep 17 00:00:00 2001 From: Luciano Antonio Borguetti Faustino Date: Thu, 20 Jul 2017 09:49:33 -0300 Subject: [PATCH 19/27] Upgrade to nash v1 --- examples/append.sh | 2 +- examples/args.sh | 14 ++++++-------- examples/init | 30 ++++++++++++++++-------------- 3 files changed, 23 insertions(+), 23 deletions(-) mode change 100644 => 100755 examples/args.sh mode change 100644 => 100755 examples/init diff --git a/examples/append.sh b/examples/append.sh index a6116af4..ae71f68b 100755 --- a/examples/append.sh +++ b/examples/append.sh @@ -1,6 +1,6 @@ #!/usr/bin/env nash -example_list = () +var example_list = () echo "appending string 1" example_list <= append($example_list, "1") echo $example_list diff --git a/examples/args.sh b/examples/args.sh old mode 100644 new mode 100755 index 43ccffba..677550f2 --- a/examples/args.sh +++ b/examples/args.sh @@ -1,13 +1,11 @@ #!/usr/bin/env nash -echo "iterating through the arguments list" -echo "" +print("iterating through the arguments list\n\n") for arg in $ARGS { - echo $arg + print("%s\n", $arg) } -echo "" -echo "acessing individual parameter" -somearg = $ARGS[0] -echo $somearg -echo "" +print("\n") +print("acessing individual parameter\n") +var somearg = $ARGS[0] +print("%s\n", $somearg) diff --git a/examples/init b/examples/init old mode 100644 new mode 100755 index c212ce28..41804850 --- a/examples/init +++ b/examples/init @@ -1,47 +1,49 @@ #!/usr/bin/env nash + # Simple init script for you base your own # For a more complete and organized .nash see: # https://github.com/tiago4orion/dotnash # PROMPT is a special variable used by nash command line to # setup your prompt. -RED = "\033[31m" -GREEN = "\033[32m" -RESET = "\033[0m" - -PROMPTSYM = "λ> " -DEFPROMPT = $RED + $PROMPTSYM + $RESET - -PROMPT = $DEFPROMPT +var RED = "" +var GREEN = "" +var RESET = "" +var PROMPTSYM = "λ> " +var DEFPROMPT = $RED+$PROMPTSYM+$RESET -setenv PROMPT +setenv PROMPT = $DEFPROMPT # cd overrides built-in cd # Add the current branch before prompt (if current directory is a git repo) fn cd(path) { + var branch = "" + var prompt = "" + if $path == "" { path = $HOME } chdir($path) - -test -d ./.git + var _, status <= test -d ./.git if $status != "0" { - PROMPT = $DEFPROMPT + prompt = $DEFPROMPT } else { branch <= git rev-parse --abbrev-ref HEAD | xargs echo -n - PROMPT = "(" + $GREEN + $branch + $RESET + ")" + $DEFPROMPT + + prompt = "("+$GREEN+$branch+$RESET+")"+$DEFPROMPT } - setenv PROMPT + setenv PROMPT = $prompt } bindfn cd cd # syntax sugar to cd into go project fn gocd(path) { - cd $GOPATH + "/src/" + $path + cd $GOPATH+"/src/"+$path } bindfn gocd gocd From ba5b568b9c99daa5a03f34e4aaf032a34b344a24 Mon Sep 17 00:00:00 2001 From: i4k Date: Thu, 4 Jan 2018 06:31:28 -0200 Subject: [PATCH 20/27] merge with fix-windows Signed-off-by: i4k --- internal/sh/shell_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 71dc51ec..89a6c7f3 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -306,7 +306,7 @@ func TestExecuteAssignment(t *testing.T) { code: `var name=i4k`, expectedStdout: "", expectedStderr: "", - expectedErr: "wrong assignment:1:5: Unexpected token IDENT. Expecting VARIABLE, STRING or (", + expectedErr: "wrong assignment:1:9: Unexpected token IDENT. Expecting VARIABLE, STRING or (", }, { desc: "assignment", From b0310513b86aabdd7e6ff73643e0ede54de74318 Mon Sep 17 00:00:00 2001 From: i4k Date: Sat, 13 Jan 2018 00:28:06 -0200 Subject: [PATCH 21/27] cherry-pick from fix-windows Signed-off-by: i4k --- internal/sh/shell_test.go | 41 +++------------------- nash_test.go | 40 ++------------------- spec_test.go | 23 +++---------- tests/cfg.go | 61 ++++++++++++++++++++++++++++++++- tests/internal/tester/tester.go | 1 - tests/listindex_test.go | 2 +- tests/stringindex_test.go | 6 ++-- 7 files changed, 75 insertions(+), 99 deletions(-) diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 89a6c7f3..39d3671a 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -7,8 +7,6 @@ import ( "net" "os" "os/exec" - "os/user" - "path" "path/filepath" "runtime" "strconv" @@ -17,6 +15,7 @@ import ( "time" "github.com/NeowayLabs/nash/sh" + "github.com/NeowayLabs/nash/tests" ) type ( @@ -39,47 +38,15 @@ func init() { } -func getGopath() string { - gopath := os.Getenv("GOPATH") - if gopath == "" { - usr, err := user.Current() - if err != nil { - panic(err) - } - if usr.HomeDir == "" { - panic("Unable to discover GOPATH") - } - gopath = path.Join(usr.HomeDir, "go") - } - return gopath -} - func setup(t *testing.T) (fixture, func()) { - gopath := getGopath() - testDir := filepath.FromSlash(path.Join(gopath, "/src/github.com/NeowayLabs/nash/", "testfiles")) - nashdPath := filepath.FromSlash(path.Join(gopath, "/src/github.com/NeowayLabs/nash/cmd/nash/nash")) - - if runtime.GOOS == "windows" { - nashdPath += ".exe" - } - - if _, err := os.Stat(nashdPath); err != nil { - panic(fmt.Errorf("Please, run make build before running tests: %s", err.Error())) - } - - tmpNashPath, err := ioutil.TempDir("", "nashpath") - if err != nil { - t.Fatal(err) - } - - err = os.Setenv("NASHPATH", tmpNashPath) + err := os.Setenv("NASHPATH", "/tmp/.nash") if err != nil { t.Fatal(err) } return fixture{ - dir: testDir, - nashdPath: nashdPath, + dir: tests.Testdir, + nashdPath: tests.Nashcmd, }, func() { os.RemoveAll(tmpNashPath) err := os.Unsetenv("NASHPATH") diff --git a/nash_test.go b/nash_test.go index cbd35d1f..62ec7605 100644 --- a/nash_test.go +++ b/nash_test.go @@ -1,53 +1,19 @@ package nash import ( - "fmt" "bytes" "os" "testing" - "os/user" - "path" - "path/filepath" - "runtime" "github.com/NeowayLabs/nash/sh" + "github.com/NeowayLabs/nash/tests" ) // only testing the public API // bypass to internal sh.Shell -var ( - gopath, testDir, nashdPath string -) - -func init() { - gopath = os.Getenv("GOPATH") - - if gopath == "" { - usr, err := user.Current() - if err != nil { - panic(err) - } - if usr.HomeDir == "" { - panic("Unable to discover GOPATH") - } - gopath = path.Join(usr.HomeDir, "go") - } - - testDir = filepath.FromSlash(path.Join(gopath, "/src/github.com/NeowayLabs/nash/", "testfiles")) - nashdPath = filepath.FromSlash(path.Join(gopath, "/src/github.com/NeowayLabs/nash/cmd/nash/nash")) - - if runtime.GOOS == "windows" { - nashdPath += ".exe" - } - - if _, err := os.Stat(nashdPath); err != nil { - panic(fmt.Errorf("Please, run make build before running tests: %s", err.Error())) - } -} - func TestExecuteFile(t *testing.T) { - testfile := testDir + "/ex1.sh" + testfile := tests.Testdir + "/ex1.sh" var out bytes.Buffer @@ -58,7 +24,7 @@ func TestExecuteFile(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(tests.Nashcmd) shell.SetStdout(&out) shell.SetStderr(os.Stderr) shell.SetStdin(os.Stdin) diff --git a/spec_test.go b/spec_test.go index a7630db0..aaa3f931 100644 --- a/spec_test.go +++ b/spec_test.go @@ -3,28 +3,16 @@ package nash import ( "bytes" "io/ioutil" - "os" - "testing" - "os/user" - "path" "path/filepath" + "testing" + "github.com/NeowayLabs/nash/tests" "golang.org/x/exp/ebnf" ) func TestSpecificationIsSane(t *testing.T) { - gopath := os.Getenv("GOPATH") - if gopath == "" { - usr, err := user.Current() - if err != nil { - t.Fatal(err) - } - if usr.HomeDir == "" { - t.Fatal("Unable to discover GOPATH") - } - gopath = path.Join(usr.HomeDir, "go") - } - filename := filepath.Join(gopath, filepath.FromSlash("/src/github.com/NeowayLabs/nash/spec.ebnf")) + filename := filepath.Join(tests.Gopath, "src", "github.com", + "NeowayLabs", "nash", "spec.ebnf") content, err := ioutil.ReadFile(filename) if err != nil { t.Error(err) @@ -32,16 +20,13 @@ func TestSpecificationIsSane(t *testing.T) { } buf := bytes.NewBuffer(content) - grammar, err := ebnf.Parse(filename, buf) - if err != nil { t.Error(err) return } err = ebnf.Verify(grammar, "program") - if err != nil { t.Error(err) return diff --git a/tests/cfg.go b/tests/cfg.go index ea6c164e..fc1fa08f 100644 --- a/tests/cfg.go +++ b/tests/cfg.go @@ -1,3 +1,62 @@ package tests -const nashcmd = "../cmd/nash/nash" +import ( + "errors" + "fmt" + "os" + "os/user" + "path/filepath" + "strings" +) + +var ( + // Nashcmd is the nash's absolute binary path in source + Nashcmd string + // Testdir is the test assets directory + Testdir string + Gopath string +) + +func getGopath() (string, error) { + gopathenv := os.Getenv("GOPATH") + if gopathenv != "" { + return gopathenv, nil + } + + usr, err := user.Current() + if err != nil { + return "", fmt.Errorf("failed to get user's home directory: %s", err) + } + + gopathhome := filepath.Join(usr.HomeDir, "go") + if _, err := os.Stat(gopathhome); err != nil { + return "", errors.New("gopath not found") + } + + wd, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("failed to get working directory: %s", err) + } + + if !strings.HasPrefix(wd, gopathhome) { + return "", errors.New("Run tests require code inside gopath") + } + return gopathhome, nil +} + +func init() { + gopath, err := getGopath() + if err != nil { + panic(err) + } + + Gopath = gopath + Testdir = filepath.Join(Gopath, "src", "github.com", + "NeowayLabs", "nash", "testfiles") + Nashcmd = filepath.Join(Gopath, "src", "github.com", + "NeowayLabs", "nash", "cmd", "nash", "nash") + + if _, err := os.Stat(Nashcmd); err != nil { + panic("Please, run make build before running tests") + } +} diff --git a/tests/internal/tester/tester.go b/tests/internal/tester/tester.go index 2fd48f9a..341e46c5 100644 --- a/tests/internal/tester/tester.go +++ b/tests/internal/tester/tester.go @@ -18,7 +18,6 @@ type TestCase struct { } func Run(t *testing.T, nashcmd string, cases ...TestCase) { - for _, tcase := range cases { t.Run(tcase.Name, func(t *testing.T) { stdout, stderr, err := sh.Exec(t, nashcmd, tcase.ScriptCode) diff --git a/tests/listindex_test.go b/tests/listindex_test.go index 22990980..4c7b5a8e 100644 --- a/tests/listindex_test.go +++ b/tests/listindex_test.go @@ -7,7 +7,7 @@ import ( ) func TestListIndexing(t *testing.T) { - tester.Run(t, nashcmd, + tester.Run(t, Nashcmd, tester.TestCase{ Name: "PositionalAccess", ScriptCode: ` diff --git a/tests/stringindex_test.go b/tests/stringindex_test.go index b2337ecd..e177e7b0 100644 --- a/tests/stringindex_test.go +++ b/tests/stringindex_test.go @@ -7,7 +7,7 @@ import ( ) func TestStringIndexing(t *testing.T) { - tester.Run(t, nashcmd, + tester.Run(t, Nashcmd, tester.TestCase{ Name: "IterateEmpty", ScriptCode: ` @@ -41,7 +41,7 @@ func TestStringIndexing(t *testing.T) { ) } func TestStringIndexingASCII(t *testing.T) { - tester.Run(t, nashcmd, + tester.Run(t, Nashcmd, tester.TestCase{Name: "PositionalAccess", ScriptCode: ` a = "12" @@ -91,7 +91,7 @@ func TestStringIndexingASCII(t *testing.T) { } func TestStringIndexingNonASCII(t *testing.T) { - tester.Run(t, nashcmd, + tester.Run(t, Nashcmd, tester.TestCase{Name: "PositionalAccess", ScriptCode: ` a = "⌘⌘" From a8ea504d5ac466e4bfe0652921dca4c49d9f0f9c Mon Sep 17 00:00:00 2001 From: i4k Date: Sat, 13 Jan 2018 02:57:26 -0200 Subject: [PATCH 22/27] update stdlib to use var Signed-off-by: i4k --- internal/sh/shell.go | 1 - internal/sh/shell_test.go | 26 ++++++++++++-------------- stdlib/io_test.sh | 8 ++++---- stdlib/map.sh | 9 ++++----- stdlib/map_test.sh | 21 ++++++++++----------- tests/listindex_test.go | 16 ++++++++-------- tests/stringindex_test.go | 34 +++++++++++++++++----------------- 7 files changed, 55 insertions(+), 60 deletions(-) diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 19d4a609..9f4647fc 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -731,7 +731,6 @@ func (shell *Shell) executeTree(tr *ast.Tree, stopable bool) ([]sh.Obj, error) { for _, node := range root.Nodes { objs, err := shell.executeNode(node) - if err != nil { type ( IgnoreError interface { diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 39d3671a..994ed9db 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -39,7 +39,12 @@ func init() { } func setup(t *testing.T) (fixture, func()) { - err := os.Setenv("NASHPATH", "/tmp/.nash") + tmpNashPath, err := ioutil.TempDir("", "nash-tests") + if err != nil { + t.Fatal(err) + } + + err = os.Setenv("NASHPATH", tmpNashPath) if err != nil { t.Fatal(err) } @@ -754,10 +759,11 @@ func TestExecuteCd(t *testing.T) { homeEnvVar = "HOMEPATH" // hack to use nash's pwd instead of gnu on windows - projectDir := getGopath() + "/src/github.com/NeowayLabs/nash" - pwdDir := projectDir + "/stdbin/pwd" + projectDir := filepath.Join(tests.Gopath, filepath.FromSlash( + "src/github.com/NeowayLabs/nash")) + pwdDir := filepath.Join(projectDir, "stdbin", "pwd") path := os.Getenv("Path") - defer os.Setenv("Path", path) + defer os.Setenv("Path", path) // TODO(i4k): very unsafe os.Setenv("Path", pwdDir+";"+path) } @@ -2108,13 +2114,12 @@ for i in $seq {}`) func TestExecuteErrorSuppressionAll(t *testing.T) { shell, err := NewShell() - if err != nil { t.Error(err) return } - err = shell.Exec("-input-", `-command-not-exists`) + err = shell.Exec("-input-", `_, status <= command-not-exists`) if err != nil { t.Errorf("Expected to not fail...: %s", err.Error()) return @@ -2126,7 +2131,7 @@ func TestExecuteErrorSuppressionAll(t *testing.T) { return } - err = shell.Exec("-input-", `echo works >[1=]`) + err = shell.Exec("-input-", `_, status <= echo works`) if err != nil { t.Error(err) return @@ -2150,13 +2155,6 @@ func TestExecuteErrorSuppressionAll(t *testing.T) { t.Errorf("Unexpected error: %s", err.Error()) return } - - scode, ok = shell.Getvar("status", Local) - - if !ok || scode.Type() != sh.StringType || scode.String() != "255|127" { - t.Errorf("Invalid status code %v", scode) - return - } } func TestExecuteGracefullyError(t *testing.T) { diff --git a/stdlib/io_test.sh b/stdlib/io_test.sh index f9a18f5a..f5549d44 100644 --- a/stdlib/io_test.sh +++ b/stdlib/io_test.sh @@ -1,6 +1,6 @@ fn run_example(args...) { - got, status <= ./cmd/nash/nash ./stdlib/io_example.sh $args + var got, status <= ./cmd/nash/nash ./stdlib/io_example.sh $args return $got, $status } @@ -16,14 +16,14 @@ fn assert_success(expected, got, status) { } fn test_println_format() { - got, status <= run_example("hello %s", "world") + var got, status <= run_example("hello %s", "world") assert_success("hello world", $got, $status) } fn test_println() { - expected = "pazu" - got, status <= run_example($expected) + var expected = "pazu" + var got, status <= run_example($expected) assert_success($expected, $got, $status) } diff --git a/stdlib/map.sh b/stdlib/map.sh index 8330affa..4b438fcf 100644 --- a/stdlib/map.sh +++ b/stdlib/map.sh @@ -1,6 +1,5 @@ fn map_new() { - map = () - return $map + return () } fn map_get(map, key) { @@ -31,17 +30,17 @@ fn map_add(map, key, val) { } } - tuple = ($key $val) + var tuple = ($key $val) map <= append($map, $tuple) return $map } fn map_del(map, key) { - newmap = () + var newmap = () for entry in $map { if $entry[0] != $key { - tuple = ($entry[0] $entry[1]) + var tuple = ($entry[0] $entry[1]) newmap <= append($newmap, $tuple) } } diff --git a/stdlib/map_test.sh b/stdlib/map_test.sh index 2e9ba3f7..b054f684 100644 --- a/stdlib/map_test.sh +++ b/stdlib/map_test.sh @@ -1,8 +1,7 @@ import map fn expect(map, key, want) { - got <= map_get($map, $key) - + var got <= map_get($map, $key) if $got != $want { echo "error: got["+$got+"] want["+$want+"]" exit("1") @@ -10,7 +9,7 @@ fn expect(map, key, want) { } fn test_adding_keys() { - map <= map_new() + var map <= map_new() map <= map_add($map, "key", "value") expect($map, "key", "value") @@ -23,14 +22,14 @@ fn test_adding_keys() { } fn test_absent_key_will_have_empty_string_value() { - map <= map_new() + var map <= map_new() expect($map, "absent", "") } fn test_absent_key_with_custom_default_value() { - map <= map_new() - want = "hi" - got <= map_get_default($map, "absent", $want) + var map <= map_new() + var want = "hi" + var got <= map_get_default($map, "absent", $want) if $got != $want { echo "error: got["+$got+"] want["+$want+"]" exit("1") @@ -38,11 +37,11 @@ fn test_absent_key_with_custom_default_value() { } fn test_iterates_map() { - map <= map_new() + var map <= map_new() map <= map_add($map, "key", "value") map <= map_add($map, "key2", "value2") - got <= map_new() + var got <= map_new() fn iter(key, val) { got <= map_add($got, $key, $val) @@ -55,7 +54,7 @@ fn test_iterates_map() { } fn test_removing_key() { - map <= map_new() + var map <= map_new() map <= map_add($map, "key", "value") map <= map_add($map, "key2", "value2") @@ -69,7 +68,7 @@ fn test_removing_key() { } fn test_removing_absent_key() { - map <= map_new() + var map <= map_new() expect($map, "key", "") map <= map_del($map, "key") diff --git a/tests/listindex_test.go b/tests/listindex_test.go index 4c7b5a8e..8d6322da 100644 --- a/tests/listindex_test.go +++ b/tests/listindex_test.go @@ -11,7 +11,7 @@ func TestListIndexing(t *testing.T) { tester.TestCase{ Name: "PositionalAccess", ScriptCode: ` - a = ("1" "2") + var a = ("1" "2") echo $a[0] echo $a[1] `, @@ -20,7 +20,7 @@ func TestListIndexing(t *testing.T) { tester.TestCase{ Name: "PositionalAssigment", ScriptCode: ` - a = ("1" "2") + var a = ("1" "2") a[0] = "9" a[1] = "p" echo $a[0] + $a[1] @@ -30,8 +30,8 @@ func TestListIndexing(t *testing.T) { tester.TestCase{ Name: "PositionalAccessWithVar", ScriptCode: ` - a = ("1" "2") - i = "0" + var a = ("1" "2") + var i = "0" echo $a[$i] i = "1" echo $a[$i] @@ -41,7 +41,7 @@ func TestListIndexing(t *testing.T) { tester.TestCase{ Name: "Iteration", ScriptCode: ` - a = ("1" "2" "3") + var a = ("1" "2" "3") for x in $a { echo $x } @@ -51,7 +51,7 @@ func TestListIndexing(t *testing.T) { tester.TestCase{ Name: "IterateEmpty", ScriptCode: ` - a = () + var a = () for x in $a { exit("1") } @@ -62,7 +62,7 @@ func TestListIndexing(t *testing.T) { tester.TestCase{ Name: "IndexOutOfRangeFails", ScriptCode: ` - a = ("1" "2" "3") + var a = ("1" "2" "3") echo $a[3] `, Fails: true, @@ -71,7 +71,7 @@ func TestListIndexing(t *testing.T) { tester.TestCase{ Name: "IndexEmptyFails", ScriptCode: ` - a = () + var a = () echo $a[0] `, Fails: true, diff --git a/tests/stringindex_test.go b/tests/stringindex_test.go index e177e7b0..951ab02e 100644 --- a/tests/stringindex_test.go +++ b/tests/stringindex_test.go @@ -11,7 +11,7 @@ func TestStringIndexing(t *testing.T) { tester.TestCase{ Name: "IterateEmpty", ScriptCode: ` - a = "" + var a = "" for x in $a { exit("1") } @@ -22,7 +22,7 @@ func TestStringIndexing(t *testing.T) { tester.TestCase{ Name: "IndexEmptyFails", ScriptCode: ` - a = "" + var a = "" echo $a[0] `, Fails: true, @@ -31,7 +31,7 @@ func TestStringIndexing(t *testing.T) { tester.TestCase{ Name: "IsImmutable", ScriptCode: ` - a = "12" + var a = "12" a[0] = "2" echo $a `, @@ -44,7 +44,7 @@ func TestStringIndexingASCII(t *testing.T) { tester.Run(t, Nashcmd, tester.TestCase{Name: "PositionalAccess", ScriptCode: ` - a = "12" + var a = "12" echo $a[0] echo $a[1] `, @@ -53,8 +53,8 @@ func TestStringIndexingASCII(t *testing.T) { tester.TestCase{ Name: "PositionalAccessReturnsString", ScriptCode: ` - a = "12" - x = $a[0] + $a[1] + var a = "12" + var x = $a[0] + $a[1] echo $x `, ExpectStdout: "12\n", @@ -62,8 +62,8 @@ func TestStringIndexingASCII(t *testing.T) { tester.TestCase{ Name: "Len", ScriptCode: ` - a = "12" - l <= len($a) + var a = "12" + var l <= len($a) echo $l `, ExpectStdout: "2\n", @@ -71,7 +71,7 @@ func TestStringIndexingASCII(t *testing.T) { tester.TestCase{ Name: "Iterate", ScriptCode: ` - a = "123" + var a = "123" for x in $a { echo $x } @@ -81,7 +81,7 @@ func TestStringIndexingASCII(t *testing.T) { tester.TestCase{ Name: "IndexOutOfRangeFails", ScriptCode: ` - a = "123" + var a = "123" echo $a[3] `, Fails: true, @@ -94,7 +94,7 @@ func TestStringIndexingNonASCII(t *testing.T) { tester.Run(t, Nashcmd, tester.TestCase{Name: "PositionalAccess", ScriptCode: ` - a = "⌘⌘" + var a = "⌘⌘" echo $a[0] echo $a[1] `, @@ -103,7 +103,7 @@ func TestStringIndexingNonASCII(t *testing.T) { tester.TestCase{ Name: "Iterate", ScriptCode: ` - a = "⌘⌘" + var a = "⌘⌘" for x in $a { echo $x } @@ -113,8 +113,8 @@ func TestStringIndexingNonASCII(t *testing.T) { tester.TestCase{ Name: "PositionalAccessReturnsString", ScriptCode: ` - a = "⌘⌘" - x = $a[0] + $a[1] + var a = "⌘⌘" + var x = $a[0] + $a[1] echo $x `, ExpectStdout: "⌘⌘\n", @@ -122,8 +122,8 @@ func TestStringIndexingNonASCII(t *testing.T) { tester.TestCase{ Name: "Len", ScriptCode: ` - a = "⌘⌘" - l <= len($a) + var a = "⌘⌘" + var l <= len($a) echo $l `, ExpectStdout: "2\n", @@ -131,7 +131,7 @@ func TestStringIndexingNonASCII(t *testing.T) { tester.TestCase{ Name: "IndexOutOfRangeFails", ScriptCode: ` - a = "⌘⌘" + var a = "⌘⌘" echo $a[2] `, Fails: true, From a5f448484c76c798f78a956a2d85b2f63856e1ff Mon Sep 17 00:00:00 2001 From: i4k Date: Wed, 24 Jan 2018 11:42:08 -0200 Subject: [PATCH 23/27] redeclare variable is not an error Signed-off-by: i4k --- internal/sh/shell.go | 53 ----------------------------------- internal/sh/shell_test.go | 3 +- internal/sh/shell_var_test.go | 26 +++++++++++++++-- 3 files changed, 25 insertions(+), 57 deletions(-) diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 552ddc42..d20a57bf 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -1910,7 +1910,6 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, where bool) error { if len(assign.Names) == 2 { outbuf, _, status, _ := shell.execCmdOutput(cmd, false, false) err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), Local) - if err != nil { return err } @@ -2009,44 +2008,9 @@ func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { ) } - var ( - initVarNames []*ast.NameNode - initVarValues []ast.Expr - - setVarNames []*ast.NameNode - setVarValues []ast.Expr - ) - for i := 0; i < len(assign.Names); i++ { name := assign.Names[i] value := assign.Values[i] - _, varLocalExists := shell.Getvar(name.Ident, Local) - if !varLocalExists { - initVarNames = append(initVarNames, name) - initVarValues = append(initVarValues, value) - } else { - setVarNames = append(setVarNames, name) - setVarValues = append(setVarValues, value) - } - } - - // the 'var' keyword requires that at least one of the - // variables in the assignment do not exists. - if len(initVarNames) == 0 { - varNames := []string{} - for _, n := range setVarNames { - varNames = append(varNames, n.String()) - } - return errors.NewEvalError(shell.filename, - assign, "Cannot redeclare variables (%s) in current block, at least one of them must be new", - strings.Join(varNames, ", ")) - } - - // initialize variables - // set their values in the current scope - for i := 0; i < len(initVarNames); i++ { - name := initVarNames[i] - value := initVarValues[i] err := shell.initVar(name, value) if err != nil { @@ -2054,23 +2018,6 @@ func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { } } - // set the rest of the variables - // but in the scope where they reside - for i := 0; i < len(setVarNames); i++ { - name := initVarNames[i] - value := initVarValues[i] - - obj, err := shell.evalExpr(value) - if err == nil { - return err - } - - err = shell.setvar(name, obj, false) - if err != nil { - return err - } - } - return nil } diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 994ed9db..61837364 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -116,6 +116,8 @@ func testShellExec(t *testing.T, shell *Shell, testcase execTestCase) { testcase.expectedErr, err.Error()) } + } else if testcase.expectedErr != "" { + t.Fatalf("Expected error[%s] but got nil", testcase.expectedErr) } if testcase.expectedStdout != string(bout.Bytes()) { @@ -138,7 +140,6 @@ func testShellExec(t *testing.T, shell *Shell, testcase execTestCase) { } func testExec(t *testing.T, testcase execTestCase) { - f, teardown := setup(t) defer teardown() diff --git a/internal/sh/shell_var_test.go b/internal/sh/shell_var_test.go index f41fd166..02f48427 100644 --- a/internal/sh/shell_var_test.go +++ b/internal/sh/shell_var_test.go @@ -15,9 +15,9 @@ func TestVarAssign(t *testing.T) { expectedErr: `:1:0: Variable 'a' is not initialized. Use 'var a = '`, }, { - desc: "variable already initialized", - code: `var a = "1"; var a = "2"; echo -n $a`, - expectedErr: `:1:17: Cannot redeclare variables (a) in current block, at least one of them must be new`, + desc: "variable already initialized", + code: `var a = "1"; var a = "2"; echo -n $a`, + expectedStdout: "2", }, { desc: "variable set", @@ -38,3 +38,23 @@ func TestVarAssign(t *testing.T) { }) } } + +func TestVarExecAssign(t *testing.T) { + for _, test := range []execTestCase{ + { + desc: "simple exec var", + code: `var heart <= echo -n "feed both wolves" + echo -n $heart`, + expectedStdout: "feed both wolves", + }, + { + desc: "var do not exists", + code: `__a <= echo -n "fury"`, + expectedErr: "variable do not exists", + }, + } { + t.Run(test.desc, func(t *testing.T) { + testExec(t, test) + }) + } +} From 682e82fb59cf2fc08346e8daaeb471952b56193f Mon Sep 17 00:00:00 2001 From: i4k Date: Tue, 30 Jan 2018 02:00:39 -0200 Subject: [PATCH 24/27] fix exec with var Signed-off-by: i4k --- internal/sh/shell.go | 20 ++++++++++---------- internal/sh/shell_test.go | 7 ++++--- internal/sh/shell_var_test.go | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/internal/sh/shell.go b/internal/sh/shell.go index d20a57bf..6a4c7946 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -687,7 +687,7 @@ func (shell *Shell) executeNode(node ast.Node) ([]sh.Obj, error) { case ast.NodeAssign: err = shell.executeAssignment(node.(*ast.AssignNode)) case ast.NodeExecAssign: - err = shell.executeExecAssign(node.(*ast.ExecAssignNode), false) + err = shell.executeExecAssign(node.(*ast.ExecAssignNode), Global) case ast.NodeCommand: _, err = shell.executeCommand(node.(*ast.CommandNode)) case ast.NodePipe: @@ -1893,7 +1893,7 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, where bool) error { // In this case the script must abort in case of errors if len(assign.Names) == 1 { outbuf, _, _, cmdErr := shell.execCmdOutput(cmd, false, false) - err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), Local) + err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), where) if cmdErr != nil { return cmdErr @@ -1909,12 +1909,12 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, where bool) error { // Only getting stdout and exit status if len(assign.Names) == 2 { outbuf, _, status, _ := shell.execCmdOutput(cmd, false, false) - err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), Local) + err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), where) if err != nil { return err } - err = shell.setvar(assign.Names[1], status, Local) + err = shell.setvar(assign.Names[1], status, where) if err != nil { return err } @@ -1924,9 +1924,9 @@ func (shell *Shell) executeExecAssignCmd(v ast.Node, where bool) error { if len(assign.Names) == 3 { outbuf, errbuf, status, _ := shell.execCmdOutput(cmd, true, false) - err1 := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), Local) - err2 := shell.setvar(assign.Names[1], sh.NewStrObj(string(errbuf)), Local) - err3 := shell.setvar(assign.Names[2], status, Local) + err1 := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), where) + err2 := shell.setvar(assign.Names[1], sh.NewStrObj(string(errbuf)), where) + err3 := shell.setvar(assign.Names[2], status, where) errs := []error{err1, err2, err3} for _, e := range errs { @@ -1996,7 +1996,7 @@ func (shell *Shell) initVar(name *ast.NameNode, value ast.Expr) error { if err != nil { return err } - return shell.setvar(name, obj, true) + return shell.setvar(name, obj, Local) } func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { @@ -2022,7 +2022,7 @@ func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { } func (shell *Shell) executeVarExecAssign(v *ast.VarExecAssignDeclNode) error { - return shell.executeExecAssign(v.ExecAssign, true) + return shell.executeExecAssign(v.ExecAssign, Local) } func (shell *Shell) executeAssignment(v *ast.AssignNode) error { @@ -2042,7 +2042,7 @@ func (shell *Shell) executeAssignment(v *ast.AssignNode) error { return err } - err = shell.setvar(name, obj, false) + err = shell.setvar(name, obj, Global) if err != nil { return err } diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 61837364..79964297 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -140,6 +140,7 @@ func testShellExec(t *testing.T, shell *Shell, testcase execTestCase) { } func testExec(t *testing.T, testcase execTestCase) { + t.Helper() f, teardown := setup(t) defer teardown() @@ -512,7 +513,7 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { desc: "list assignment", code: `var l = (0 1 2 3) var a = "2" - l[$a], err <= echo -n "666" + var l[$a], err <= echo -n "666" if $err == "0" { echo -n $l }`, @@ -2120,7 +2121,7 @@ func TestExecuteErrorSuppressionAll(t *testing.T) { return } - err = shell.Exec("-input-", `_, status <= command-not-exists`) + err = shell.Exec("-input-", `var _, status <= command-not-exists`) if err != nil { t.Errorf("Expected to not fail...: %s", err.Error()) return @@ -2132,7 +2133,7 @@ func TestExecuteErrorSuppressionAll(t *testing.T) { return } - err = shell.Exec("-input-", `_, status <= echo works`) + err = shell.Exec("-input-", `var _, status <= echo works`) if err != nil { t.Error(err) return diff --git a/internal/sh/shell_var_test.go b/internal/sh/shell_var_test.go index 02f48427..ee288646 100644 --- a/internal/sh/shell_var_test.go +++ b/internal/sh/shell_var_test.go @@ -50,7 +50,7 @@ func TestVarExecAssign(t *testing.T) { { desc: "var do not exists", code: `__a <= echo -n "fury"`, - expectedErr: "variable do not exists", + expectedErr: ":1:0: Variable '__a' is not initialized. Use 'var __a = '", }, } { t.Run(test.desc, func(t *testing.T) { From 9987d0e1ee7b1fd391e8f4c66454c3be4f95afc2 Mon Sep 17 00:00:00 2001 From: i4k Date: Wed, 31 Jan 2018 10:31:42 -0200 Subject: [PATCH 25/27] fix 3 variable exec assignment Signed-off-by: i4k --- internal/sh/shell_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 79964297..136c1d00 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -501,7 +501,7 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { { desc: "list assignment", code: `var l = (0 1 2 3) - l[0], err <= echo -n 666 + var l[0], err <= echo -n 666 if $err == "0" { echo -n $l }`, @@ -523,10 +523,10 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) { }, { desc: "cmd assignment works with 1 or 2 variables", - code: "var out, err1, err2 <= echo something", + code: "var out, err, status <= echo something", expectedStdout: "", expectedStderr: "", - expectedErr: ":1:4: multiple assignment of commands requires two variable names, but got 3", + expectedErr: "", }, { desc: "ignore error", From af62ef848488e769dca51c34e2dbb4633cbb644c Mon Sep 17 00:00:00 2001 From: i4k Date: Sat, 3 Feb 2018 01:12:00 -0200 Subject: [PATCH 26/27] add more tests Signed-off-by: i4k --- Makefile | 2 + hack/check.sh | 4 +- internal/sh/fncall.go | 9 +- internal/sh/shell.go | 325 ++++++++++++++++++++----------- internal/sh/shell_test.go | 4 +- internal/sh/shell_var_test.go | 51 ++++- nash.go | 23 ++- nash_test.go | 2 +- stdbin/write/fd_linux.go | 15 ++ stdbin/write/fd_windows.go | 13 ++ stdbin/write/main.go | 38 ++++ stdbin/write/test.sh | 12 ++ stdbin/write/write.go | 54 +++++ stdbin/write/write_linux_test.sh | 31 +++ stdbin/write/write_test.sh | 23 +++ tests/cfg.go | 16 +- 16 files changed, 487 insertions(+), 135 deletions(-) create mode 100644 stdbin/write/fd_linux.go create mode 100644 stdbin/write/fd_windows.go create mode 100644 stdbin/write/main.go create mode 100644 stdbin/write/test.sh create mode 100644 stdbin/write/write.go create mode 100644 stdbin/write/write_linux_test.sh create mode 100644 stdbin/write/write_test.sh diff --git a/Makefile b/Makefile index 2629d665..ea3299f0 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ build: cd cmd/nashfmt && go build $(buildargs) cd stdbin/mkdir && go build $(buildargs) cd stdbin/pwd && go build $(buildargs) + cd stdbin/write && go build $(buildargs) NASHPATH=$(HOME)/nash NASHROOT=$(HOME)/nashroot @@ -27,6 +28,7 @@ install: build cp -pr ./stdlib $(NASHROOT)/stdlib cp -pr ./stdbin/mkdir/mkdir $(NASHROOT)/bin/mkdir cp -pr ./stdbin/pwd/pwd $(NASHROOT)/bin/pwd + cp -pr ./stdbin/write/write $(NASHROOT)/bin/write docsdeps: go get github.com/katcipis/mdtoc diff --git a/hack/check.sh b/hack/check.sh index b629418e..98331f77 100755 --- a/hack/check.sh +++ b/hack/check.sh @@ -33,9 +33,9 @@ done $GO tool cover -func coverage.txt -echo "running stdlib tests" +echo "running stdlib and stdbin tests" export NASHPATH=`pwd` -tests=$(find ./stdlib -name "*_test.sh") +tests=$(find ./stdlib ./stdbin -name "*_test.sh") for t in ${tests[*]} do diff --git a/internal/sh/fncall.go b/internal/sh/fncall.go index b88f881f..4fc25915 100644 --- a/internal/sh/fncall.go +++ b/internal/sh/fncall.go @@ -100,7 +100,7 @@ func (fn *UserFn) SetArgs(args []sh.Obj) error { // and user supplied no argument... // then only initialize the variadic variable to // empty list - fn.subshell.Setvar(fn.argNames[0].Name, sh.NewListObj([]sh.Obj{}), true) + fn.subshell.Newvar(fn.argNames[0].Name, sh.NewListObj([]sh.Obj{})) return nil } } @@ -118,9 +118,9 @@ func (fn *UserFn) SetArgs(args []sh.Obj) error { valist = append(valist, arg) } valistarg := sh.NewListObj(valist) - fn.subshell.Setvar(argName, valistarg, true) + fn.subshell.Newvar(argName, valistarg) } else { - fn.subshell.Setvar(argName, arg, true) + fn.subshell.Newvar(argName, arg) } } @@ -130,7 +130,8 @@ func (fn *UserFn) SetArgs(args []sh.Obj) error { if !last.IsVariadic { return errors.NewError("internal error: optional arguments only for variadic parameter") } - fn.subshell.Setvar(last.Name, sh.NewListObj([]sh.Obj{}), true) + + fn.subshell.Newvar(last.Name, sh.NewListObj([]sh.Obj{})) } return nil diff --git a/internal/sh/shell.go b/internal/sh/shell.go index 6a4c7946..fa901e72 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -28,11 +28,6 @@ const ( defPrompt = "\033[31mλ>\033[0m " ) -const ( - Local = true - Global = false -) - type ( // Env is the environment map of lists Env map[string]sh.Obj @@ -177,7 +172,7 @@ func (shell *Shell) initEnv(processEnv []string) error { argv := sh.NewListObj(largs) shell.Setenv("argv", argv) - shell.Setvar("argv", argv, Local) + shell.Newvar("argv", argv) for _, penv := range processEnv { var value sh.Obj @@ -193,19 +188,19 @@ func (shell *Shell) initEnv(processEnv []string) error { value = sh.NewStrObj(strings.Join(p[1:], "=")) shell.Setenv(p[0], value) - shell.Setvar(p[0], value, Local) + shell.Newvar(p[0], value) } } pidVal := sh.NewStrObj(strconv.Itoa(os.Getpid())) shell.Setenv("PID", pidVal) - shell.Setvar("PID", pidVal, Local) + shell.Newvar("PID", pidVal) if _, ok := shell.Getenv("SHELL"); !ok { shellVal := sh.NewStrObj(nashdAutoDiscover()) shell.Setenv("SHELL", shellVal) - shell.Setvar("SHELL", shellVal, Local) + shell.Newvar("SHELL", shellVal) } cwd, err := os.Getwd() @@ -216,7 +211,7 @@ func (shell *Shell) initEnv(processEnv []string) error { cwdObj := sh.NewStrObj(cwd) shell.Setenv("PWD", cwdObj) - shell.Setvar("PWD", cwdObj, Local) + shell.Newvar("PWD", cwdObj) return nil } @@ -284,7 +279,7 @@ func (shell *Shell) Setenv(name string, value sh.Obj) { return } - shell.Setvar(name, value, Local) + shell.Newvar(name, value) shell.env[name] = value os.Setenv(name, value.String()) @@ -300,24 +295,33 @@ func (shell *Shell) SetEnviron(processEnv []string) { if len(p) == 2 { value = sh.NewStrObj(p[1]) - shell.Setvar(p[0], value, Local) shell.Setenv(p[0], value) + shell.Newvar(p[0], value) } } } -func (shell *Shell) Getvar(name string, where bool) (sh.Obj, bool) { +// GetLocalvar returns a local scoped variable. +func (shell *Shell) GetLocalvar(name string) (sh.Obj, bool) { + v, ok := shell.vars[name] + return v, ok +} + +// Getvar returns the value of the variable name. It could look in their +// parent scopes if not found locally. +func (shell *Shell) Getvar(name string) (sh.Obj, bool) { if value, ok := shell.vars[name]; ok { return value, ok } - if where == Global && shell.parent != nil { - return shell.parent.Getvar(name, Global) + if shell.parent != nil { + return shell.parent.Getvar(name) } return nil, false } +// GetFn returns the function name or error if not found. func (shell *Shell) GetFn(name string) (*sh.FnObj, error) { shell.logf("Looking for function '%s' on shell '%s'\n", name, shell.name) if obj, ok := shell.vars[name]; ok { @@ -351,22 +355,29 @@ func (shell *Shell) Getbindfn(cmdName string) (sh.FnDef, bool) { return nil, false } -func (shell *Shell) Setvar(name string, value sh.Obj, where bool) bool { +// Newvar creates a new variable in the scope. +func (shell *Shell) Newvar(name string, value sh.Obj) { + shell.vars[name] = value +} + +// Setvar updates the value of an existing variable. If the variable +// doesn't exist in current scope it looks up in their parent scopes. +// It returns true if the variable was found and updated. +func (shell *Shell) Setvar(name string, value sh.Obj) bool { _, ok := shell.vars[name] - if ok || where == Local { + if ok { shell.vars[name] = value return true } if shell.parent != nil { - return shell.parent.Setvar(name, value, Global) + return shell.parent.Setvar(name, value) } return false } -func (shell *Shell) IsFn() bool { return shell.isFn } - +func (shell *Shell) IsFn() bool { return shell.isFn } func (shell *Shell) SetIsFn(b bool) { shell.isFn = b } // SetNashdPath sets an alternativa path to nashd @@ -410,7 +421,7 @@ func (shell *Shell) SetRepr(a string) { func (shell *Shell) setupBuiltin() { for name, constructor := range builtin.Constructors() { fnDef := newBuiltinFnDef(name, shell, constructor) - _ = shell.Setvar(name, sh.NewFnObj(fnDef), Local) + shell.Newvar(name, sh.NewFnObj(fnDef)) } } @@ -446,12 +457,12 @@ func (shell *Shell) setup() error { if shell.env["PROMPT"] == nil { pobj := sh.NewStrObj(defPrompt) shell.Setenv("PROMPT", pobj) - shell.Setvar("PROMPT", pobj, Local) + shell.Newvar("PROMPT", pobj) } - _, ok := shell.Getvar("_", Global) + _, ok := shell.Getvar("_") if !ok { - shell.Setvar("_", sh.NewStrObj(""), Local) + shell.Newvar("_", sh.NewStrObj("")) } shell.setupBuiltin() @@ -545,9 +556,47 @@ func (shell *Shell) ExecFile(path string) error { return shell.Exec(path, string(content)) } -func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj, where bool) error { +func (shell *Shell) newvar(name *ast.NameNode, value sh.Obj) error { if name.Index == nil { - if !shell.Setvar(name.Ident, value, where) { + shell.Newvar(name.Ident, value) + return nil + } + + // handles ident[x] = v + + obj, ok := shell.Getvar(name.Ident) + if !ok { + return errors.NewEvalError(shell.filename, + name, "Variable %s not found", name.Ident) + } + + index, err := shell.evalIndex(name.Index) + if err != nil { + return err + } + + col, err := sh.NewWriteableCollection(obj) + if err != nil { + return errors.NewEvalError(shell.filename, name, err.Error()) + } + + err = col.Set(index, value) + if err != nil { + return errors.NewEvalError( + shell.filename, + name, + "error[%s] setting var", + err, + ) + } + + shell.Newvar(name.Ident, obj) + return nil +} + +func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj) error { + if name.Index == nil { + if !shell.Setvar(name.Ident, value) { return errors.NewEvalError(shell.filename, name, "Variable '%s' is not initialized. Use 'var %s = '", name, name) @@ -555,7 +604,7 @@ func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj, where bool) error { return nil } - obj, ok := shell.Getvar(name.Ident, Global) + obj, ok := shell.Getvar(name.Ident) if !ok { return errors.NewEvalError(shell.filename, name, "Variable %s not found", name.Ident) @@ -581,7 +630,7 @@ func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj, where bool) error { ) } - if !shell.Setvar(name.Ident, obj, where) { + if !shell.Setvar(name.Ident, obj) { return errors.NewEvalError(shell.filename, name, "Variable '%s' is not initialized. Use 'var %s = '", name, name) @@ -589,6 +638,67 @@ func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj, where bool) error { return nil } +func (shell *Shell) setvars(names []*ast.NameNode, values []sh.Obj) error { + for i := 0; i < len(names); i++ { + err := shell.setvar(names[i], values[i]) + if err != nil { + return err + } + } + return nil +} + +func (shell *Shell) newvars(names []*ast.NameNode, values []sh.Obj) { + for i := 0; i < len(names); i++ { + shell.newvar(names[i], values[i]) + } +} + +func (shell *Shell) setcmdvars(names []*ast.NameNode, stdout, stderr, status sh.Obj) error { + if len(names) == 3 { + err := shell.setvar(names[0], stdout) + if err != nil { + return err + } + err = shell.setvar(names[1], stderr) + if err != nil { + return err + } + return shell.setvar(names[2], status) + } else if len(names) == 2 { + err := shell.setvar(names[0], stdout) + if err != nil { + return err + } + return shell.setvar(names[1], status) + } else if len(names) == 1 { + return shell.setvar(names[0], stdout) + } + + panic(fmt.Sprintf("internal error: expects 1 <= len(names) <= 3,"+ + " but got %d", + len(names))) + + return nil +} + +func (shell *Shell) newcmdvars(names []*ast.NameNode, stdout, stderr, status sh.Obj) { + if len(names) == 3 { + shell.newvar(names[0], stdout) + shell.newvar(names[1], stderr) + shell.newvar(names[2], status) + } else if len(names) == 2 { + shell.newvar(names[0], stdout) + shell.newvar(names[1], status) + } else if len(names) == 1 { + shell.newvar(names[0], stdout) + } else { + panic(fmt.Sprintf("internal error: expects 1 <= len(names) <= 3,"+ + " but got %d", + len(names))) + } +} + // evalConcat reveives the AST representation of a concatenation of objects and // returns the string representation, or error. func (shell *Shell) evalConcat(path ast.Expr) (string, error) { @@ -687,7 +797,7 @@ func (shell *Shell) executeNode(node ast.Node) ([]sh.Obj, error) { case ast.NodeAssign: err = shell.executeAssignment(node.(*ast.AssignNode)) case ast.NodeExecAssign: - err = shell.executeExecAssign(node.(*ast.ExecAssignNode), Global) + err = shell.executeExecAssign(node.(*ast.ExecAssignNode)) case ast.NodeCommand: _, err = shell.executeCommand(node.(*ast.CommandNode)) case ast.NodePipe: @@ -1556,7 +1666,7 @@ func (shell *Shell) evalVariable(a ast.Expr) (sh.Obj, error) { vexpr := a.(*ast.VarExpr) varName := vexpr.Name - if value, ok = shell.Getvar(varName[1:], Global); !ok { + if value, ok = shell.Getvar(varName[1:]); !ok { return nil, errors.NewEvalError(shell.filename, a, "Variable %s not set on shell %s", varName, shell.name) } @@ -1579,7 +1689,7 @@ func (shell *Shell) evalArgVariable(a ast.Expr) ([]sh.Obj, error) { } vexpr := a.(*ast.VarExpr) - if value, ok = shell.Getvar(vexpr.Name[1:], Global); !ok { + if value, ok = shell.Getvar(vexpr.Name[1:]); !ok { return nil, errors.NewEvalError(shell.filename, a, "Variable %s not set on shell %s", vexpr.Name, shell.name) @@ -1748,7 +1858,7 @@ func (shell *Shell) executeSetenvAssign(assign *ast.AssignNode) error { if err != nil { return err } - obj, ok := shell.Getvar(name.Ident, Local) + obj, ok := shell.GetLocalvar(name.Ident) if !ok { return errors.NewEvalError(shell.filename, assign, @@ -1762,13 +1872,13 @@ func (shell *Shell) executeSetenvAssign(assign *ast.AssignNode) error { } func (shell *Shell) executeSetenvExec(assign *ast.ExecAssignNode) error { - err := shell.executeExecAssign(assign, true) + err := shell.executeExecAssign(assign) if err != nil { return err } for i := 0; i < len(assign.Names); i++ { name := assign.Names[i] - obj, ok := shell.Getvar(name.Ident, Local) + obj, ok := shell.GetLocalvar(name.Ident) if !ok { return errors.NewEvalError(shell.filename, assign, @@ -1800,7 +1910,7 @@ func (shell *Shell) executeSetenv(v *ast.SetenvNode) error { assign) } - varValue, ok = shell.Getvar(v.Name, Global) + varValue, ok = shell.Getvar(v.Name) if !ok { return errors.NewEvalError(shell.filename, v, "Variable '%s' not set on shell %s", v.Name, @@ -1836,7 +1946,8 @@ func (shell *Shell) concatElements(expr *ast.ConcatExpr) (string, error) { return value, nil } -func (shell *Shell) execCmdOutput(cmd ast.Node, getstderr, ignoreError bool) ([]byte, []byte, sh.Obj, error) { +func (shell *Shell) execCmdOutput(cmd ast.Node, + getstderr, ignoreError bool) ([]byte, []byte, sh.Obj, error) { var ( outBuf, errBuf bytes.Buffer err error @@ -1885,64 +1996,22 @@ func (shell *Shell) execCmdOutput(cmd ast.Node, getstderr, ignoreError bool) ([] return trimnl(outb), trimnl(errb), status, err } -func (shell *Shell) executeExecAssignCmd(v ast.Node, where bool) error { +func (shell *Shell) executeExecAssignCmd(v ast.Node) (stdout, stderr, status sh.Obj, err error) { assign := v.(*ast.ExecAssignNode) cmd := assign.Command() - // Only getting command output - // In this case the script must abort in case of errors - if len(assign.Names) == 1 { - outbuf, _, _, cmdErr := shell.execCmdOutput(cmd, false, false) - err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), where) - - if cmdErr != nil { - return cmdErr - } - - if err != nil { - return err - } - - return nil - } - - // Only getting stdout and exit status - if len(assign.Names) == 2 { - outbuf, _, status, _ := shell.execCmdOutput(cmd, false, false) - err := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), where) - if err != nil { - return err - } - - err = shell.setvar(assign.Names[1], status, where) - if err != nil { - return err - } - - return nil - } + mustIgnoreErr := len(assign.Names) > 1 + collectStderr := len(assign.Names) == 3 - if len(assign.Names) == 3 { - outbuf, errbuf, status, _ := shell.execCmdOutput(cmd, true, false) - err1 := shell.setvar(assign.Names[0], sh.NewStrObj(string(outbuf)), where) - err2 := shell.setvar(assign.Names[1], sh.NewStrObj(string(errbuf)), where) - err3 := shell.setvar(assign.Names[2], status, where) - errs := []error{err1, err2, err3} - - for _, e := range errs { - if e != nil { - return e - } - } - return nil + outb, errb, status, err := shell.execCmdOutput(cmd, collectStderr, mustIgnoreErr) + if err != nil { + return nil, nil, nil, err } - return errors.NewEvalError(shell.filename, - v, "multiple assignment of commands requires between one and three variable names, but got %d", - len(assign.Names)) + return sh.NewStrObj(string(outb)), sh.NewStrObj(string(errb)), status, nil } -func (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode, where bool) error { +func (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode) ([]sh.Obj, error) { var ( err error fnValues []sh.Obj @@ -1950,45 +2019,50 @@ func (shell *Shell) executeExecAssignFn(assign *ast.ExecAssignNode, where bool) cmd := assign.Command() if cmd.Type() != ast.NodeFnInv { - return errors.NewEvalError(shell.filename, + return nil, errors.NewEvalError(shell.filename, cmd, "Invalid node type (%v). Expected function call", cmd) } fnValues, err = shell.executeFnInv(cmd.(*ast.FnInvNode)) if err != nil { - return err + return nil, err } if len(fnValues) != len(assign.Names) { - return errors.NewEvalError(shell.filename, + return nil, errors.NewEvalError(shell.filename, assign, "Functions returns %d objects, but statement expects %d", len(fnValues), len(assign.Names)) } - for i := 0; i < len(assign.Names); i++ { - err := shell.setvar(assign.Names[i], fnValues[i], where) + return fnValues, nil +} + +func (shell *Shell) executeExecAssign(v *ast.ExecAssignNode) (err error) { + exec := v.Command() + switch exec.Type() { + case ast.NodeFnInv: + var values []sh.Obj + values, err = shell.executeExecAssignFn(v) + if err != nil { + return err + } + err = shell.setvars(v.Names, values) + case ast.NodeCommand, ast.NodePipe: + var stdout, stderr, status sh.Obj + stdout, stderr, status, err = shell.executeExecAssignCmd(v) if err != nil { return err } - } - - return nil -} - -func (shell *Shell) executeExecAssign(v *ast.ExecAssignNode, where bool) error { - assign := v.Command() - if assign.Type() == ast.NodeFnInv { - return shell.executeExecAssignFn(v, where) - } else if assign.Type() == ast.NodeCommand || - assign.Type() == ast.NodePipe { - return shell.executeExecAssignCmd(v, where) + err = shell.setcmdvars(v.Names, stdout, stderr, status) + default: + err = errors.NewEvalError(shell.filename, + exec, "Invalid node type (%v). Expected function call, command or pipe", + exec) } - return errors.NewEvalError(shell.filename, - assign, "Invalid node type (%v). Expected function call, command or pipe", - assign) + return err } func (shell *Shell) initVar(name *ast.NameNode, value ast.Expr) error { @@ -1996,7 +2070,7 @@ func (shell *Shell) initVar(name *ast.NameNode, value ast.Expr) error { if err != nil { return err } - return shell.setvar(name, obj, Local) + return shell.newvar(name, obj) } func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { @@ -2021,8 +2095,32 @@ func (shell *Shell) executeVarAssign(v *ast.VarAssignDeclNode) error { return nil } -func (shell *Shell) executeVarExecAssign(v *ast.VarExecAssignDeclNode) error { - return shell.executeExecAssign(v.ExecAssign, Local) +func (shell *Shell) executeVarExecAssign(v *ast.VarExecAssignDeclNode) (err error) { + assign := v.ExecAssign + exec := assign.Command() + switch exec.Type() { + case ast.NodeFnInv: + var values []sh.Obj + values, err = shell.executeExecAssignFn(assign) + if err != nil { + return err + } + shell.newvars(assign.Names, values) + case ast.NodeCommand, ast.NodePipe: + var stdout, stderr, status sh.Obj + stdout, stderr, status, err = shell.executeExecAssignCmd(assign) + if err != nil { + return err + } + + shell.newcmdvars(assign.Names, stdout, stderr, status) + default: + err = errors.NewEvalError(shell.filename, + exec, "Invalid node type (%v). Expected function call, command or pipe", + exec) + } + + return err } func (shell *Shell) executeAssignment(v *ast.AssignNode) error { @@ -2042,7 +2140,7 @@ func (shell *Shell) executeAssignment(v *ast.AssignNode) error { return err } - err = shell.setvar(name, obj, Global) + err = shell.setvar(name, obj) if err != nil { return err } @@ -2306,8 +2404,7 @@ func (shell *Shell) executeFor(n *ast.ForNode) ([]sh.Obj, error) { return nil, errors.NewEvalError(shell.filename, inExpr, "unexpected error[%s] during iteration", err) } - shell.Setvar(id, val, Local) - + shell.Newvar(id, val) objs, err := shell.executeTree(n.Tree(), false) type ( @@ -2357,7 +2454,7 @@ func (shell *Shell) executeFnDecl(n *ast.FnDeclNode) error { return err } - shell.Setvar(n.Name(), sh.NewFnObj(fnDef), Local) + shell.Newvar(n.Name(), sh.NewFnObj(fnDef)) shell.logf("Function %s declared on '%s'", n.Name(), shell.name) return nil } @@ -2387,5 +2484,5 @@ func (shell *Shell) executeIf(n *ast.IfNode) ([]sh.Obj, error) { return shell.executeIfNotEqual(n) } - return nil, fmt.Errorf("Invalid operation '%s'.", op) + return nil, fmt.Errorf("invalid operation '%s'", op) } diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index 136c1d00..99166693 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -2127,7 +2127,7 @@ func TestExecuteErrorSuppressionAll(t *testing.T) { return } - scode, ok := shell.Getvar("status", Local) + scode, ok := shell.GetLocalvar("status") if !ok || scode.Type() != sh.StringType || scode.String() != strconv.Itoa(ENotFound) { t.Errorf("Invalid status code %v", scode) return @@ -2139,7 +2139,7 @@ func TestExecuteErrorSuppressionAll(t *testing.T) { return } - scode, ok = shell.Getvar("status", Local) + scode, ok = shell.GetLocalvar("status") if !ok || scode.Type() != sh.StringType || scode.String() != "0" { t.Errorf("Invalid status code %v", scode) return diff --git a/internal/sh/shell_var_test.go b/internal/sh/shell_var_test.go index ee288646..5c6c47ed 100644 --- a/internal/sh/shell_var_test.go +++ b/internal/sh/shell_var_test.go @@ -1,6 +1,11 @@ package sh -import "testing" +import ( + "fmt" + "testing" + + "github.com/NeowayLabs/nash/tests" +) func TestVarAssign(t *testing.T) { for _, test := range []execTestCase{ @@ -40,6 +45,7 @@ func TestVarAssign(t *testing.T) { } func TestVarExecAssign(t *testing.T) { + for _, test := range []execTestCase{ { desc: "simple exec var", @@ -52,6 +58,49 @@ func TestVarExecAssign(t *testing.T) { code: `__a <= echo -n "fury"`, expectedErr: ":1:0: Variable '__a' is not initialized. Use 'var __a = '", }, + { + desc: "multiple var same name", + code: `var a = "1" + var a = "2" + var a = "3" + echo -n $a`, + expectedStdout: "3", + }, + { + desc: "multiple var same name with exec", + code: `var a <= echo -n "1" + var a <= echo -n "hello" + echo -n $a`, + expectedStdout: "hello", + }, + { + desc: "first variable is stdout", + code: `var out <= echo -n "hello" + echo -n $out`, + expectedStdout: "hello", + }, + { + desc: "two variable, first stdout and second is status", + code: `var stdout, status <= echo -n "bleh" + echo -n $stdout $status`, + expectedStdout: "bleh 0", + }, + { + desc: "three variables, stdout empty, stderr with data, status", + code: fmt.Sprintf(`var out, err, st <= %s/write/write /dev/stderr "hello" + echo $out + echo $err + echo -n $st`, tests.Stdbindir), + expectedStdout: "\nhello\n0", + }, + { + desc: "three variables, stdout with data, stderr empty, status", + code: fmt.Sprintf(`var out, err, st <= %s/write/write /dev/stdout "hello" + echo $out + echo $err + echo -n $st`, tests.Stdbindir), + expectedStdout: "hello\n\n0", + }, } { t.Run(test.desc, func(t *testing.T) { testExec(t, test) diff --git a/nash.go b/nash.go index 923a4ccf..0cf2e1fc 100644 --- a/nash.go +++ b/nash.go @@ -50,7 +50,7 @@ func (nash *Shell) SetInteractive(b bool) { func (nash *Shell) SetDotDir(path string) { obj := sh.NewStrObj(path) nash.interp.Setenv("NASHPATH", obj) - nash.interp.Setvar("NASHPATH", obj, true) + nash.interp.Newvar("NASHPATH", obj) } // DotDir returns the value of the NASHPATH environment variable @@ -177,18 +177,29 @@ func (nash *Shell) SetStdin(in io.Reader) { nash.interp.SetStdin(in) } -func (nash *Shell) Stdin() io.Reader { return nash.interp.Stdin() } +// Stdin is the interpreter standard input +func (nash *Shell) Stdin() io.Reader { return nash.interp.Stdin() } + +// Stdout is the interpreter standard output func (nash *Shell) Stdout() io.Writer { return nash.interp.Stdout() } + +// Stderr is the interpreter standard error func (nash *Shell) Stderr() io.Writer { return nash.interp.Stderr() } -// Setvar sets or updates the variable in the nash session -func (nash *Shell) Setvar(name string, value sh.Obj) { - nash.interp.Setvar(name, value, true) +// Setvar sets or updates the variable in the nash session. It +// returns true if variable was found and properly updated. +func (nash *Shell) Setvar(name string, value sh.Obj) bool { + return nash.interp.Setvar(name, value) +} + +// Newvar creates a new variable in the interpreter scope +func (nash *Shell) Newvar(name string, value sh.Obj) { + nash.interp.Newvar(name, value) } // Getvar retrieves a variable from nash session func (nash *Shell) Getvar(name string) (sh.Obj, bool) { - return nash.interp.Getvar(name, false) + return nash.interp.Getvar(name) } func args2Nash(args []string) string { diff --git a/nash_test.go b/nash_test.go index b98c6150..55ded74a 100644 --- a/nash_test.go +++ b/nash_test.go @@ -118,7 +118,7 @@ func TestSetvar(t *testing.T) { return } - shell.Setvar("__TEST__", sh.NewStrObj("something")) + shell.Newvar("__TEST__", sh.NewStrObj("something")) var out bytes.Buffer shell.SetStdout(&out) diff --git a/stdbin/write/fd_linux.go b/stdbin/write/fd_linux.go new file mode 100644 index 00000000..f5f73ce2 --- /dev/null +++ b/stdbin/write/fd_linux.go @@ -0,0 +1,15 @@ +package main + +import ( + "io" + "os" +) + +func specialFile(path string) (io.WriteCloser, bool) { + if path == "/dev/stdout" { + return os.Stdout, true + } else if path == "/dev/stderr" { + return os.Stderr, true + } + return nil, false +} diff --git a/stdbin/write/fd_windows.go b/stdbin/write/fd_windows.go new file mode 100644 index 00000000..e0086d64 --- /dev/null +++ b/stdbin/write/fd_windows.go @@ -0,0 +1,13 @@ +package main + +import ( + "io" + "os" +) + +func specialFile(path string) (io.Writer, bool) { + if fname == "CON" { // holycrap! + return os.Stdout, true + } + return nil, false +} diff --git a/stdbin/write/main.go b/stdbin/write/main.go new file mode 100644 index 00000000..f16aa9c1 --- /dev/null +++ b/stdbin/write/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "os" +) + +var banner = fmt.Sprintf("%s \n", os.Args[0]) + +func fatal(msg string) { + fmt.Fprintf(os.Stderr, "%s", msg) + os.Exit(1) +} + +func main() { + if len(os.Args) <= 1 || + len(os.Args) > 3 { + fatal(banner) + } + + var ( + fname = os.Args[1] + in io.Reader + ) + + if len(os.Args) == 2 { + in = os.Stdin + } else { + in = bytes.NewBufferString(os.Args[2]) + } + + err := write(fname, in) + if err != nil { + fatal(err.Error()) + } +} diff --git a/stdbin/write/test.sh b/stdbin/write/test.sh new file mode 100644 index 00000000..5197639c --- /dev/null +++ b/stdbin/write/test.sh @@ -0,0 +1,12 @@ +# common test routines + +fn fatal(msg) { + print($msg) + exit("1") +} + +fn assert(expected, got, desc) { + if $expected != $got { + fatal(format("%s: FAILED. Expected[%s] but got[%s]\n", $desc, $expected, $got)) + } +} \ No newline at end of file diff --git a/stdbin/write/write.go b/stdbin/write/write.go new file mode 100644 index 00000000..b5b86a8f --- /dev/null +++ b/stdbin/write/write.go @@ -0,0 +1,54 @@ +package main + +import ( + "io" + "os" + "path/filepath" +) + +func toabs(path string) (string, error) { + if !filepath.IsAbs(path) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + path = filepath.Join(wd, path) + } + return path, nil +} + +func outfd(fname string) (io.WriteCloser, error) { + fname, err := toabs(fname) + if err != nil { + return nil, err + } + + var out io.WriteCloser + + out, ok := specialFile(fname) + if !ok { + f, err := os.OpenFile(fname, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) + if err != nil { + return nil, err + } + out = f + } + return out, nil +} + +func write(fname string, in io.Reader) (err error) { + out, err := outfd(fname) + if err != nil { + return err + } + + defer func() { + err2 := out.Close() + if err == nil { + err = err2 + } + }() + + _, err = io.Copy(out, in) + return err +} diff --git a/stdbin/write/write_linux_test.sh b/stdbin/write/write_linux_test.sh new file mode 100644 index 00000000..264d3d42 --- /dev/null +++ b/stdbin/write/write_linux_test.sh @@ -0,0 +1,31 @@ +# linux tests of write command + +import "./test.sh" + +# this test uses only the write binary +setenv PATH = "./stdbin/write" + +# (desc (out err status)) +var tests = ( + ("standard out" ("/dev/stdout" "hello world" "" "0")) + ("standard err" ("/dev/stderr" "" "hello world" "0")) +) + +var outstr = "hello world" + +for test in $tests { + var desc = $test[0] + var tc = $test[1] + + print("testing %s\n", $desc) + + var device = $tc[0] + var expectedOut = $tc[1] + var expectedErr = $tc[2] + var expectedSts = $tc[3] + + var out, err, status <= write $device $outstr + assert($expectedSts, $status, "status code") + assert($expectedOut, $out, "standard output") + assert($expectedErr, $err, "standard error") +} diff --git a/stdbin/write/write_test.sh b/stdbin/write/write_test.sh new file mode 100644 index 00000000..3b94763d --- /dev/null +++ b/stdbin/write/write_test.sh @@ -0,0 +1,23 @@ +import "./test.sh" + +setenv PATH = "./stdbin/write:/bin" + +# FIXME: we need our mktemp +var nonExistentFile = "./here-be-dragons" + +fn clean() { + _, _ <= rm -f $nonExistentFile +} + +clean() + +var out, err, status <= write $nonExistentFile "hello" +assert("", $out, "standard out isnt empty") +assert("", $err, "standard err isnt empty") +assert("0", $status, "status is not success") + +var content, status <= cat $nonExistentFile +assert("0", $status, "status is not success") +assert("hello", $content, "file content is wrong") + +clean() diff --git a/tests/cfg.go b/tests/cfg.go index b2e731c2..69350812 100644 --- a/tests/cfg.go +++ b/tests/cfg.go @@ -15,7 +15,12 @@ var ( Nashcmd string // Testdir is the test assets directory Testdir string - Gopath string + + // Stdbindir is the stdbin directory + Stdbindir string + + // Gopath of golang + Gopath string ) func getGopath() (string, error) { @@ -52,10 +57,11 @@ func init() { } Gopath = gopath - Testdir = filepath.Join(Gopath, "src", "github.com", - "NeowayLabs", "nash", "testfiles") - Nashcmd = filepath.Join(Gopath, "src", "github.com", - "NeowayLabs", "nash", "cmd", "nash", "nash") + projectpath := filepath.Join(Gopath, "src", "github.com", + "NeowayLabs", "nash") + Testdir = filepath.Join(projectpath, "testfiles") + Nashcmd = filepath.Join(projectpath, "cmd", "nash", "nash") + Stdbindir = filepath.Join(projectpath, "stdbin") if runtime.GOOS == "windows" { Nashcmd += ".exe" From 56d722ddd89d08b0c2cceb8a89092fd7068ae5c9 Mon Sep 17 00:00:00 2001 From: i4k Date: Sat, 3 Feb 2018 01:14:45 -0200 Subject: [PATCH 27/27] rename test.sg to common_test.sh Signed-off-by: i4k --- stdbin/write/{test.sh => common_test.sh} | 0 stdbin/write/write_linux_test.sh | 2 +- stdbin/write/write_test.sh | 12 +++++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) rename stdbin/write/{test.sh => common_test.sh} (100%) diff --git a/stdbin/write/test.sh b/stdbin/write/common_test.sh similarity index 100% rename from stdbin/write/test.sh rename to stdbin/write/common_test.sh diff --git a/stdbin/write/write_linux_test.sh b/stdbin/write/write_linux_test.sh index 264d3d42..9db4e12c 100644 --- a/stdbin/write/write_linux_test.sh +++ b/stdbin/write/write_linux_test.sh @@ -1,6 +1,6 @@ # linux tests of write command -import "./test.sh" +import "./common_test.sh" # this test uses only the write binary setenv PATH = "./stdbin/write" diff --git a/stdbin/write/write_test.sh b/stdbin/write/write_test.sh index 3b94763d..4b449548 100644 --- a/stdbin/write/write_test.sh +++ b/stdbin/write/write_test.sh @@ -1,4 +1,4 @@ -import "./test.sh" +import "./common_test.sh" setenv PATH = "./stdbin/write:/bin" @@ -20,4 +20,14 @@ var content, status <= cat $nonExistentFile assert("0", $status, "status is not success") assert("hello", $content, "file content is wrong") +# test append +out, err, status <= write $nonExistentFile "1" +assert("", $out, "standard out isnt empty") +assert("", $err, "standard err isnt empty") +assert("0", $status, "status is not success") + +content, status <= cat $nonExistentFile +assert("0", $status, "status is not success") +assert("hello1", $content, "file content is wrong") + clean()