for-multi-variable iterate

This commit is contained in:
John Kerl 2020-11-17 23:29:19 -05:00
parent 803c0b5579
commit 38f179bd5c
5 changed files with 281 additions and 6 deletions

View file

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

View file

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

25
go/u/for-multi.json Normal file
View file

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

View file

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

View file

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