From 38f179bd5c2e36c402399e1a040027ea166cd4e7 Mon Sep 17 00:00:00 2001 From: John Kerl Date: Tue, 17 Nov 2020 23:29:19 -0500 Subject: [PATCH] for-multi-variable iterate --- go/src/miller/dsl/cst/for.go | 193 +++++++++++++++++++++++++++- go/src/miller/dsl/cst/statements.go | 4 +- go/u/for-multi.json | 25 ++++ go/u/try-cst | 5 + go/u/try-cst.out | 60 +++++++++ 5 files changed, 281 insertions(+), 6 deletions(-) create mode 100644 go/u/for-multi.json diff --git a/go/src/miller/dsl/cst/for.go b/go/src/miller/dsl/cst/for.go index 2ab543801..bd1edbc8e 100644 --- a/go/src/miller/dsl/cst/for.go +++ b/go/src/miller/dsl/cst/for.go @@ -297,10 +297,7 @@ func (this *ForLoopTwoVariableNode) Execute(state *State) (*BlockExitPayload, er mapkey := types.MlrvalFromString(*pe.Key) state.stack.BindVariable(this.keyVariableName, &mapkey) - // TODO: comment-reap - if this.valueVariableName != "" { // 'for (k in ...)' not 'for (k,v in ...)' - state.stack.BindVariable(this.valueVariableName, pe.Value) - } + state.stack.BindVariable(this.valueVariableName, pe.Value) // The loop body will push its own frame blockExitPayload, err := this.statementBlockNode.Execute(state) if err != nil { @@ -368,6 +365,194 @@ func (this *ForLoopTwoVariableNode) Execute(state *State) (*BlockExitPayload, er return nil, nil } +// ================================================================ +type ForLoopMultivariableNode struct { + keyVariableNames []string + valueVariableName string + indexableNode IEvaluable + statementBlockNode *StatementBlockNode +} + +func NewForLoopMultivariableNode( + keyVariableNames []string, + valueVariableName string, + indexableNode IEvaluable, + statementBlockNode *StatementBlockNode, +) *ForLoopMultivariableNode { + return &ForLoopMultivariableNode{ + keyVariableNames, + valueVariableName, + indexableNode, + statementBlockNode, + } +} + +// ---------------------------------------------------------------- +// Sample AST: + +// mlr -n put -v 'for ((k1, k2), v in $*) { }' +// DSL EXPRESSION: +// for ((k1, k2), v in $*) { } +// RAW AST: +// * statement block +// * multi-variable for-loop "for" +// * parameter list +// * local variable "k1" +// * local variable "k2" +// * local variable "v" +// * full record "$*" +// * statement block + +func (this *RootNode) BuildForLoopMultivariableNode( + astNode *dsl.ASTNode, +) (*ForLoopMultivariableNode, error) { + lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeForLoopMultivariable) + lib.InternalCodingErrorIf(len(astNode.Children) != 4) + + keyVariablesASTNode := astNode.Children[0] + valueVariableASTNode := astNode.Children[1] + indexableASTNode := astNode.Children[2] + blockASTNode := astNode.Children[3] + + lib.InternalCodingErrorIf(keyVariablesASTNode.Type != dsl.NodeTypeParameterList) + lib.InternalCodingErrorIf(keyVariablesASTNode.Children == nil) + keyVariableNames := make([]string, len(keyVariablesASTNode.Children)) + for i, keyVariableASTNode := range keyVariablesASTNode.Children { + lib.InternalCodingErrorIf(keyVariableASTNode.Token == nil) + keyVariableNames[i] = string(keyVariableASTNode.Token.Lit) + } + + lib.InternalCodingErrorIf(valueVariableASTNode.Type != dsl.NodeTypeLocalVariable) + lib.InternalCodingErrorIf(valueVariableASTNode.Token == nil) + valueVariableName := string(valueVariableASTNode.Token.Lit) + + // TODO: error if loop-over node isn't map/array (inasmuch as can be + // detected at CST-build time) + indexableNode, err := this.BuildEvaluableNode(indexableASTNode) + if err != nil { + return nil, err + } + + statementBlockNode, err := this.BuildStatementBlockNode(blockASTNode) + if err != nil { + return nil, err + } + + return NewForLoopMultivariableNode( + keyVariableNames, + valueVariableName, + indexableNode, + statementBlockNode, + ), nil +} + +// ---------------------------------------------------------------- +// Note: The statement-block has its own push/pop for its localvars. +// Meanwhile we need to restrict scope of the bindvar to the for-loop. +// +// So we have: +// +// mlr put ' +// x = 1; <--- frame #1 main +// for (k in $*) { <--- frame #2 for for-loop bindvars (right here) +// x = 2 <--- frame #3 for for-loop locals +// } +// x = 3; <--- back in frame #1 main +// ' +// + +func (this *ForLoopMultivariableNode) Execute(state *State) (*BlockExitPayload, error) { + mlrval := this.indexableNode.Evaluate(state) + + if mlrval.IsMap() { + + mapval := mlrval.GetMap() + + // Make a frame for the loop variable(s) + state.stack.PushStackFrame() + defer state.stack.PopStackFrame() + for pe := mapval.Head; pe != nil; pe = pe.Next { + mapkey := types.MlrvalFromString(*pe.Key) + + for _, keyVariableName := range this.keyVariableNames { + state.stack.BindVariable(keyVariableName, &mapkey) + } + state.stack.BindVariable(this.valueVariableName, pe.Value) + + // TODO: recursive helper function + + // The loop body will push its own frame + blockExitPayload, err := this.statementBlockNode.Execute(state) + if err != nil { + return nil, err + } + if blockExitPayload != nil { + if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { + break + } + // If continue, keep going -- this means the body was exited + // early but we keep going at this level + if blockExitPayload.blockExitStatus == BLOCK_EXIT_RETURN_VOID { + return blockExitPayload, nil + } + if blockExitPayload.blockExitStatus == BLOCK_EXIT_RETURN_VALUE { + return blockExitPayload, nil + } + } + // TODO: runtime errors for any other types + } + + } else if mlrval.IsArray() { + + arrayval := mlrval.GetArray() + + // Note: Miller user-space array indices ("mindex") are 1-up. Internal + // Go storage ("zindex") is 0-up. + + // Make a frame for the loop variable(s) + state.stack.PushStackFrame() + defer state.stack.PopStackFrame() + for zindex, element := range arrayval { + mindex := types.MlrvalFromInt64(int64(zindex + 1)) + + for _, keyVariableName := range this.keyVariableNames { + state.stack.BindVariable(keyVariableName, &mindex) + } + if this.valueVariableName != "" { // 'for (k in ...)' not 'for (k,v in ...)' + state.stack.BindVariable(this.valueVariableName, &element) + } + + // TODO: recursive helper function + + // The loop body will push its own frame + blockExitPayload, err := this.statementBlockNode.Execute(state) + if err != nil { + return nil, err + } + if blockExitPayload != nil { + if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { + break + } + // If continue, keep going -- this means the body was exited + // early but we keep going at this level + if blockExitPayload.blockExitStatus == BLOCK_EXIT_RETURN_VOID { + return blockExitPayload, nil + } + if blockExitPayload.blockExitStatus == BLOCK_EXIT_RETURN_VALUE { + return blockExitPayload, nil + } + } + // TODO: runtime errors for any other types + } + + } else { + // TODO: give more context + return nil, errors.New("Miller: looped-over item is not a map.") + } + + return nil, nil +} + // ================================================================ type TripleForLoopNode struct { startBlockNode *StatementBlockNode diff --git a/go/src/miller/dsl/cst/statements.go b/go/src/miller/dsl/cst/statements.go index b3b7ee739..b539d9d81 100644 --- a/go/src/miller/dsl/cst/statements.go +++ b/go/src/miller/dsl/cst/statements.go @@ -74,8 +74,8 @@ func (this *RootNode) BuildStatementNode( return this.BuildForLoopOneVariableNode(astNode) case dsl.NodeTypeForLoopTwoVariable: return this.BuildForLoopTwoVariableNode(astNode) - //case dsl.NodeTypeForLoopMultivariable: - //return this.BuildForLoopMultivariableNode(astNode) + case dsl.NodeTypeForLoopMultivariable: + return this.BuildForLoopMultivariableNode(astNode) case dsl.NodeTypeTripleForLoop: return this.BuildTripleForLoopNode(astNode) diff --git a/go/u/for-multi.json b/go/u/for-multi.json new file mode 100644 index 000000000..7ff15afc8 --- /dev/null +++ b/go/u/for-multi.json @@ -0,0 +1,25 @@ +{ + "a": { + "x": 1, + "y": 2 + }, + "b": { + "x": 3, + "y": 4 + } +} +{ + "a": { + "x": 3, + "y": 4 + }, + "b": { + "x": 5 + } +} +{ + "b": { + "x": 6, + "y": 7 + } +} diff --git a/go/u/try-cst b/go/u/try-cst index 4cfe9c6b4..2884154aa 100755 --- a/go/u/try-cst +++ b/go/u/try-cst @@ -356,6 +356,11 @@ echo; run_mlr --from u/s.dkvp head -n 2 then put -q 'for (k,v in $*) { emit { "f echo; run_mlr --from u/s.dkvp head -n 2 then put -q 'for (k,v in $*) { emit { k: "bar" } }' echo; run_mlr --from u/s.dkvp head -n 2 then put -q 'for (k,v in $*) { emit { k : v } }' +echo; run_mlr -n put -v 'for (k in @*) {}' +echo; run_mlr -n put -v 'for (k, v in @*) {}' +echo; run_mlr -n put -v 'for ((k1,k2), v in @*) {}' +echo; run_mlr -n put -v 'for ((k1,k2,k3), v in @*) {}' + echo; run_mlr --from u/s.dkvp put '$z = 0; while ($z < $i) {$z += 1}' echo; run_mlr --from u/s.dkvp put '$z = 0; do {$z += 1} while ($z < $i)' echo; run_mlr --from u/s.dkvp put '$z = 10; while ($z < $i) {$z += 1}' diff --git a/go/u/try-cst.out b/go/u/try-cst.out index 015f809a8..47019a31c 100644 --- a/go/u/try-cst.out +++ b/go/u/try-cst.out @@ -3076,6 +3076,66 @@ x=0.7586799647899636 y=0.5221511083334797 +---------------------------------------------------------------- +mlr -n put -v for (k in @*) {} +DSL EXPRESSION: +for (k in @*) {} +RAW AST: +* statement block + * single-variable for-loop "for" + * local variable "k" + * full oosvar "@*" + * statement block + + + +---------------------------------------------------------------- +mlr -n put -v for (k, v in @*) {} +DSL EXPRESSION: +for (k, v in @*) {} +RAW AST: +* statement block + * double-variable for-loop "for" + * local variable "k" + * local variable "v" + * full oosvar "@*" + * statement block + + + +---------------------------------------------------------------- +mlr -n put -v for ((k1,k2), v in @*) {} +DSL EXPRESSION: +for ((k1,k2), v in @*) {} +RAW AST: +* statement block + * multi-variable for-loop "for" + * parameter list + * local variable "k1" + * local variable "k2" + * local variable "v" + * full oosvar "@*" + * statement block + + + +---------------------------------------------------------------- +mlr -n put -v for ((k1,k2,k3), v in @*) {} +DSL EXPRESSION: +for ((k1,k2,k3), v in @*) {} +RAW AST: +* statement block + * multi-variable for-loop "for" + * parameter list + * local variable "k1" + * local variable "k2" + * local variable "k3" + * local variable "v" + * full oosvar "@*" + * statement block + + + ---------------------------------------------------------------- mlr --from u/s.dkvp put $z = 0; while ($z < $i) {$z += 1} a=pan,b=pan,i=1,x=0.3467901443380824,y=0.7268028627434533,z=1