diff --git a/go/reg-test/cases/case-c-dsl-for-oosvar-loops.sh b/go/reg-test/cases/case-dsl-for-oosvar-loops.sh similarity index 100% rename from go/reg-test/cases/case-c-dsl-for-oosvar-loops.sh rename to go/reg-test/cases/case-dsl-for-oosvar-loops.sh diff --git a/go/reg-test/expected/case-c-dsl-for-oosvar-loops.sh.out b/go/reg-test/expected/case-dsl-for-oosvar-loops.sh.out similarity index 97% rename from go/reg-test/expected/case-c-dsl-for-oosvar-loops.sh.out rename to go/reg-test/expected/case-dsl-for-oosvar-loops.sh.out index 082d29ab3..359366985 100644 --- a/go/reg-test/expected/case-c-dsl-for-oosvar-loops.sh.out +++ b/go/reg-test/expected/case-dsl-for-oosvar-loops.sh.out @@ -273,7 +273,6 @@ RAW AST: * full record "$*" * statement block -Exit status was 0; expected 1. mlr -n put -v for (k, k in @*) {} DSL EXPRESSION: @@ -286,7 +285,6 @@ RAW AST: * full oosvar "@*" * statement block -Exit status was 0; expected 1. mlr -n put -v for ((a,a), c in @*) {} DSL EXPRESSION: @@ -301,7 +299,6 @@ RAW AST: * full oosvar "@*" * statement block -Exit status was 0; expected 1. mlr -n put -v for ((a,b), a in @*) {} DSL EXPRESSION: @@ -316,7 +313,6 @@ RAW AST: * full oosvar "@*" * statement block -Exit status was 0; expected 1. mlr -n put -v for ((a,b), b in @*) {} DSL EXPRESSION: @@ -331,7 +327,6 @@ RAW AST: * full oosvar "@*" * statement block -Exit status was 0; expected 1. mlr -n put -v for ((a,a,c), d in @*) {} DSL EXPRESSION: @@ -347,7 +342,6 @@ RAW AST: * full oosvar "@*" * statement block -Exit status was 0; expected 1. mlr -n put -v for ((a,b,a), d in @*) {} DSL EXPRESSION: @@ -363,7 +357,6 @@ RAW AST: * full oosvar "@*" * statement block -Exit status was 0; expected 1. mlr -n put -v for ((a,b,c), a in @*) {} DSL EXPRESSION: @@ -379,7 +372,6 @@ RAW AST: * full oosvar "@*" * statement block -Exit status was 0; expected 1. mlr -n put -v for ((a,b,b), d in @*) {} DSL EXPRESSION: @@ -395,7 +387,6 @@ RAW AST: * full oosvar "@*" * statement block -Exit status was 0; expected 1. mlr -n put -v for ((a,b,c), b in @*) {} DSL EXPRESSION: @@ -411,7 +402,6 @@ RAW AST: * full oosvar "@*" * statement block -Exit status was 0; expected 1. mlr -n put -v for ((a,b,c), c in @*) {} DSL EXPRESSION: @@ -427,7 +417,6 @@ RAW AST: * full oosvar "@*" * statement block -Exit status was 0; expected 1. mlr --from ./reg-test/input/xyz2 put -q func f() { return {"a"."b":"c"."d",3:4}}; for (k,v in f()){print "k=".k.",v=".v} k=ab,v=cd diff --git a/go/src/miller/dsl/cst/validate.go b/go/src/miller/dsl/cst/validate.go index e2edb5133..4970b0a27 100644 --- a/go/src/miller/dsl/cst/validate.go +++ b/go/src/miller/dsl/cst/validate.go @@ -9,8 +9,10 @@ package cst import ( "errors" "fmt" + "os" "miller/dsl" + "miller/lib" ) // ---------------------------------------------------------------- @@ -122,6 +124,18 @@ func validateASTAux( } nextLevelInUDS = true } + if astNode.Type == dsl.NodeTypeForLoopTwoVariable { + err := validateForLoopTwoVariableUniqueNames(astNode) + if err != nil { + return err + } + } + if astNode.Type == dsl.NodeTypeForLoopMultivariable { + err := validateForLoopMultivariableUniqueNames(astNode) + if err != nil { + return err + } + } // Check: $-anything cannot be in begin/end if inBeginOrEnd { @@ -241,6 +255,87 @@ func validateASTAux( return nil } +// Check against 'for (a, a in $*)' -- repeated 'a'. +// AST: +// * statement block +// * double-variable for-loop "for" +// * local variable "a" +// * local variable "a" +// * full record "$*" +// * statement block + +func validateForLoopTwoVariableUniqueNames(astNode *dsl.ASTNode) error { + lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeForLoopTwoVariable) + lib.InternalCodingErrorIf(len(astNode.Children) != 4) + keyVarNode := astNode.Children[0] + valVarNode := astNode.Children[1] + lib.InternalCodingErrorIf(keyVarNode.Type != dsl.NodeTypeLocalVariable) + lib.InternalCodingErrorIf(valVarNode.Type != dsl.NodeTypeLocalVariable) + keyVarName := string(keyVarNode.Token.Lit) + valVarName := string(valVarNode.Token.Lit) + if keyVarName == valVarName { + return errors.New( + fmt.Sprintf( + "%s: redefinition of variable %s in the same scope.", + os.Args[0], + keyVarName, + ), + ) + } else { + return nil + } +} + +// Check against 'for ((a,a), b in $*)' or 'for ((a,b), a in $*)' -- repeated 'a'. +// AST: +// * statement block +// * multi-variable for-loop "for" +// * parameter list +// * local variable "a" +// * local variable "b" +// * local variable "a" +// * full record "$*" +// * statement block +func validateForLoopMultivariableUniqueNames(astNode *dsl.ASTNode) error { + lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeForLoopMultivariable) + keyVarsNode := astNode.Children[0] + valVarNode := astNode.Children[1] + lib.InternalCodingErrorIf(keyVarsNode.Type != dsl.NodeTypeParameterList) + lib.InternalCodingErrorIf(valVarNode.Type != dsl.NodeTypeLocalVariable) + + seen := make(map[string]bool) + + for _, keyVarNode := range keyVarsNode.Children { + lib.InternalCodingErrorIf(keyVarNode.Type != dsl.NodeTypeLocalVariable) + name := string(keyVarNode.Token.Lit) + _, present := seen[name] + if present { + return errors.New( + fmt.Sprintf( + "%s: redefinition of variable %s in the same scope.", + os.Args[0], + name, + ), + ) + } + seen[name] = true + } + + valVarName := string(valVarNode.Token.Lit) + if seen[valVarName] { + return errors.New( + fmt.Sprintf( + "%s: redefinition of variable %s in the same scope.", + os.Args[0], + valVarName, + ), + ) + } + + return nil +} + +// ================================================================ var VALID_LHS_NODE_TYPES = map[dsl.TNodeType]bool{ dsl.NodeTypeArrayOrMapIndexAccess: true, dsl.NodeTypeArrayOrMapPositionalNameAccess: true, diff --git a/go/todo.txt b/go/todo.txt index 919c3502c..780312c3c 100644 --- a/go/todo.txt +++ b/go/todo.txt @@ -26,9 +26,6 @@ bugs: mlr put -q 'print | "tr \[a-z\] \[A-Z\]", "abi:".$a.$b.$i' s mlr: cannot parse DSL expression. -* ./reg-test/cases/case-c-dsl-break-continue.sh - bug w/ break - * ./reg-test/cases/case-c-dsl-for-oosvar-loops.sh bug w/ non-unique for-loop names: e.g. 'for(a,a in $*)'