// This is for Lvalues, i.e. things on the left-hand-side of an assignment // statement. package cst import ( "errors" "fmt" "os" "github.com/johnkerl/miller/v6/pkg/lib" "github.com/johnkerl/miller/v6/pkg/mlrval" "github.com/johnkerl/miller/v6/pkg/runtime" "github.com/johnkerl/pgpg/go/lib/pkg/asts" ) func (root *RootNode) BuildAssignableNode( astNode *asts.ASTNode, ) (IAssignable, error) { switch astNode.Type { case asts.NodeType(NodeTypeDirectFieldValue), asts.NodeType(NodeTypeBracedFieldValue): return root.BuildDirectFieldValueLvalueNode(astNode) case asts.NodeType(NodeTypeIndirectFieldValue): return root.BuildIndirectFieldValueLvalueNode(astNode) case asts.NodeType(NodeTypeFullSrec): return root.BuildFullSrecLvalueNode(astNode) case asts.NodeType(NodeTypeDirectOosvarValue), asts.NodeType(NodeTypeBracedOosvarValue): return root.BuildDirectOosvarValueLvalueNode(astNode) case asts.NodeType(NodeTypeIndirectOosvarValue): return root.BuildIndirectOosvarValueLvalueNode(astNode) case asts.NodeType(NodeTypeFullOosvar): return root.BuildFullOosvarLvalueNode(astNode) case asts.NodeType(NodeTypeLocalVariable): return root.BuildLocalVariableLvalueNode(astNode) case asts.NodeType(NodeTypeArrayOrMapIndexAccess): return root.BuildIndexedLvalueNode(astNode) case asts.NodeType(NodeTypeDotOperator): return root.BuildIndexedLvalueNode(astNode) case asts.NodeType(NodeTypeEnvironmentVariable): return root.BuildEnvironmentVariableLvalueNode(astNode) case asts.NodeType("TypedeclLocalVariable"): return root.BuildTypedeclLocalVariableLvalueNode(astNode) } return nil, errors.New( "at CST BuildAssignableNode: unhandled AST node " + string(astNode.Type), ) } type DirectFieldValueLvalueNode struct { lhsFieldName *mlrval.Mlrval } func (root *RootNode) BuildDirectFieldValueLvalueNode( astNode *asts.ASTNode, ) (IAssignable, error) { lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeDirectFieldValue) && astNode.Type != asts.NodeType(NodeTypeBracedFieldValue)) var name string if astNode.Type == asts.NodeType(NodeTypeBracedFieldValue) { name = tokenLitStripBraced(astNode) } else { name = tokenLitStripDollarOrAt(astNode) } lhsFieldName := mlrval.FromString(name) 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 } 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 { // unset of a non-existent path is a no-op _ = state.Inrec.RemoveIndexed( append([]*mlrval.Mlrval{node.lhsFieldName}, indices...), ) } } type IndirectFieldValueLvalueNode struct { lhsFieldNameExpression IEvaluable } func (root *RootNode) BuildIndirectFieldValueLvalueNode( astNode *asts.ASTNode, ) (IAssignable, error) { lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeIndirectFieldValue)) lib.InternalCodingErrorIf(astNode == nil) lib.InternalCodingErrorIf(len(astNode.Children) != 1) child := astNode.Children[0] if child.Type == asts.NodeType(NodeTypeArrayLiteral) && len(child.Children) == 1 { inner := child.Children[0] if inner.Type == asts.NodeType(NodeTypeArrayLiteral) && len(inner.Children) == 1 { // $[[[n]]] → positional field value lvalue indexASTNode := inner.Children[0] syntheticAST := asts.NewASTNode(nil, asts.NodeType(NodeTypePositionalFieldValue), []*asts.ASTNode{indexASTNode}) return root.BuildPositionalFieldValueLvalueNode(syntheticAST) } // $[[n]] → positional field name lvalue indexASTNode := inner syntheticAST := asts.NewASTNode(nil, asts.NodeType(NodeTypePositionalFieldName), []*asts.ASTNode{indexASTNode}) return root.BuildPositionalFieldNameLvalueNode(syntheticAST) } lhsFieldNameExpression, err := root.BuildEvaluableNode(child) 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 } 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 { // unset of a non-existent path is a no-op _ = 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 *asts.ASTNode, ) (IAssignable, error) { lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(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 } return fmt.Errorf( "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( "$[[...]] = ... 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) } // TODO: incorporate error-return into this API for the non-int case } else { // xxx positional // unset of a non-existent path is a no-op _ = 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 *asts.ASTNode, ) (IAssignable, error) { lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(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 } return fmt.Errorf( "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) } // TODO: incorporate error-return into this API for the non-int case } else { // xxx positional // unset of a non-existent path is a no-op _ = state.Inrec.RemoveIndexed( append([]*mlrval.Mlrval{lhsFieldIndex}, indices...), ) } } type FullSrecLvalueNode struct { } func (root *RootNode) BuildFullSrecLvalueNode(astNode *asts.ASTNode) (IAssignable, error) { lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeFullSrec)) lib.InternalCodingErrorIf(astNode == nil) // PGPG FullSrec has empty list as children lib.InternalCodingErrorIf(len(astNode.Children) > 0) 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 { // unset of a non-existent path is a no-op _ = state.Inrec.RemoveIndexed(indices) } } type DirectOosvarValueLvalueNode struct { lhsOosvarName *mlrval.Mlrval } func (root *RootNode) BuildDirectOosvarValueLvalueNode(astNode *asts.ASTNode) (IAssignable, error) { lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeDirectOosvarValue) && astNode.Type != asts.NodeType(NodeTypeBracedOosvarValue)) var name string if astNode.Type == asts.NodeType(NodeTypeBracedOosvarValue) { name = tokenLitStripBraced(astNode) } else { name = tokenLitStripDollarOrAt(astNode) } lhsOosvarName := mlrval.FromString(name) 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 } 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 { // unset of a non-existent path is a no-op _ = state.Oosvars.RemoveIndexed( append([]*mlrval.Mlrval{node.lhsOosvarName}, indices...), ) } } type IndirectOosvarValueLvalueNode struct { lhsOosvarNameExpression IEvaluable } func (root *RootNode) BuildIndirectOosvarValueLvalueNode( astNode *asts.ASTNode, ) (IAssignable, error) { lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(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 } 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 { // unset of a non-existent path is a no-op _ = state.Oosvars.RemoveIndexed( append([]*mlrval.Mlrval{lhsOosvarName}, indices...), ) } } type FullOosvarLvalueNode struct { } func (root *RootNode) BuildFullOosvarLvalueNode(astNode *asts.ASTNode) (IAssignable, error) { lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeFullOosvar)) lib.InternalCodingErrorIf(astNode == nil) lib.InternalCodingErrorIf(len(astNode.Children) > 0) 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 { // unset of a non-existent path is a no-op _ = 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) BuildTypedeclLocalVariableLvalueNode(astNode *asts.ASTNode) (IAssignable, error) { lib.InternalCodingErrorIf(len(astNode.Children) < 2) typeNode := astNode.Children[0] varNode := astNode.Children[1] // PGPG Typedecl produces kw_int, kw_bool, etc. (no Typedecl wrapper node) lib.InternalCodingErrorIf(varNode.Type != asts.NodeType(NodeTypeLocalVariable)) typeName := tokenLit(typeNode) variableName := tokenLit(varNode) return NewLocalVariableLvalueNode( runtime.NewStackVariable(variableName), typeName, true, // defineTypedAtScope ), nil } func (root *RootNode) BuildLocalVariableLvalueNode(astNode *asts.ASTNode) (IAssignable, error) { lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeLocalVariable)) // TODO require type mask in strict mode variableName := tokenLit(astNode) typeName := "any" defineTypedAtScope := false // PGPG: LocalVariable is terminal (children nil or empty). Miller had typed params with Children[0]=Typedecl. if len(astNode.Children) > 0 { // typed, like 'num x = 3' typeNode := astNode.Children[0] lib.InternalCodingErrorIf(typeNode.Type != asts.NodeType(NodeTypeTypedecl)) typeName = tokenLit(typeNode) 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 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 *asts.ASTNode) (IAssignable, error) { lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeArrayOrMapIndexAccess) && astNode.Type != asts.NodeType(NodeTypeDotOperator)) lib.InternalCodingErrorIf(astNode == nil) var baseLvalue IAssignable = nil indexEvaluables := []IEvaluable{} var err error // $ 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 == asts.NodeType(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 == asts.NodeType(NodeTypeDotOperator) { lib.InternalCodingErrorIf(walkerNode == nil) lib.InternalCodingErrorIf(len(walkerNode.Children) != 2) indexEvaluable := root.BuildStringLiteralNode(tokenLit(walkerNode.Children[1])) 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 *asts.ASTNode) (IAssignable, error) { lib.InternalCodingErrorIf(astNode.Type != asts.NodeType(NodeTypeEnvironmentVariable)) lib.InternalCodingErrorIf(astNode == nil) lib.InternalCodingErrorIf(len(astNode.Children) != 1) child := astNode.Children[0] var nameExpression IEvaluable // ENV.FOO: child is non_sigil_name; BuildEvaluableNode doesn't handle bare identifiers if string(child.Type) == "non_sigil_name" { sval := tokenLit(child) nameExpression = root.BuildStringLiteralNode(sval) } else { var err error nameExpression, err = root.BuildEvaluableNode(child) 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"`, name.GetTypeName(), name.String(), ) } sname := name.String() svalue := rvalue.String() if err := os.Setenv(sname, svalue); err != nil { return err } 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("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") }