miller/pkg/dsl/cst/while.go
Adam Lesperance 085e831668
The package version must match the major tag version (#1654)
* Update package version

* Update makefile targets

* Update readme packages

* Remaining old packages via rg/sd
2024-09-20 12:10:11 -04:00

169 lines
4.6 KiB
Go

// ================================================================
// This is for while/do-while loops
// ================================================================
package cst
import (
"fmt"
"github.com/johnkerl/miller/v6/pkg/dsl"
"github.com/johnkerl/miller/v6/pkg/lib"
"github.com/johnkerl/miller/v6/pkg/parsing/token"
"github.com/johnkerl/miller/v6/pkg/runtime"
)
// ================================================================
type WhileLoopNode struct {
conditionNode IEvaluable
conditionToken *token.Token
statementBlockNode *StatementBlockNode
}
func NewWhileLoopNode(
conditionNode IEvaluable,
conditionToken *token.Token,
statementBlockNode *StatementBlockNode,
) *WhileLoopNode {
return &WhileLoopNode{
conditionNode: conditionNode,
conditionToken: conditionToken,
statementBlockNode: statementBlockNode,
}
}
func (root *RootNode) BuildWhileLoopNode(astNode *dsl.ASTNode) (*WhileLoopNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeWhileLoop)
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
conditionNode, err := root.BuildEvaluableNode(astNode.Children[0])
if err != nil {
return nil, err
}
conditionToken := astNode.Children[0].Token
statementBlockNode, err := root.BuildStatementBlockNode(astNode.Children[1])
if err != nil {
return nil, err
}
return NewWhileLoopNode(
conditionNode,
conditionToken,
statementBlockNode,
), nil
}
// ----------------------------------------------------------------
func (node *WhileLoopNode) Execute(state *runtime.State) (*BlockExitPayload, error) {
for {
condition := node.conditionNode.Evaluate(state)
boolValue, isBool := condition.GetBoolValue()
if !isBool {
return nil, fmt.Errorf(
"mlr: conditional expression did not evaluate to boolean%s.",
dsl.TokenToLocationInfo(node.conditionToken),
)
}
if boolValue != true {
break
}
blockExitPayload, err := node.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: handle return statements
// TODO: runtime errors for any other types
}
return nil, nil
}
// ================================================================
type DoWhileLoopNode struct {
statementBlockNode *StatementBlockNode
conditionNode IEvaluable
conditionToken *token.Token
}
func NewDoWhileLoopNode(
statementBlockNode *StatementBlockNode,
conditionNode IEvaluable,
conditionToken *token.Token,
) *DoWhileLoopNode {
return &DoWhileLoopNode{
statementBlockNode: statementBlockNode,
conditionNode: conditionNode,
conditionToken: conditionToken,
}
}
func (root *RootNode) BuildDoWhileLoopNode(astNode *dsl.ASTNode) (*DoWhileLoopNode, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeDoWhileLoop)
lib.InternalCodingErrorIf(len(astNode.Children) != 2)
statementBlockNode, err := root.BuildStatementBlockNode(astNode.Children[0])
if err != nil {
return nil, err
}
conditionNode, err := root.BuildEvaluableNode(astNode.Children[1])
if err != nil {
return nil, err
}
conditionToken := astNode.Children[1].Token
return NewDoWhileLoopNode(
statementBlockNode,
conditionNode,
conditionToken,
), nil
}
// ----------------------------------------------------------------
func (node *DoWhileLoopNode) Execute(state *runtime.State) (*BlockExitPayload, error) {
for {
blockExitPayload, err := node.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: handle return statements
// TODO: runtime errors for any other types
condition := node.conditionNode.Evaluate(state)
boolValue, isBool := condition.GetBoolValue()
if !isBool {
return nil, fmt.Errorf(
"mlr: conditional expression did not evaluate to boolean%s.",
dsl.TokenToLocationInfo(node.conditionToken),
)
}
if boolValue == false {
break
}
}
return nil, nil
}