mirror of
https://github.com/johnkerl/miller.git
synced 2026-01-23 02:14:13 +00:00
940 lines
28 KiB
Go
940 lines
28 KiB
Go
// ================================================================
|
|
// 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 {
|
|
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
|
|
}
|