mirror of
https://github.com/johnkerl/miller.git
synced 2026-01-23 02:14:13 +00:00
* address some staticcheck issues * address some staticcheck issues * address some staticcheck issues * address some staticcheck issues
171 lines
5.4 KiB
Go
171 lines
5.4 KiB
Go
// ================================================================
|
|
// This handles anything on the right-hand sides of assignment statements.
|
|
// (Also, computed field names on the left-hand sides of assignment
|
|
// statements.)
|
|
// ================================================================
|
|
|
|
package cst
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/johnkerl/miller/internal/pkg/dsl"
|
|
"github.com/johnkerl/miller/internal/pkg/lib"
|
|
"github.com/johnkerl/miller/internal/pkg/mlrval"
|
|
"github.com/johnkerl/miller/internal/pkg/runtime"
|
|
)
|
|
|
|
// ----------------------------------------------------------------
|
|
func (root *RootNode) BuildEvaluableNode(astNode *dsl.ASTNode) (IEvaluable, error) {
|
|
|
|
if astNode.Children == nil {
|
|
return root.BuildLeafNode(astNode)
|
|
}
|
|
|
|
switch astNode.Type {
|
|
|
|
case dsl.NodeTypeArrayLiteral: // [...]
|
|
return root.BuildArrayLiteralNode(astNode)
|
|
|
|
case dsl.NodeTypeMapLiteral: // {...}
|
|
return root.BuildMapLiteralNode(astNode)
|
|
|
|
case dsl.NodeTypeArrayOrMapIndexAccess: // x[...]
|
|
return root.BuildArrayOrMapIndexAccessNode(astNode)
|
|
|
|
case dsl.NodeTypeArraySliceAccess: // myarray[lo:hi]
|
|
return root.BuildArraySliceAccessNode(astNode)
|
|
|
|
case dsl.NodeTypePositionalFieldName: // $[[...]]
|
|
return root.BuildPositionalFieldNameNode(astNode)
|
|
|
|
case dsl.NodeTypePositionalFieldValue: // $[[[...]]]
|
|
return root.BuildPositionalFieldValueNode(astNode)
|
|
|
|
case dsl.NodeTypeArrayOrMapPositionalNameAccess: // mymap[[...]]]
|
|
return root.BuildArrayOrMapPositionalNameAccessNode(astNode)
|
|
|
|
case dsl.NodeTypeArrayOrMapPositionalValueAccess: // mymap[[[...]]]
|
|
return root.BuildArrayOrMapPositionalValueAccessNode(astNode)
|
|
|
|
case dsl.NodeTypeIndirectFieldValue: // $[...]
|
|
return root.BuildIndirectFieldValueNode(astNode)
|
|
case dsl.NodeTypeIndirectOosvarValue: // $[...]
|
|
return root.BuildIndirectOosvarValueNode(astNode)
|
|
|
|
case dsl.NodeTypeEnvironmentVariable: // ENV["NAME"]
|
|
return root.BuildEnvironmentVariableNode(astNode)
|
|
|
|
// Operators are just functions with infix syntax so we treat them like
|
|
// functions in the CST. (The distinction between infix syntax, e.g.
|
|
// '1+2', and prefix syntax, e.g. 'plus(1,2)' disappears post-parse -- both
|
|
// parse to the same-shape AST.)
|
|
case dsl.NodeTypeOperator:
|
|
return root.BuildFunctionCallsiteNode(astNode)
|
|
case dsl.NodeTypeFunctionCallsite:
|
|
return root.BuildFunctionCallsiteNode(astNode)
|
|
|
|
// The dot operator is a little different from other operators since it's
|
|
// type-dependent: for strings/int/bools etc it's just concatenation of
|
|
// string representations, but if the left-hand side is a map, it's a
|
|
// key-lookup with an unquoted literal on the right. E.g. mymap.foo is the
|
|
// same as mymap["foo"].
|
|
case dsl.NodeTypeDotOperator:
|
|
return root.BuildDotCallsiteNode(astNode)
|
|
|
|
// Function literals like 'func (a,b) { return b - a }'
|
|
case dsl.NodeTypeUnnamedFunctionDefinition:
|
|
return root.BuildUnnamedUDFNode(astNode)
|
|
}
|
|
|
|
return nil, fmt.Errorf(
|
|
"at CST BuildEvaluableNode: unhandled AST node type %s", string(astNode.Type),
|
|
)
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
type IndirectFieldValueNode struct {
|
|
fieldNameEvaluable IEvaluable
|
|
}
|
|
|
|
func (root *RootNode) BuildIndirectFieldValueNode(
|
|
astNode *dsl.ASTNode,
|
|
) (*IndirectFieldValueNode, error) {
|
|
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeIndirectFieldValue)
|
|
lib.InternalCodingErrorIf(astNode.Children == nil)
|
|
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
|
|
fieldNameEvaluable, err := root.BuildEvaluableNode(astNode.Children[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &IndirectFieldValueNode{
|
|
fieldNameEvaluable: fieldNameEvaluable,
|
|
}, nil
|
|
}
|
|
|
|
func (node *IndirectFieldValueNode) Evaluate(
|
|
state *runtime.State,
|
|
) *mlrval.Mlrval { // TODO: err
|
|
fieldName := node.fieldNameEvaluable.Evaluate(state)
|
|
if fieldName.IsAbsent() {
|
|
return mlrval.ABSENT
|
|
}
|
|
|
|
// For normal DSL use the CST validator will prohibit this from being
|
|
// called in places the current record is undefined (begin and end blocks).
|
|
// However in the REPL people can read past end of stream and still try to
|
|
// print inrec attributes. Also, a UDF/UDS invoked from begin/end could try
|
|
// to access the inrec, and that would get past the validator.
|
|
if state.Inrec == nil {
|
|
return mlrval.ABSENT
|
|
}
|
|
|
|
value, err := state.Inrec.GetWithMlrvalIndex(fieldName)
|
|
if err != nil {
|
|
// Key isn't int or string.
|
|
// TODO: needs error-return in the API
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
if value == nil {
|
|
return mlrval.ABSENT
|
|
}
|
|
return value
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
type IndirectOosvarValueNode struct {
|
|
oosvarNameEvaluable IEvaluable
|
|
}
|
|
|
|
func (root *RootNode) BuildIndirectOosvarValueNode(
|
|
astNode *dsl.ASTNode,
|
|
) (*IndirectOosvarValueNode, error) {
|
|
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeIndirectOosvarValue)
|
|
lib.InternalCodingErrorIf(astNode.Children == nil)
|
|
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
|
|
oosvarNameEvaluable, err := root.BuildEvaluableNode(astNode.Children[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &IndirectOosvarValueNode{
|
|
oosvarNameEvaluable: oosvarNameEvaluable,
|
|
}, nil
|
|
}
|
|
|
|
func (node *IndirectOosvarValueNode) Evaluate(
|
|
state *runtime.State,
|
|
) *mlrval.Mlrval { // TODO: err
|
|
oosvarName := node.oosvarNameEvaluable.Evaluate(state)
|
|
if oosvarName.IsAbsent() {
|
|
return mlrval.ABSENT
|
|
}
|
|
|
|
value := state.Oosvars.Get(oosvarName.String())
|
|
if value == nil {
|
|
return mlrval.ABSENT
|
|
}
|
|
|
|
return value
|
|
}
|