// ================================================================ // This is for various flavors of for-loop // ================================================================ package cst import ( "fmt" "github.com/johnkerl/miller/v6/pkg/dsl" "github.com/johnkerl/miller/v6/pkg/lib" "github.com/johnkerl/miller/v6/pkg/mlrval" "github.com/johnkerl/miller/v6/pkg/parsing/token" "github.com/johnkerl/miller/v6/pkg/runtime" ) // ---------------------------------------------------------------- // Sample AST: // mlr -n put -v 'for (k in $*) { emit { k : k } }' // DSL EXPRESSION: // for (k in $*) { emit { k : k} } // AST: // * StatementBlock // * ForLoopOneVariable "for" // * LocalVariable "k" // * FullSrec "$*" // * StatementBlock // * EmitStatement "emit" // * MapLiteral "{}" // * MapLiteralTwoVariablePair ":" // * LocalVariable "k" // * LocalVariable "k" // ================================================================ type ForLoopOneVariableNode struct { indexVariable *runtime.StackVariable indexableNode IEvaluable statementBlockNode *StatementBlockNode } func NewForLoopOneVariableNode( variableName string, indexableNode IEvaluable, statementBlockNode *StatementBlockNode, ) *ForLoopOneVariableNode { return &ForLoopOneVariableNode{ runtime.NewStackVariable(variableName), indexableNode, statementBlockNode, } } // ---------------------------------------------------------------- // Sample AST: // mlr -n put -v 'for (k in $*) { emit { k : k } }' // DSL EXPRESSION: // for (k, v in $*) { emit { k : v } } // AST: // * StatementBlock // * ForLoopOneVariable "for" // * LocalVariable "k" // * LocalVariable "v" // * FullSrec "$*" // * StatementBlock // * EmitStatement "emit" // * MapLiteral "{}" // * MapLiteralOneVariablePair ":" // * LocalVariable "k" // * LocalVariable "v" func (root *RootNode) BuildForLoopOneVariableNode(astNode *dsl.ASTNode) (*ForLoopOneVariableNode, error) { lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeForLoopOneVariable) lib.InternalCodingErrorIf(len(astNode.Children) != 3) variableASTNode := astNode.Children[0] indexableASTNode := astNode.Children[1] blockASTNode := astNode.Children[2] lib.InternalCodingErrorIf(variableASTNode.Type != dsl.NodeTypeLocalVariable) lib.InternalCodingErrorIf(variableASTNode.Token == nil) variableName := string(variableASTNode.Token.Lit) // TODO: error if loop-over node isn't map/array (inasmuch as can be // detected at CST-build time) indexableNode, err := root.BuildEvaluableNode(indexableASTNode) if err != nil { return nil, err } statementBlockNode, err := root.BuildStatementBlockNode(blockASTNode) if err != nil { return nil, err } return NewForLoopOneVariableNode( variableName, 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 (node *ForLoopOneVariableNode) Execute(state *runtime.State) (*BlockExitPayload, error) { indexMlrval := node.indexableNode.Evaluate(state) if indexMlrval.IsMap() { mapval := indexMlrval.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 := mlrval.FromString(pe.Key) err := state.Stack.SetAtScope(node.indexVariable, mapkey) if err != nil { return nil, err } // The loop body will push its own frame blockExitPayload, err := node.statementBlockNode.Execute(state) if err != nil { return nil, err } if blockExitPayload != nil { if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { break } // If BLOCK_EXIT_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 } } } } else if indexMlrval.IsArray() { arrayval := indexMlrval.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 _, element := range arrayval { err := state.Stack.SetAtScope(node.indexVariable, element) if err != nil { return nil, err } // The loop body will push its own frame blockExitPayload, err := node.statementBlockNode.Execute(state) if err != nil { return nil, err } if blockExitPayload != nil { if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { break } // If BLOCK_EXIT_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 } } } } else if indexMlrval.IsAbsent() { // Data-heterogeneity no-op } // TODO: backwards compatibility with the C port means we treat this as // silent zero-pass. But maybe we should surface it as an error. Maybe // with a "mlr put --errors" flag or something. // } else { // return nil, fmt.Errorf( // "mlr: looped-over item is not a map or array; got %s", // indexMlrval.GetTypeName(), // ) return nil, nil } // ================================================================ type ForLoopTwoVariableNode struct { keyIndexVariable *runtime.StackVariable valueIndexVariable *runtime.StackVariable indexableNode IEvaluable statementBlockNode *StatementBlockNode } func NewForLoopTwoVariableNode( keyIndexVariable *runtime.StackVariable, valueIndexVariable *runtime.StackVariable, indexableNode IEvaluable, statementBlockNode *StatementBlockNode, ) *ForLoopTwoVariableNode { return &ForLoopTwoVariableNode{ keyIndexVariable, valueIndexVariable, indexableNode, statementBlockNode, } } // ---------------------------------------------------------------- // Sample AST: // mlr -n put -v 'for (k, v in $*) { emit { k : v } }' // DSL EXPRESSION: // for (k, v in $*) { emit { k : v } } // AST: // * StatementBlock // * ForLoopTwoVariable "for" // * LocalVariable "k" // * LocalVariable "v" // * FullSrec "$*" // * StatementBlock // * EmitStatement "emit" // * MapLiteral "{}" // * MapLiteralTwoVariablePair ":" // * LocalVariable "k" // * LocalVariable "v" func (root *RootNode) BuildForLoopTwoVariableNode(astNode *dsl.ASTNode) (*ForLoopTwoVariableNode, error) { lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeForLoopTwoVariable) lib.InternalCodingErrorIf(len(astNode.Children) != 4) keyVariableASTNode := astNode.Children[0] valueVariableASTNode := astNode.Children[1] indexableASTNode := astNode.Children[2] blockASTNode := astNode.Children[3] lib.InternalCodingErrorIf(keyVariableASTNode.Type != dsl.NodeTypeLocalVariable) lib.InternalCodingErrorIf(keyVariableASTNode.Token == nil) keyVariableName := string(keyVariableASTNode.Token.Lit) keyIndexVariable := runtime.NewStackVariable(keyVariableName) lib.InternalCodingErrorIf(valueVariableASTNode.Type != dsl.NodeTypeLocalVariable) lib.InternalCodingErrorIf(valueVariableASTNode.Token == nil) valueVariableName := string(valueVariableASTNode.Token.Lit) valueIndexVariable := runtime.NewStackVariable(valueVariableName) // TODO: error if loop-over node isn't map/array (inasmuch as can be // detected at CST-build time) indexableNode, err := root.BuildEvaluableNode(indexableASTNode) if err != nil { return nil, err } statementBlockNode, err := root.BuildStatementBlockNode(blockASTNode) if err != nil { return nil, err } return NewForLoopTwoVariableNode( keyIndexVariable, valueIndexVariable, 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,v 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 (node *ForLoopTwoVariableNode) Execute(state *runtime.State) (*BlockExitPayload, error) { indexMlrval := node.indexableNode.Evaluate(state) if indexMlrval.IsMap() { mapval := indexMlrval.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 := mlrval.FromString(pe.Key) err := state.Stack.SetAtScope(node.keyIndexVariable, mapkey) if err != nil { return nil, err } err = state.Stack.SetAtScope(node.valueIndexVariable, pe.Value) if err != nil { return nil, err } // The loop body will push its own frame blockExitPayload, err := node.statementBlockNode.Execute(state) if err != nil { return nil, err } if blockExitPayload != nil { if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { break } // If BLOCK_EXIT_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 } } } } else if indexMlrval.IsArray() { arrayval := indexMlrval.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 := mlrval.FromInt(int64(zindex + 1)) err := state.Stack.SetAtScope(node.keyIndexVariable, mindex) if err != nil { return nil, err } err = state.Stack.SetAtScope(node.valueIndexVariable, element) if err != nil { return nil, err } // The loop body will push its own frame blockExitPayload, err := node.statementBlockNode.Execute(state) if err != nil { return nil, err } if blockExitPayload != nil { if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { break } // If BLOCK_EXIT_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 } } } } else if indexMlrval.IsAbsent() { // Data-heterogeneity no-op } // TODO: backwards compatibility with the C port means we treat this as // silent zero-pass. But maybe we should surface it as an error. Maybe // with a "mlr put --errors" flag or something. // } else { // return nil, fmt.Errorf( // "mlr: looped-over item is not a map or array; got %s", // indexMlrval.GetTypeName(), // ) return nil, nil } // ================================================================ type ForLoopMultivariableNode struct { keyIndexVariables []*runtime.StackVariable valueIndexVariable *runtime.StackVariable indexableNode IEvaluable statementBlockNode *StatementBlockNode } func NewForLoopMultivariableNode( keyIndexVariables []*runtime.StackVariable, valueIndexVariable *runtime.StackVariable, indexableNode IEvaluable, statementBlockNode *StatementBlockNode, ) *ForLoopMultivariableNode { return &ForLoopMultivariableNode{ keyIndexVariables, valueIndexVariable, indexableNode, statementBlockNode, } } // ---------------------------------------------------------------- // Sample AST: // mlr -n put -v 'for ((k1, k2), v in $*) { }' // DSL EXPRESSION: // for ((k1, k2), v in $*) { } // AST: // * statement block // * multi-variable for-loop "for" // * parameter list // * local variable "k1" // * local variable "k2" // * local variable "v" // * full record "$*" // * statement block func (root *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) keyIndexVariables := make([]*runtime.StackVariable, len(keyVariablesASTNode.Children)) for i, keyVariableASTNode := range keyVariablesASTNode.Children { lib.InternalCodingErrorIf(keyVariableASTNode.Token == nil) keyIndexVariableName := string(keyVariableASTNode.Token.Lit) keyIndexVariables[i] = runtime.NewStackVariable(keyIndexVariableName) } lib.InternalCodingErrorIf(valueVariableASTNode.Type != dsl.NodeTypeLocalVariable) lib.InternalCodingErrorIf(valueVariableASTNode.Token == nil) valueVariableName := string(valueVariableASTNode.Token.Lit) valueIndexVariable := runtime.NewStackVariable(valueVariableName) // TODO: error if loop-over node isn't map/array (inasmuch as can be // detected at CST-build time) indexableNode, err := root.BuildEvaluableNode(indexableASTNode) if err != nil { return nil, err } statementBlockNode, err := root.BuildStatementBlockNode(blockASTNode) if err != nil { return nil, err } return NewForLoopMultivariableNode( keyIndexVariables, valueIndexVariable, 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 ((k1,k2),v 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 (node *ForLoopMultivariableNode) Execute(state *runtime.State) (*BlockExitPayload, error) { indexMlrval := node.indexableNode.Evaluate(state) // Make a frame for the loop variables state.Stack.PushStackFrame() defer state.Stack.PopStackFrame() // Miller's multi-variable loops, in the Miller DSL, have a single {...} // but are implemented in Go via multiple, recursive functions. A break // from any of the latter is a break from all. However, at this point, the // break has been "broken" and should not be returned to the caller. // Return-statements should, though. blockExitPayload, err := node.executeOuter(indexMlrval, node.keyIndexVariables, state) if blockExitPayload == nil { return nil, err } else { if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { return nil, err } else { return blockExitPayload, err } } } // ---------------------------------------------------------------- func (node *ForLoopMultivariableNode) executeOuter( mv *mlrval.Mlrval, keyIndexVariables []*runtime.StackVariable, state *runtime.State, ) (*BlockExitPayload, error) { if len(keyIndexVariables) == 1 { return node.executeInner(mv, keyIndexVariables[0], state) } // else, recurse if mv.IsMap() { mapval := mv.GetMap() for pe := mapval.Head; pe != nil; pe = pe.Next { mapkey := mlrval.FromString(pe.Key) err := state.Stack.SetAtScope(keyIndexVariables[0], mapkey) if err != nil { return nil, err } blockExitPayload, err := node.executeOuter(pe.Value, keyIndexVariables[1:], state) if err != nil { return nil, err } if blockExitPayload != nil { if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { return blockExitPayload, nil } // If BLOCK_EXIT_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 } } } } else if mv.IsArray() { arrayval := mv.GetArray() // Note: Miller user-space array indices ("mindex") are 1-up. Internal // Go storage ("zindex") is 0-up. for zindex, element := range arrayval { mindex := mlrval.FromInt(int64(zindex + 1)) err := state.Stack.SetAtScope(keyIndexVariables[0], mindex) if err != nil { return nil, err } blockExitPayload, err := node.executeOuter(element, keyIndexVariables[1:], state) if err != nil { return nil, err } if blockExitPayload != nil { if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { return blockExitPayload, nil } // If BLOCK_EXIT_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 } } } } else if mv.IsAbsent() { // Data-heterogeneity no-op } // TODO: backwards compatibility with the C port means we treat this as // silent zero-pass. But maybe we should surface it as an error. Maybe // with a "mlr put --errors" flag or something. // } else { // return nil, fmt.Errorf( // "mlr: looped-over item is not a map or array; got %s", // mv.GetTypeName(), // ) return nil, nil } // ---------------------------------------------------------------- func (node *ForLoopMultivariableNode) executeInner( mv *mlrval.Mlrval, keyIndexVariable *runtime.StackVariable, state *runtime.State, ) (*BlockExitPayload, error) { if mv.IsMap() { mapval := mv.GetMap() for pe := mapval.Head; pe != nil; pe = pe.Next { mapkey := mlrval.FromString(pe.Key) err := state.Stack.SetAtScope(keyIndexVariable, mapkey) if err != nil { return nil, err } err = state.Stack.SetAtScope(node.valueIndexVariable, pe.Value) if err != nil { return nil, err } // The loop body will push its own frame blockExitPayload, err := node.statementBlockNode.Execute(state) if err != nil { return nil, err } if blockExitPayload != nil { if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { return blockExitPayload, nil } // If BLOCK_EXIT_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 } } } } else if mv.IsArray() { arrayval := mv.GetArray() // Note: Miller user-space array indices ("mindex") are 1-up. Internal // Go storage ("zindex") is 0-up. for zindex, element := range arrayval { mindex := mlrval.FromInt(int64(zindex + 1)) err := state.Stack.SetAtScope(keyIndexVariable, mindex) if err != nil { return nil, err } err = state.Stack.SetAtScope(node.valueIndexVariable, element) if err != nil { return nil, err } // The loop body will push its own frame blockExitPayload, err := node.statementBlockNode.Execute(state) if err != nil { return nil, err } if blockExitPayload != nil { if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { return blockExitPayload, nil } // If BLOCK_EXIT_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 } } } } else if mv.IsAbsent() { // Data-heterogeneity no-op } // TODO: backwards compatibility with the C port means we treat this as // silent zero-pass. But maybe we should surface it as an error. Maybe // with a "mlr put --errors" flag or something. // } else { // return nil, fmt.Errorf( // "mlr: looped-over item is not a map or array; got %s", // mv.GetTypeName(), // ) return nil, nil } // ================================================================ type TripleForLoopNode struct { startBlockNode *StatementBlockNode precontinuationAssignments []IExecutable continuationExpressionNode IEvaluable continuationExpressionToken *token.Token updateBlockNode *StatementBlockNode bodyBlockNode *StatementBlockNode } func NewTripleForLoopNode( startBlockNode *StatementBlockNode, precontinuationAssignments []IExecutable, continuationExpressionNode IEvaluable, continuationExpressionToken *token.Token, updateBlockNode *StatementBlockNode, bodyBlockNode *StatementBlockNode, ) *TripleForLoopNode { return &TripleForLoopNode{ startBlockNode, precontinuationAssignments, continuationExpressionNode, continuationExpressionToken, updateBlockNode, bodyBlockNode, } } // ---------------------------------------------------------------- // Sample ASTs: // DSL EXPRESSION: // for (;;) {} // AST: // * StatementBlock // * TripleForLoop "for" // * StatementBlock // * StatementBlock // * StatementBlock // * StatementBlock // mlr --from u/s.dkvp put -v for (i = 0; i < NR; i += 1) { $i += i } // DSL EXPRESSION: // for (i = 0; i < NR; i += 1) { $i += i } // AST: // * StatementBlock // * TripleForLoop "for" // * StatementBlock // * Assignment "=" // * LocalVariable "i" // * IntLiteral "0" // * StatementBlock // * BareBoolean // * Operator "<" // * LocalVariable "i" // * ContextVariable "NR" // * StatementBlock // * Assignment "=" // * LocalVariable "i" // * Operator "+" // * LocalVariable "i" // * IntLiteral "1" // * StatementBlock // * Assignment "=" // * DirectFieldValue "i" // * Operator "+" // * DirectFieldValue "i" // * LocalVariable "i" func (root *RootNode) BuildTripleForLoopNode(astNode *dsl.ASTNode) (*TripleForLoopNode, error) { lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeTripleForLoop) lib.InternalCodingErrorIf(len(astNode.Children) != 4) startBlockASTNode := astNode.Children[0] continuationExpressionASTNode := astNode.Children[1] updateBlockASTNode := astNode.Children[2] bodyBlockASTNode := astNode.Children[3] lib.InternalCodingErrorIf(startBlockASTNode.Type != dsl.NodeTypeStatementBlock) lib.InternalCodingErrorIf(continuationExpressionASTNode.Type != dsl.NodeTypeStatementBlock) lib.InternalCodingErrorIf(updateBlockASTNode.Type != dsl.NodeTypeStatementBlock) lib.InternalCodingErrorIf(bodyBlockASTNode.Type != dsl.NodeTypeStatementBlock) startBlockNode, err := root.BuildStatementBlockNode(startBlockASTNode) if err != nil { return nil, err } // Enforced here, not in the grammar: the last must be a bare boolean; the ones // before must be assignments. Example: // for (int i = 0; c += 1, i < 10; i += 1) { ... } var precontinuationAssignments []IExecutable = nil var continuationExpressionNode IEvaluable = nil var continuationExpressionToken *token.Token = nil if len(continuationExpressionASTNode.Children) > 0 { // empty is true n := len(continuationExpressionASTNode.Children) if n > 1 { precontinuationAssignments = make([]IExecutable, n-1) for i := 0; i < n-1; i++ { if continuationExpressionASTNode.Children[i].Type != dsl.NodeTypeAssignment { return nil, fmt.Errorf( "mlr: the non-final triple-for continuation statements must be assignments.", ) } precontinuationAssignment, err := root.BuildAssignmentNode( continuationExpressionASTNode.Children[i], ) if err != nil { return nil, err } precontinuationAssignments[i] = precontinuationAssignment } } bareBooleanASTNode := continuationExpressionASTNode.Children[n-1] if bareBooleanASTNode.Type != dsl.NodeTypeBareBoolean { if n == 1 { return nil, fmt.Errorf( "mlr: the triple-for continuation statement must be a bare boolean.", ) } else { return nil, fmt.Errorf( "mlr: the final triple-for continuation statement must be a bare boolean.", ) } } lib.InternalCodingErrorIf(len(bareBooleanASTNode.Children) != 1) continuationExpressionNode, err = root.BuildEvaluableNode(bareBooleanASTNode.Children[0]) continuationExpressionToken = bareBooleanASTNode.Children[0].Token if err != nil { return nil, err } } updateBlockNode, err := root.BuildStatementBlockNode(updateBlockASTNode) if err != nil { return nil, err } bodyBlockNode, err := root.BuildStatementBlockNode(bodyBlockASTNode) if err != nil { return nil, err } return NewTripleForLoopNode( startBlockNode, precontinuationAssignments, continuationExpressionNode, continuationExpressionToken, updateBlockNode, bodyBlockNode, ), 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 (int i = 0; i < 10; i += 1) { <--- frame #2 for for-loop bindvars (right here) // x = 2 <--- frame #3 for for-loop locals // } // x = 3; <--- back in frame #1 main // ' // func (node *TripleForLoopNode) Execute(state *runtime.State) (*BlockExitPayload, error) { // Make a frame for the loop variables. state.Stack.PushStackFrame() defer state.Stack.PopStackFrame() // Use ExecuteFrameless here, otherwise the start-statements would be // within an ephemeral, isolated frame and not accessible to the remaining // parts of the for-loop. _, err := node.startBlockNode.ExecuteFrameless(state) if err != nil { return nil, err } for { for _, precontinuationAssignment := range node.precontinuationAssignments { _, err := precontinuationAssignment.Execute(state) if err != nil { return nil, err } } if node.continuationExpressionNode != nil { // empty is true continuationValue := node.continuationExpressionNode.Evaluate(state) boolValue, isBool := continuationValue.GetBoolValue() if !isBool { return nil, fmt.Errorf( "mlr: for-loop continuation did not evaluate to boolean%s.", dsl.TokenToLocationInfo(node.continuationExpressionToken), ) } if boolValue == false { break } } blockExitPayload, err := node.bodyBlockNode.Execute(state) if err != nil { return nil, err } if blockExitPayload != nil { if blockExitPayload.blockExitStatus == BLOCK_EXIT_BREAK { break } // If BLOCK_EXIT_CONTINUE, keep going -- this means the body was exited // early but we keep going at this level. In particular we still // need to execute the update-block. if blockExitPayload.blockExitStatus == BLOCK_EXIT_RETURN_VOID { return blockExitPayload, nil } if blockExitPayload.blockExitStatus == BLOCK_EXIT_RETURN_VALUE { return blockExitPayload, nil } } // The loop body will push its own frame. state.Stack.PushStackFrame() _, err = node.updateBlockNode.ExecuteFrameless(state) if err != nil { state.Stack.PopStackFrame() return nil, err } state.Stack.PopStackFrame() } return nil, nil }