miller/pkg/dsl/cst/lvalues.go
John Kerl 268a96d002
Export library code in pkg/ (#1391)
* Export library code in `pkg/`

* new doc page
2023-09-10 17:15:13 -04:00

1137 lines
32 KiB
Go

// ================================================================
// This is for Lvalues, i.e. things on the left-hand-side of an assignment
// statement.
// ================================================================
package cst
import (
"fmt"
"os"
"github.com/johnkerl/miller/pkg/dsl"
"github.com/johnkerl/miller/pkg/lib"
"github.com/johnkerl/miller/pkg/mlrval"
"github.com/johnkerl/miller/pkg/runtime"
)
// ----------------------------------------------------------------
func (root *RootNode) BuildAssignableNode(
astNode *dsl.ASTNode,
) (IAssignable, error) {
switch astNode.Type {
case dsl.NodeTypeDirectFieldValue:
return root.BuildDirectFieldValueLvalueNode(astNode)
case dsl.NodeTypeIndirectFieldValue:
return root.BuildIndirectFieldValueLvalueNode(astNode)
case dsl.NodeTypePositionalFieldName:
return root.BuildPositionalFieldNameLvalueNode(astNode)
case dsl.NodeTypePositionalFieldValue:
return root.BuildPositionalFieldValueLvalueNode(astNode)
case dsl.NodeTypeFullSrec:
return root.BuildFullSrecLvalueNode(astNode)
case dsl.NodeTypeDirectOosvarValue:
return root.BuildDirectOosvarValueLvalueNode(astNode)
case dsl.NodeTypeIndirectOosvarValue:
return root.BuildIndirectOosvarValueLvalueNode(astNode)
case dsl.NodeTypeFullOosvar:
return root.BuildFullOosvarLvalueNode(astNode)
case dsl.NodeTypeLocalVariable:
return root.BuildLocalVariableLvalueNode(astNode)
case dsl.NodeTypeArrayOrMapPositionalNameAccess:
return nil, fmt.Errorf(
"mlr: '[[...]]' is allowed on assignment left-hand sides only when immediately preceded by '$'.",
)
case dsl.NodeTypeArrayOrMapPositionalValueAccess:
return nil, fmt.Errorf(
"mlr: '[[[...]]]' is allowed on assignment left-hand sides only when immediately preceded by '$'.",
)
case dsl.NodeTypeArrayOrMapIndexAccess:
return root.BuildIndexedLvalueNode(astNode)
case dsl.NodeTypeDotOperator:
return root.BuildIndexedLvalueNode(astNode)
case dsl.NodeTypeEnvironmentVariable:
return root.BuildEnvironmentVariableLvalueNode(astNode)
}
return nil, fmt.Errorf(
"at CST BuildAssignableNode: unhandled AST node " + string(astNode.Type),
)
}
// ----------------------------------------------------------------
type DirectFieldValueLvalueNode struct {
lhsFieldName *mlrval.Mlrval
}
func (root *RootNode) BuildDirectFieldValueLvalueNode(
astNode *dsl.ASTNode,
) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeDirectFieldValue)
lhsFieldName := mlrval.FromString(string(astNode.Token.Lit))
return NewDirectFieldValueLvalueNode(lhsFieldName), nil
}
func NewDirectFieldValueLvalueNode(lhsFieldName *mlrval.Mlrval) *DirectFieldValueLvalueNode {
return &DirectFieldValueLvalueNode{
lhsFieldName: lhsFieldName,
}
}
func (node *DirectFieldValueLvalueNode) Assign(
rvalue *mlrval.Mlrval,
state *runtime.State,
) error {
return node.AssignIndexed(rvalue, nil, state)
}
func (node *DirectFieldValueLvalueNode) AssignIndexed(
rvalue *mlrval.Mlrval,
indices []*mlrval.Mlrval,
state *runtime.State,
) error {
// 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 fmt.Errorf("there is no current record to assign to.")
}
// AssignmentNode checks for absent, so we just assign whatever we get
lib.InternalCodingErrorIf(rvalue.IsAbsent())
if indices == nil {
err := state.Inrec.PutCopyWithMlrvalIndex(node.lhsFieldName, rvalue)
if err != nil {
return err
}
return nil
} else {
return state.Inrec.PutIndexed(
append([]*mlrval.Mlrval{node.lhsFieldName}, indices...),
rvalue,
)
}
}
func (node *DirectFieldValueLvalueNode) Unassign(
state *runtime.State,
) {
node.UnassignIndexed(nil, state)
}
func (node *DirectFieldValueLvalueNode) UnassignIndexed(
indices []*mlrval.Mlrval,
state *runtime.State,
) {
// 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
}
if indices == nil {
lib.InternalCodingErrorIf(!node.lhsFieldName.IsString())
name := node.lhsFieldName.String()
state.Inrec.Remove(name)
} else {
state.Inrec.RemoveIndexed(
append([]*mlrval.Mlrval{node.lhsFieldName}, indices...),
)
}
}
// ----------------------------------------------------------------
type IndirectFieldValueLvalueNode struct {
lhsFieldNameExpression IEvaluable
}
func (root *RootNode) BuildIndirectFieldValueLvalueNode(
astNode *dsl.ASTNode,
) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeIndirectFieldValue)
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
lhsFieldNameExpression, err := root.BuildEvaluableNode(astNode.Children[0])
if err != nil {
return nil, err
}
return NewIndirectFieldValueLvalueNode(lhsFieldNameExpression), nil
}
func NewIndirectFieldValueLvalueNode(
lhsFieldNameExpression IEvaluable,
) *IndirectFieldValueLvalueNode {
return &IndirectFieldValueLvalueNode{
lhsFieldNameExpression: lhsFieldNameExpression,
}
}
func (node *IndirectFieldValueLvalueNode) Assign(
rvalue *mlrval.Mlrval,
state *runtime.State,
) error {
return node.AssignIndexed(rvalue, nil, state)
}
func (node *IndirectFieldValueLvalueNode) AssignIndexed(
rvalue *mlrval.Mlrval,
indices []*mlrval.Mlrval,
state *runtime.State,
) error {
// AssignmentNode checks for absentness of the rvalue, so we just assign
// whatever we get
lib.InternalCodingErrorIf(rvalue.IsAbsent())
// 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 fmt.Errorf("there is no current record to assign to.")
}
lhsFieldName := node.lhsFieldNameExpression.Evaluate(state)
if indices == nil {
err := state.Inrec.PutCopyWithMlrvalIndex(lhsFieldName, rvalue)
if err != nil {
return err
}
return nil
} else {
return state.Inrec.PutIndexed(
append([]*mlrval.Mlrval{lhsFieldName.Copy()}, indices...),
rvalue,
)
}
}
func (node *IndirectFieldValueLvalueNode) Unassign(
state *runtime.State,
) {
node.UnassignIndexed(nil, state)
}
func (node *IndirectFieldValueLvalueNode) UnassignIndexed(
indices []*mlrval.Mlrval,
state *runtime.State,
) {
// 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
}
lhsFieldName := node.lhsFieldNameExpression.Evaluate(state)
if indices == nil {
name := lhsFieldName.String()
state.Inrec.Remove(name)
} else {
state.Inrec.RemoveIndexed(
append([]*mlrval.Mlrval{lhsFieldName.Copy()}, indices...),
)
}
}
// ----------------------------------------------------------------
// Set the name at 2nd positional index in the current stream record: e.g.
// '$[[2]] = "abc"
type PositionalFieldNameLvalueNode struct {
lhsFieldIndexExpression IEvaluable
}
func (root *RootNode) BuildPositionalFieldNameLvalueNode(
astNode *dsl.ASTNode,
) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypePositionalFieldName)
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
lhsFieldIndexExpression, err := root.BuildEvaluableNode(astNode.Children[0])
if err != nil {
return nil, err
}
return NewPositionalFieldNameLvalueNode(lhsFieldIndexExpression), nil
}
func NewPositionalFieldNameLvalueNode(
lhsFieldIndexExpression IEvaluable,
) *PositionalFieldNameLvalueNode {
return &PositionalFieldNameLvalueNode{
lhsFieldIndexExpression: lhsFieldIndexExpression,
}
}
func (node *PositionalFieldNameLvalueNode) Assign(
rvalue *mlrval.Mlrval,
state *runtime.State,
) error {
// AssignmentNode checks for absentness of the rvalue, so we just assign
// whatever we get
lib.InternalCodingErrorIf(rvalue.IsAbsent())
// 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 fmt.Errorf("there is no current record to assign to.")
}
lhsFieldIndex := node.lhsFieldIndexExpression.Evaluate(state)
index, ok := lhsFieldIndex.GetIntValue()
if ok {
// TODO: incorporate error-return into this API
state.Inrec.PutNameWithPositionalIndex(index, rvalue)
return nil
} else {
return fmt.Errorf(
"mlr: positional index for $[[...]] assignment must be integer; got %s.",
lhsFieldIndex.GetTypeName(),
)
}
}
func (node *PositionalFieldNameLvalueNode) AssignIndexed(
rvalue *mlrval.Mlrval,
indices []*mlrval.Mlrval,
state *runtime.State,
) error {
// TODO: reconsider this if /when we decide to allow string-slice
// assignments.
return fmt.Errorf(
"mlr: $[[...]] = ... expressions are not indexable.",
)
}
func (node *PositionalFieldNameLvalueNode) Unassign(
state *runtime.State,
) {
node.UnassignIndexed(nil, state)
}
func (node *PositionalFieldNameLvalueNode) UnassignIndexed(
indices []*mlrval.Mlrval,
state *runtime.State,
) {
lhsFieldIndex := node.lhsFieldIndexExpression.Evaluate(state)
// 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
}
if indices == nil {
index, ok := lhsFieldIndex.GetIntValue()
if ok {
state.Inrec.RemoveWithPositionalIndex(index)
} else {
// TODO: incorporate error-return into this API
}
} else {
// xxx positional
state.Inrec.RemoveIndexed(
append([]*mlrval.Mlrval{lhsFieldIndex}, indices...),
)
}
}
// ----------------------------------------------------------------
// Set the value at 2nd positional index in the current stream record: e.g.
// '$[[[2]]] = "abc"
type PositionalFieldValueLvalueNode struct {
lhsFieldIndexExpression IEvaluable
}
func (root *RootNode) BuildPositionalFieldValueLvalueNode(
astNode *dsl.ASTNode,
) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypePositionalFieldValue)
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
lhsFieldIndexExpression, err := root.BuildEvaluableNode(astNode.Children[0])
if err != nil {
return nil, err
}
return NewPositionalFieldValueLvalueNode(lhsFieldIndexExpression), nil
}
func NewPositionalFieldValueLvalueNode(
lhsFieldIndexExpression IEvaluable,
) *PositionalFieldValueLvalueNode {
return &PositionalFieldValueLvalueNode{
lhsFieldIndexExpression: lhsFieldIndexExpression,
}
}
func (node *PositionalFieldValueLvalueNode) Assign(
rvalue *mlrval.Mlrval,
state *runtime.State,
) error {
return node.AssignIndexed(rvalue, nil, state)
}
func (node *PositionalFieldValueLvalueNode) AssignIndexed(
rvalue *mlrval.Mlrval,
indices []*mlrval.Mlrval,
state *runtime.State,
) error {
// AssignmentNode checks for absentness of the rvalue, so we just assign
// whatever we get
lib.InternalCodingErrorIf(rvalue.IsAbsent())
// 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 fmt.Errorf("there is no current record to assign to.")
}
lhsFieldIndex := node.lhsFieldIndexExpression.Evaluate(state)
if indices == nil {
index, ok := lhsFieldIndex.GetIntValue()
if ok {
// TODO: incorporate error-return into this API
//err := state.Inrec.PutCopyWithPositionalIndex(&lhsFieldIndex, rvalue)
//if err != nil {
//return err
//}
//return nil
state.Inrec.PutCopyWithPositionalIndex(index, rvalue)
return nil
} else {
return fmt.Errorf(
"mlr: positional index for $[[[...]]] assignment must be integer; got %s.",
lhsFieldIndex.GetTypeName(),
)
}
} else {
// xxx positional
return state.Inrec.PutIndexed(
append([]*mlrval.Mlrval{lhsFieldIndex}, indices...),
rvalue,
)
}
}
// Same code as PositionalFieldNameLvalueNode.
// May as well let them do 'unset $[[[7]]]' as well as $[[7]]'.
func (node *PositionalFieldValueLvalueNode) Unassign(
state *runtime.State,
) {
node.UnassignIndexed(nil, state)
}
func (node *PositionalFieldValueLvalueNode) UnassignIndexed(
indices []*mlrval.Mlrval,
state *runtime.State,
) {
// 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
}
lhsFieldIndex := node.lhsFieldIndexExpression.Evaluate(state)
if indices == nil {
index, ok := lhsFieldIndex.GetIntValue()
if ok {
state.Inrec.RemoveWithPositionalIndex(index)
} else {
// TODO: incorporate error-return into this API
}
} else {
// xxx positional
state.Inrec.RemoveIndexed(
append([]*mlrval.Mlrval{lhsFieldIndex}, indices...),
)
}
}
// ----------------------------------------------------------------
type FullSrecLvalueNode struct {
}
func (root *RootNode) BuildFullSrecLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeFullSrec)
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(astNode.Children != nil)
return NewFullSrecLvalueNode(), nil
}
func NewFullSrecLvalueNode() *FullSrecLvalueNode {
return &FullSrecLvalueNode{}
}
func (node *FullSrecLvalueNode) Assign(
rvalue *mlrval.Mlrval,
state *runtime.State,
) error {
return node.AssignIndexed(rvalue, nil, state)
}
func (node *FullSrecLvalueNode) AssignIndexed(
rvalue *mlrval.Mlrval,
indices []*mlrval.Mlrval,
state *runtime.State,
) error {
// 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 fmt.Errorf("there is no current record to assign to.")
}
// AssignmentNode checks for absentness of the rvalue, so we just assign
// whatever we get
lib.InternalCodingErrorIf(rvalue.IsAbsent())
// The input record is a *Mlrmap so just invoke its PutIndexed.
err := state.Inrec.PutIndexed(indices, rvalue)
if err != nil {
return err
}
return nil
}
func (node *FullSrecLvalueNode) Unassign(
state *runtime.State,
) {
node.UnassignIndexed(nil, state)
}
func (node *FullSrecLvalueNode) UnassignIndexed(
indices []*mlrval.Mlrval,
state *runtime.State,
) {
// 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
}
if indices == nil {
state.Inrec.Clear()
} else {
state.Inrec.RemoveIndexed(indices)
}
}
// ----------------------------------------------------------------
type DirectOosvarValueLvalueNode struct {
lhsOosvarName *mlrval.Mlrval
}
func (root *RootNode) BuildDirectOosvarValueLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeDirectOosvarValue)
lhsOosvarName := mlrval.FromString(string(astNode.Token.Lit))
return NewDirectOosvarValueLvalueNode(lhsOosvarName), nil
}
func NewDirectOosvarValueLvalueNode(lhsOosvarName *mlrval.Mlrval) *DirectOosvarValueLvalueNode {
return &DirectOosvarValueLvalueNode{
lhsOosvarName: lhsOosvarName,
}
}
func (node *DirectOosvarValueLvalueNode) Assign(
rvalue *mlrval.Mlrval,
state *runtime.State,
) error {
return node.AssignIndexed(rvalue, nil, state)
}
func (node *DirectOosvarValueLvalueNode) AssignIndexed(
rvalue *mlrval.Mlrval,
indices []*mlrval.Mlrval,
state *runtime.State,
) error {
// AssignmentNode checks for absent, so we just assign whatever we get
lib.InternalCodingErrorIf(rvalue.IsAbsent())
if indices == nil {
err := state.Oosvars.PutCopyWithMlrvalIndex(node.lhsOosvarName, rvalue)
if err != nil {
return err
}
return nil
} else {
return state.Oosvars.PutIndexed(
append([]*mlrval.Mlrval{node.lhsOosvarName}, indices...),
rvalue,
)
}
}
func (node *DirectOosvarValueLvalueNode) Unassign(
state *runtime.State,
) {
node.UnassignIndexed(nil, state)
}
func (node *DirectOosvarValueLvalueNode) UnassignIndexed(
indices []*mlrval.Mlrval,
state *runtime.State,
) {
if indices == nil {
name := node.lhsOosvarName.String()
state.Oosvars.Remove(name)
} else {
state.Oosvars.RemoveIndexed(
append([]*mlrval.Mlrval{node.lhsOosvarName}, indices...),
)
}
}
// ----------------------------------------------------------------
type IndirectOosvarValueLvalueNode struct {
lhsOosvarNameExpression IEvaluable
}
func (root *RootNode) BuildIndirectOosvarValueLvalueNode(
astNode *dsl.ASTNode,
) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeIndirectOosvarValue)
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
lhsOosvarNameExpression, err := root.BuildEvaluableNode(astNode.Children[0])
if err != nil {
return nil, err
}
return NewIndirectOosvarValueLvalueNode(lhsOosvarNameExpression), nil
}
func NewIndirectOosvarValueLvalueNode(
lhsOosvarNameExpression IEvaluable,
) *IndirectOosvarValueLvalueNode {
return &IndirectOosvarValueLvalueNode{
lhsOosvarNameExpression: lhsOosvarNameExpression,
}
}
func (node *IndirectOosvarValueLvalueNode) Assign(
rvalue *mlrval.Mlrval,
state *runtime.State,
) error {
return node.AssignIndexed(rvalue, nil, state)
}
func (node *IndirectOosvarValueLvalueNode) AssignIndexed(
rvalue *mlrval.Mlrval,
indices []*mlrval.Mlrval,
state *runtime.State,
) error {
// AssignmentNode checks for absentness of the rvalue, so we just assign
// whatever we get
lib.InternalCodingErrorIf(rvalue.IsAbsent())
lhsOosvarName := node.lhsOosvarNameExpression.Evaluate(state)
if indices == nil {
err := state.Oosvars.PutCopyWithMlrvalIndex(lhsOosvarName, rvalue)
if err != nil {
return err
}
return nil
} else {
return state.Oosvars.PutIndexed(
append([]*mlrval.Mlrval{lhsOosvarName.Copy()}, indices...),
rvalue,
)
}
}
func (node *IndirectOosvarValueLvalueNode) Unassign(
state *runtime.State,
) {
node.UnassignIndexed(nil, state)
}
func (node *IndirectOosvarValueLvalueNode) UnassignIndexed(
indices []*mlrval.Mlrval,
state *runtime.State,
) {
lhsOosvarName := node.lhsOosvarNameExpression.Evaluate(state)
if indices == nil {
sname := lhsOosvarName.String()
state.Oosvars.Remove(sname)
} else {
state.Oosvars.RemoveIndexed(
append([]*mlrval.Mlrval{lhsOosvarName}, indices...),
)
}
}
// ----------------------------------------------------------------
type FullOosvarLvalueNode struct {
}
func (root *RootNode) BuildFullOosvarLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeFullOosvar)
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(astNode.Children != nil)
return NewFullOosvarLvalueNode(), nil
}
func NewFullOosvarLvalueNode() *FullOosvarLvalueNode {
return &FullOosvarLvalueNode{}
}
func (node *FullOosvarLvalueNode) Assign(
rvalue *mlrval.Mlrval,
state *runtime.State,
) error {
return node.AssignIndexed(rvalue, nil, state)
}
func (node *FullOosvarLvalueNode) AssignIndexed(
rvalue *mlrval.Mlrval,
indices []*mlrval.Mlrval,
state *runtime.State,
) error {
// AssignmentNode checks for absentness of the rvalue, so we just assign
// whatever we get
lib.InternalCodingErrorIf(rvalue.IsAbsent())
// The input record is a *Mlrmap so just invoke its PutIndexed.
err := state.Oosvars.PutIndexed(indices, rvalue)
if err != nil {
return err
}
return nil
}
func (node *FullOosvarLvalueNode) Unassign(
state *runtime.State,
) {
node.UnassignIndexed(nil, state)
}
func (node *FullOosvarLvalueNode) UnassignIndexed(
indices []*mlrval.Mlrval,
state *runtime.State,
) {
if indices == nil {
state.Oosvars.Clear()
} else {
state.Oosvars.RemoveIndexed(indices)
}
}
// ----------------------------------------------------------------
type LocalVariableLvalueNode struct {
stackVariable *runtime.StackVariable
typeName string
// a = 1;
// b = 1;
// if (true) {
// a = 3; <-- defineTypedAtScope is false; updates outer a
// var b = 4; <-- defineTypedAtScope is true; creates new inner b
// }
defineTypedAtScope bool
}
func (root *RootNode) BuildLocalVariableLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeLocalVariable)
// TODO require type mask in strict mode
variableName := string(astNode.Token.Lit)
typeName := "any"
defineTypedAtScope := false
if astNode.Children == nil { // untyped, like 'x = 3'
if root.strictMode {
return nil, fmt.Errorf(
"mlr: need typedecl such as \"var\", \"str\", \"num\", etc. for variable \"%s\" in strict mode",
variableName,
)
}
} else { // typed, like 'num x = 3'
typeNode := astNode.Children[0]
lib.InternalCodingErrorIf(typeNode.Type != dsl.NodeTypeTypedecl)
typeName = string(typeNode.Token.Lit)
defineTypedAtScope = true
}
return NewLocalVariableLvalueNode(
runtime.NewStackVariable(variableName),
typeName,
defineTypedAtScope,
), nil
}
func NewLocalVariableLvalueNode(
stackVariable *runtime.StackVariable,
typeName string,
defineTypedAtScope bool,
) *LocalVariableLvalueNode {
return &LocalVariableLvalueNode{
stackVariable: stackVariable,
typeName: typeName,
defineTypedAtScope: defineTypedAtScope,
}
}
func (node *LocalVariableLvalueNode) Assign(
rvalue *mlrval.Mlrval,
state *runtime.State,
) error {
return node.AssignIndexed(rvalue, nil, state)
}
func (node *LocalVariableLvalueNode) AssignIndexed(
rvalue *mlrval.Mlrval,
indices []*mlrval.Mlrval,
state *runtime.State,
) error {
// AssignmentNode checks for absent, so we just assign whatever we get
lib.InternalCodingErrorIf(rvalue.IsAbsent())
var err error = nil
if indices == nil {
if node.defineTypedAtScope {
err = state.Stack.DefineTypedAtScope(node.stackVariable, node.typeName, rvalue)
} else {
err = state.Stack.Set(node.stackVariable, rvalue)
}
} else {
// There is no 'map x[1] = {}' in the DSL grammar.
lib.InternalCodingErrorIf(node.defineTypedAtScope)
err = state.Stack.SetIndexed(node.stackVariable, indices, rvalue)
}
return err
}
func (node *LocalVariableLvalueNode) Unassign(
state *runtime.State,
) {
node.UnassignIndexed(nil, state)
}
func (node *LocalVariableLvalueNode) UnassignIndexed(
indices []*mlrval.Mlrval,
state *runtime.State,
) {
if indices == nil {
state.Stack.Unset(node.stackVariable)
} else {
state.Stack.UnsetIndexed(node.stackVariable, indices)
}
}
// ----------------------------------------------------------------
// IndexedValueNode is a delegator to base-lvalue types.
// * The baseLvalue is some IAssignable
// * The indexEvaluables are an array of IEvaluables
// * Each needs to evaluate to int or string
// * Assignment needs to walk each level:
// o error if ith mlrval is int and that level isn't an array
// o error if ith mlrval is string and that level isn't a map
// o error for any other types -- maybe absent-handling for heterogeneity ...
// ----------------------------------------------------------------
type IndexedLvalueNode struct {
baseLvalue IAssignable
indexEvaluables []IEvaluable
}
// Either 'mymap["attr"]' or 'mymap.attr'. Furthermore they can be mixed as in
// 'mymap["foo"].bar' or 'mymap.foo["bar"]'.
func (root *RootNode) BuildIndexedLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeArrayOrMapIndexAccess && astNode.Type != dsl.NodeTypeDotOperator)
lib.InternalCodingErrorIf(astNode == nil)
var baseLvalue IAssignable = nil
indexEvaluables := make([]IEvaluable, 0)
var err error = nil
// $ mlr -n put -v '$x[1][2]=3'
// DSL EXPRESSION:
// $x[1][2]=3
// AST:
// * StatementBlock
// * Assignment "="
// * ArrayOrMapIndexAccess "[]"
// * ArrayOrMapIndexAccess "[]"
// * DirectFieldValue "x"
// * IntLiteral "1"
// * IntLiteral "2"
// * IntLiteral "3"
// In the AST, the indices come in last-shallowest, down to first-deepest,
// then the base Lvalue.
walkerNode := astNode
for {
if walkerNode.Type == dsl.NodeTypeArrayOrMapIndexAccess {
lib.InternalCodingErrorIf(walkerNode == nil)
lib.InternalCodingErrorIf(len(walkerNode.Children) != 2)
indexEvaluable, err := root.BuildEvaluableNode(walkerNode.Children[1])
if err != nil {
return nil, err
}
indexEvaluables = append([]IEvaluable{indexEvaluable}, indexEvaluables...)
walkerNode = walkerNode.Children[0]
} else if walkerNode.Type == dsl.NodeTypeDotOperator {
lib.InternalCodingErrorIf(walkerNode == nil)
lib.InternalCodingErrorIf(len(walkerNode.Children) != 2)
indexEvaluable := root.BuildStringLiteralNode(string(walkerNode.Children[1].Token.Lit))
indexEvaluables = append([]IEvaluable{indexEvaluable}, indexEvaluables...)
walkerNode = walkerNode.Children[0]
} else {
baseLvalue, err = root.BuildAssignableNode(walkerNode)
if err != nil {
return nil, err
}
break
}
}
return NewIndexedLvalueNode(baseLvalue, indexEvaluables), nil
}
// BuildDottedLvalueNode is basically the same as BuildIndexedLvalueNode except
// at the syntax level:
// 'mymap["x"]["y"]' is the same as 'mymap.x.y'.
func (root *RootNode) BuildDottedLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeDotOperator)
lib.InternalCodingErrorIf(astNode == nil)
var baseLvalue IAssignable = nil
indexEvaluables := make([]IEvaluable, 0)
var err error = nil
// $ mlr -n put -v '$x.a.b=3'
// DSL EXPRESSION:
// $x.a.b=3
// AST:
// * statement block
// * assignment "="
// * dot operator "."
// * dot operator "."
// * direct field value "x"
// * local variable "a"
// * local variable "b"
// * int literal "3"
// In the AST, the indices come in last-shallowest, down to first-deepest,
// then the base Lvalue.
walkerNode := astNode
for {
if walkerNode.Type == dsl.NodeTypeDotOperator {
lib.InternalCodingErrorIf(walkerNode == nil)
lib.InternalCodingErrorIf(len(walkerNode.Children) != 2)
indexEvaluable, err := root.BuildEvaluableNode(walkerNode.Children[1])
walkerNode.Children[1].Print()
if err != nil {
return nil, err
}
indexEvaluables = append([]IEvaluable{indexEvaluable}, indexEvaluables...)
walkerNode = walkerNode.Children[0]
} else {
baseLvalue, err = root.BuildAssignableNode(walkerNode)
if err != nil {
return nil, err
}
break
}
}
return NewIndexedLvalueNode(baseLvalue, indexEvaluables), nil
}
func NewIndexedLvalueNode(
baseLvalue IAssignable,
indexEvaluables []IEvaluable,
) *IndexedLvalueNode {
return &IndexedLvalueNode{
baseLvalue: baseLvalue,
indexEvaluables: indexEvaluables,
}
}
// Computes Lvalue indices and then delegates to the baseLvalue. E.g. for
// '$x[1][2] = 3' or '@x[1][2] = 3', the indices are [1,2], and the baseLvalue
// is '$x' or '@x' respectively.
func (node *IndexedLvalueNode) Assign(
rvalue *mlrval.Mlrval,
state *runtime.State,
) error {
indices := make([]*mlrval.Mlrval, len(node.indexEvaluables))
for i := range node.indexEvaluables {
indices[i] = node.indexEvaluables[i].Evaluate(state)
if indices[i].IsAbsent() {
return nil
}
}
// This lets the user do '$y[ ["a", "b", "c"] ] = $x' in lieu of
// '$y["a"]["b"]["c"] = $x'.
if len(indices) == 1 && indices[0].IsArray() {
indices = mlrval.CopyMlrvalArray(indices[0].GetArray())
}
return node.baseLvalue.AssignIndexed(rvalue, indices, state)
}
func (node *IndexedLvalueNode) AssignIndexed(
rvalue *mlrval.Mlrval,
indices []*mlrval.Mlrval,
state *runtime.State,
) error {
// We are the delegator, not the delegatee
lib.InternalCodingErrorIf(true)
return nil // not reached
}
func (node *IndexedLvalueNode) Unassign(
state *runtime.State,
) {
indices := make([]*mlrval.Mlrval, len(node.indexEvaluables))
for i := range node.indexEvaluables {
indices[i] = node.indexEvaluables[i].Evaluate(state)
}
node.baseLvalue.UnassignIndexed(indices, state)
}
func (node *IndexedLvalueNode) UnassignIndexed(
indices []*mlrval.Mlrval,
state *runtime.State,
) {
// We are the delegator, not the delegatee
lib.InternalCodingErrorIf(true)
}
// ----------------------------------------------------------------
type EnvironmentVariableLvalueNode struct {
nameExpression IEvaluable
}
func (root *RootNode) BuildEnvironmentVariableLvalueNode(astNode *dsl.ASTNode) (IAssignable, error) {
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEnvironmentVariable)
lib.InternalCodingErrorIf(astNode == nil)
lib.InternalCodingErrorIf(len(astNode.Children) != 1)
nameExpression, err := root.BuildEvaluableNode(astNode.Children[0])
if err != nil {
return nil, err
}
return NewEnvironmentVariableLvalueNode(nameExpression), nil
}
func NewEnvironmentVariableLvalueNode(
nameExpression IEvaluable,
) *EnvironmentVariableLvalueNode {
return &EnvironmentVariableLvalueNode{
nameExpression: nameExpression,
}
}
func (node *EnvironmentVariableLvalueNode) Assign(
rvalue *mlrval.Mlrval,
state *runtime.State,
) error {
// AssignmentNode checks for absentness of the rvalue, so we just assign
// whatever we get
lib.InternalCodingErrorIf(rvalue.IsAbsent())
name := node.nameExpression.Evaluate(state)
if name.IsAbsent() {
return nil
}
if !name.IsString() {
return fmt.Errorf(
"assignments to ENV[...] must have string names; got %s \"%s\"\n",
name.GetTypeName(),
name.String(),
)
}
sname := name.String()
svalue := rvalue.String()
os.Setenv(sname, svalue)
if sname == "TZ" {
err := lib.SetTZFromEnv() // affects the time library; notify it
if err != nil {
return err
}
}
return nil
}
func (node *EnvironmentVariableLvalueNode) AssignIndexed(
rvalue *mlrval.Mlrval,
indices []*mlrval.Mlrval,
state *runtime.State,
) error {
return fmt.Errorf("mlr: ENV[...] cannot be indexed.")
}
func (node *EnvironmentVariableLvalueNode) Unassign(
state *runtime.State,
) {
name := node.nameExpression.Evaluate(state)
if name.IsAbsent() {
return
}
if !name.IsString() {
// TODO: needs error-return
return
}
os.Unsetenv(name.String())
}
func (node *EnvironmentVariableLvalueNode) UnassignIndexed(
indices []*mlrval.Mlrval,
state *runtime.State,
) {
// TODO: needs error return
//return errors.New("mlr: ENV[...] cannot be indexed.")
}