mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-17 16:38:54 +00:00
* refine the plan
* Fix all staticcheck lint findings (uncapped)
golangci-lint's default max-same-issues=3 was hiding most of the backlog:
the true pre-fix count was 69 staticcheck findings, not 34. This fixes all
of them, driving staticcheck to zero:
- ST1023/QF1011 (37): omit explicit types inferred from the RHS
- S1009/S1031 (15): drop redundant nil checks before len()/range
- SA9003 (9): remove comment-only empty branches, keeping the comments
- QF1007 (3): merge conditional assignment into declaration
- QF1006 (3): lift break conditions into loop conditions
- QF1001 (3): apply De Morgan's law / name the negated predicate
Also updates plans/lintfixes.md with the cap discovery and the corrected
errcheck picture (1202 uncapped, ~949 of them fmt.Fprint*).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Drive errcheck to zero: config for bulk categories, propagate real errors
Adds .golangci.yml with errcheck exclude-functions for fmt.Fprint* (usage
printers), (*bufio.Writer).Write/WriteString (sticky errors, surfaced at the
now-checked final Flush), and (*strings.Builder).WriteString; pins
max-issues-per-linter/max-same-issues to 0 so CI reports true counts.
Real error paths now propagate instead of being dropped:
- Finalize{Reader,Writer}Options in join/put/filter/split/tee and the
repl/script entry points: 'mlr join -i badformat' now errors instead of
silently using wrong separators
- final output-stream Flush in pkg/stream: write failure no longer exits 0
- DSL emit/print/dump redirect writes, matching their sibling branches
- CSV writer WriteCSVRecordMaybeColorized, close-time Flush in file output
handlers, ENV[...] Setenv, REPL record-write and redirect-close errors
- termcvt write-side Close before rename (had "TODO: check return status")
The rest are deliberate ignores, marked with _ = and a comment where the
reason isn't obvious: unset-of-missing-path no-ops, read-side closes,
mid-stream FlushOnEveryRecord, init-time strftime registrations, in-memory
usage-capture pipes, and regtest-harness env/temp-file teardown.
golangci-lint now reports 0 issues on ./cmd/mlr ./pkg/... with all caps off.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1113 lines
32 KiB
Go
1113 lines
32 KiB
Go
// 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")
|
|
}
|