mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-31 19:51:33 +00:00
emitx iterate
This commit is contained in:
parent
220a4847ac
commit
72bd3f7716
6 changed files with 433 additions and 268 deletions
|
|
@ -33,9 +33,9 @@ func mainUsageLong(o *os.File, argv0 string) {
|
|||
mainUsageHelpOptions(o, argv0)
|
||||
fmt.Fprintf(o, "\n")
|
||||
|
||||
fmt.Fprintf(o, "CUSTOMIZATION VIA .MLRRC:\n");
|
||||
mainUsageMlrrc(o, argv0);
|
||||
fmt.Fprintf(o, "\n");
|
||||
fmt.Fprintf(o, "CUSTOMIZATION VIA .MLRRC:\n")
|
||||
mainUsageMlrrc(o, argv0)
|
||||
fmt.Fprintf(o, "\n")
|
||||
|
||||
fmt.Fprintf(o, "VERBS:\n")
|
||||
listAllVerbs(o, " ")
|
||||
|
|
@ -136,37 +136,37 @@ func mainUsageHelpOptions(o *os.File, argv0 string) {
|
|||
}
|
||||
|
||||
func mainUsageMlrrc(o *os.File, argv0 string) {
|
||||
fmt.Fprintf(o, "You can set up personal defaults via a $HOME/.mlrrc and/or ./.mlrrc.\n");
|
||||
fmt.Fprintf(o, "For example, if you usually process CSV, then you can put \"--csv\" in your .mlrrc file\n");
|
||||
fmt.Fprintf(o, "and that will be the default input/output format unless otherwise specified on the command line.\n");
|
||||
fmt.Fprintf(o, "\n");
|
||||
fmt.Fprintf(o, "The .mlrrc file format is one \"--flag\" or \"--option value\" per line, with the leading \"--\" optional.\n");
|
||||
fmt.Fprintf(o, "Hash-style comments and blank lines are ignored.\n");
|
||||
fmt.Fprintf(o, "\n");
|
||||
fmt.Fprintf(o, "Sample .mlrrc:\n");
|
||||
fmt.Fprintf(o, "# Input and output formats are CSV by default (unless otherwise specified\n");
|
||||
fmt.Fprintf(o, "# on the mlr command line):\n");
|
||||
fmt.Fprintf(o, "csv\n");
|
||||
fmt.Fprintf(o, "# These are no-ops for CSV, but when I do use JSON output, I want these\n");
|
||||
fmt.Fprintf(o, "# pretty-printing options to be used:\n");
|
||||
fmt.Fprintf(o, "jvstack\n");
|
||||
fmt.Fprintf(o, "jlistwrap\n");
|
||||
fmt.Fprintf(o, "\n");
|
||||
fmt.Fprintf(o, "How to specify location of .mlrrc:\n");
|
||||
fmt.Fprintf(o, "* If $MLRRC is set:\n");
|
||||
fmt.Fprintf(o, " o If its value is \"__none__\" then no .mlrrc files are processed.\n");
|
||||
fmt.Fprintf(o, " o Otherwise, its value (as a filename) is loaded and processed. If there are syntax\n");
|
||||
fmt.Fprintf(o, " errors, they abort mlr with a usage message (as if you had mistyped something on the\n");
|
||||
fmt.Fprintf(o, " command line). If the file can't be loaded at all, though, it is silently skipped.\n");
|
||||
fmt.Fprintf(o, " o Any .mlrrc in your home directory or current directory is ignored whenever $MLRRC is\n");
|
||||
fmt.Fprintf(o, " set in the environment.\n");
|
||||
fmt.Fprintf(o, "* Otherwise:\n");
|
||||
fmt.Fprintf(o, " o If $HOME/.mlrrc exists, it's then processed as above.\n");
|
||||
fmt.Fprintf(o, " o If ./.mlrrc exists, it's then also processed as above.\n");
|
||||
fmt.Fprintf(o, " (I.e. current-directory .mlrrc defaults are stacked over home-directory .mlrrc defaults.)\n");
|
||||
fmt.Fprintf(o, "\n");
|
||||
fmt.Fprintf(o, "See also:\n");
|
||||
fmt.Fprintf(o, "https://miller.readthedocs.io/en/latest/customization.html\n");
|
||||
fmt.Fprintf(o, "You can set up personal defaults via a $HOME/.mlrrc and/or ./.mlrrc.\n")
|
||||
fmt.Fprintf(o, "For example, if you usually process CSV, then you can put \"--csv\" in your .mlrrc file\n")
|
||||
fmt.Fprintf(o, "and that will be the default input/output format unless otherwise specified on the command line.\n")
|
||||
fmt.Fprintf(o, "\n")
|
||||
fmt.Fprintf(o, "The .mlrrc file format is one \"--flag\" or \"--option value\" per line, with the leading \"--\" optional.\n")
|
||||
fmt.Fprintf(o, "Hash-style comments and blank lines are ignored.\n")
|
||||
fmt.Fprintf(o, "\n")
|
||||
fmt.Fprintf(o, "Sample .mlrrc:\n")
|
||||
fmt.Fprintf(o, "# Input and output formats are CSV by default (unless otherwise specified\n")
|
||||
fmt.Fprintf(o, "# on the mlr command line):\n")
|
||||
fmt.Fprintf(o, "csv\n")
|
||||
fmt.Fprintf(o, "# These are no-ops for CSV, but when I do use JSON output, I want these\n")
|
||||
fmt.Fprintf(o, "# pretty-printing options to be used:\n")
|
||||
fmt.Fprintf(o, "jvstack\n")
|
||||
fmt.Fprintf(o, "jlistwrap\n")
|
||||
fmt.Fprintf(o, "\n")
|
||||
fmt.Fprintf(o, "How to specify location of .mlrrc:\n")
|
||||
fmt.Fprintf(o, "* If $MLRRC is set:\n")
|
||||
fmt.Fprintf(o, " o If its value is \"__none__\" then no .mlrrc files are processed.\n")
|
||||
fmt.Fprintf(o, " o Otherwise, its value (as a filename) is loaded and processed. If there are syntax\n")
|
||||
fmt.Fprintf(o, " errors, they abort mlr with a usage message (as if you had mistyped something on the\n")
|
||||
fmt.Fprintf(o, " command line). If the file can't be loaded at all, though, it is silently skipped.\n")
|
||||
fmt.Fprintf(o, " o Any .mlrrc in your home directory or current directory is ignored whenever $MLRRC is\n")
|
||||
fmt.Fprintf(o, " set in the environment.\n")
|
||||
fmt.Fprintf(o, "* Otherwise:\n")
|
||||
fmt.Fprintf(o, " o If $HOME/.mlrrc exists, it's then processed as above.\n")
|
||||
fmt.Fprintf(o, " o If ./.mlrrc exists, it's then also processed as above.\n")
|
||||
fmt.Fprintf(o, " (I.e. current-directory .mlrrc defaults are stacked over home-directory .mlrrc defaults.)\n")
|
||||
fmt.Fprintf(o, "\n")
|
||||
fmt.Fprintf(o, "See also:\n")
|
||||
fmt.Fprintf(o, "https://miller.readthedocs.io/en/latest/customization.html\n")
|
||||
}
|
||||
|
||||
//func mainUsageFunctions(o *os.File, argv0 string, char* leader) {
|
||||
|
|
|
|||
|
|
@ -1,233 +0,0 @@
|
|||
// ================================================================
|
||||
// This handles emit statements.
|
||||
// ================================================================
|
||||
|
||||
package cst
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"miller/dsl"
|
||||
"miller/lib"
|
||||
"miller/types"
|
||||
)
|
||||
|
||||
// ================================================================
|
||||
type EmitStatementNode struct {
|
||||
emitEvaluable IEvaluable
|
||||
|
||||
// emitEvaluables []IEvaluable
|
||||
// keyEvaluables []IEvaluable
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
func (this *RootNode) BuildEmitStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
|
||||
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmitStatement)
|
||||
nchild := len(astNode.Children)
|
||||
lib.InternalCodingErrorIf(nchild != 1 && nchild != 2)
|
||||
|
||||
emitEvaluable, err := this.BuildEvaluableNode(astNode.Children[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &EmitStatementNode{
|
||||
emitEvaluable: emitEvaluable,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *EmitStatementNode) Execute(state *State) (*BlockExitPayload, error) {
|
||||
emitResult := this.emitEvaluable.Evaluate(state)
|
||||
|
||||
if emitResult.IsAbsent() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if emitResult.IsMap() {
|
||||
state.OutputChannel <- types.NewRecordAndContext(
|
||||
emitResult.Copy().GetMap(),
|
||||
state.Context, // xxx clone ?
|
||||
)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// cases:
|
||||
// * 'emit (@count, @sum)' -- convert to mlrmap "count=1,sum=2"
|
||||
// * 'emit (@count, @sum), "a"' -- convert to mlrmap "a=foo,count=2,sum=3.4'
|
||||
// ?? maybe alter from mlr-c syntax to require a map here -- ?
|
||||
// * 'emit {"a": @a, "b": @b}' -- ?
|
||||
// * 'for k in @u { emit {"a": k, "u": @u[k], "v": @v[k] }' -- ?
|
||||
|
||||
// possibles:
|
||||
// * maps -- as-is
|
||||
// o what about nameless bases such as @* and $*?
|
||||
// * srecs -- key-value pairs into a new map
|
||||
// * oosvars -- key-value pairs into a new map
|
||||
// * localvars -- key-value pairs into a new map
|
||||
// * otherwise error
|
||||
|
||||
// * Given @count = 2 and @sum = 3.4:
|
||||
// o 'emit (@sum, @count)' => [{ "sum": 2, "count": 3.4 }]
|
||||
|
||||
// * Given @count = {"pan": 2, "eks": 3} and @sum = {"pan" 3.4, "eks": 5.6 }:
|
||||
// o 'emit (@sum, @count)' => [{
|
||||
// "count": {"pan": 2, "eks": 3},
|
||||
// "sum": {"pan" 3.4, "eks": 5.6 }
|
||||
// }]
|
||||
|
||||
// * Given @count = {"pan": 2, "eks": 3} and @sum = {"pan" 3.4, "eks": 5.6 }:
|
||||
// o 'emit (@sum, @count), $a' =>
|
||||
// [
|
||||
// {
|
||||
// "a": "pan",
|
||||
// "count": 2,
|
||||
// "sum": 3.4
|
||||
// },
|
||||
// {
|
||||
// "a": "eks",
|
||||
// "count": 3,
|
||||
// "sum": 5.6
|
||||
// }
|
||||
// ]
|
||||
|
||||
// ================================================================
|
||||
type EmitPStatementNode struct {
|
||||
emitpEvaluable IEvaluable
|
||||
// xxx to do:
|
||||
// * required array of evaluables
|
||||
// * optional array of indexing keys
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
func (this *RootNode) BuildEmitPStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
|
||||
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmitPStatement)
|
||||
lib.InternalCodingErrorIf(len(astNode.Children) < 1)
|
||||
|
||||
emitpEvaluable, err := this.BuildEvaluableNode(astNode.Children[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &EmitPStatementNode{
|
||||
emitpEvaluable: emitpEvaluable,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *EmitPStatementNode) Execute(state *State) (*BlockExitPayload, error) {
|
||||
emitpResult := this.emitpEvaluable.Evaluate(state)
|
||||
|
||||
if emitpResult.IsAbsent() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if emitpResult.IsMap() {
|
||||
state.OutputChannel <- types.NewRecordAndContext(
|
||||
emitpResult.Copy().GetMap(),
|
||||
state.Context, // xxx clone ?
|
||||
)
|
||||
}
|
||||
|
||||
// xxx WIP
|
||||
// xxx need to reshape rvalue mlrvals -> mlrmaps; publish w/ contexts; method for that
|
||||
|
||||
// outputChannel <- types.NewRecordAndContext(
|
||||
// mlrmap goes here,
|
||||
// &context,
|
||||
// )
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Examples:
|
||||
// emitf @a
|
||||
// emitf @a, @b
|
||||
//
|
||||
// Each argument must be a non-indexed oosvar/localvar/fieldname, so we can use
|
||||
// their names as keys in the emitted record. These restrictions are enforced
|
||||
// in the CST logic, to keep this parser/AST logic simpler.
|
||||
|
||||
type EmitFStatementNode struct {
|
||||
emitfNames []string
|
||||
emitfEvaluables []IEvaluable
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// $ mlr -n put -v 'emitf a,$b,@c'
|
||||
// DSL EXPRESSION:
|
||||
// emitf a,$b,@c
|
||||
// RAW AST:
|
||||
// * statement block
|
||||
// * dump statement "emitf"
|
||||
// * local variable "a"
|
||||
// * direct field value "b"
|
||||
// * direct oosvar value "c"
|
||||
|
||||
func (this *RootNode) BuildEmitFStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
|
||||
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmitFStatement)
|
||||
n := len(astNode.Children)
|
||||
lib.InternalCodingErrorIf(n < 1)
|
||||
|
||||
emitfNames := make([]string, n)
|
||||
emitfEvaluables := make([]IEvaluable, n)
|
||||
for i, childNode := range astNode.Children {
|
||||
emitfName, err := getNameFromNamedNode(childNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
emitfEvaluable, err := this.BuildEvaluableNode(childNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
emitfNames[i] = emitfName
|
||||
emitfEvaluables[i] = emitfEvaluable
|
||||
}
|
||||
return &EmitFStatementNode{
|
||||
emitfNames: emitfNames,
|
||||
emitfEvaluables: emitfEvaluables,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *EmitFStatementNode) Execute(state *State) (*BlockExitPayload, error) {
|
||||
newrec := types.NewMlrmapAsRecord()
|
||||
for i, emitfEvaluable := range this.emitfEvaluables {
|
||||
emitfName := this.emitfNames[i]
|
||||
emitfValue := emitfEvaluable.Evaluate(state)
|
||||
|
||||
if !emitfValue.IsAbsent() {
|
||||
newrec.PutCopy(&emitfName, &emitfValue)
|
||||
}
|
||||
}
|
||||
state.OutputChannel <- types.NewRecordAndContext(
|
||||
newrec,
|
||||
state.Context.Copy(),
|
||||
)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// HELPER FUNCTIONS
|
||||
|
||||
// For emitf: gets the name of a non-indexed oosvar, localvar, or field name;
|
||||
// otherwise, returns error.
|
||||
//
|
||||
// TODO: support indirects like 'emitf @[x."_sum"]'
|
||||
|
||||
func getNameFromNamedNode(astNode *dsl.ASTNode) (string, error) {
|
||||
if astNode.Type == dsl.NodeTypeDirectOosvarValue {
|
||||
return string(astNode.Token.Lit), nil
|
||||
} else if astNode.Type == dsl.NodeTypeLocalVariable {
|
||||
return string(astNode.Token.Lit), nil
|
||||
} else if astNode.Type == dsl.NodeTypeDirectFieldValue {
|
||||
return string(astNode.Token.Lit), nil
|
||||
}
|
||||
return "", errors.New(
|
||||
fmt.Sprintf(
|
||||
"%s: can't get name of node type \"%s\" for emitf.",
|
||||
os.Args[0], string(astNode.Type),
|
||||
),
|
||||
)
|
||||
}
|
||||
279
go/src/miller/dsl/cst/emit_emitp.go
Normal file
279
go/src/miller/dsl/cst/emit_emitp.go
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
// ================================================================
|
||||
// This handles emit and emitp statements. These produce new records (in
|
||||
// addition to $*) into the output record stream.
|
||||
//
|
||||
// Some complications here are due to legacy. Emit statements existed in the
|
||||
// Miller DSL before there were for-loops. As a result, some of the
|
||||
// side-by-side emit syntaxes were invented (and supported) to allow things
|
||||
// that might have been more easily done with simpler emit syntax.
|
||||
// Nonetheless, those syntaxes are now supported and we need to support them.
|
||||
//
|
||||
// Examples for emit and emitp:
|
||||
// emit @a
|
||||
// emit (@a, @b)
|
||||
// emit @a, "x", "y"
|
||||
// emit (@a, @b), "x", "y"
|
||||
//
|
||||
// The first argument (single or in parentheses) must be non-indexed
|
||||
// oosvars/localvars/fieldnames, so we can use their names as keys in the
|
||||
// emitted record, or they must be maps. So the first complexity in this code
|
||||
// is, do we have a named variable or a map.
|
||||
//
|
||||
// The second complexity here is whether we have 'emit @a' or 'emit (@a, @b)'
|
||||
// -- the latter being the "lashed" variant. Here, the keys of the first
|
||||
// argument are used to drive indexing of the remaining arguments.
|
||||
//
|
||||
// The third complexlity here is whether we have the '"x", "y"' after the
|
||||
// emittables. These control how nested maps are used to generate multiple
|
||||
// records (via implicit looping).
|
||||
// ================================================================
|
||||
|
||||
package cst
|
||||
|
||||
import (
|
||||
"miller/dsl"
|
||||
"miller/lib"
|
||||
"miller/types"
|
||||
)
|
||||
|
||||
// ================================================================
|
||||
// Shared by emit and emitp
|
||||
|
||||
type EmitXStatementNode struct {
|
||||
// These are "_" for maps like in 'emit {...}'; "x" for named variables like
|
||||
// in 'emit @x'.
|
||||
names []string
|
||||
|
||||
// Maps or named variables: the @a, @b parts.
|
||||
emitEvaluables []IEvaluable
|
||||
|
||||
// The "x","y" parts.
|
||||
indexEvaluables []IEvaluable
|
||||
|
||||
// For shared code use between emit and emitp.
|
||||
isEmitP bool
|
||||
|
||||
executorFunc Executor
|
||||
}
|
||||
|
||||
func (this *RootNode) BuildEmitStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
|
||||
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmitStatement)
|
||||
return this.buildEmitXStatementNode(astNode, false)
|
||||
}
|
||||
func (this *RootNode) BuildEmitPStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
|
||||
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmitPStatement)
|
||||
return this.buildEmitXStatementNode(astNode, true)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// EMIT AND EMITP
|
||||
//
|
||||
// Examples:
|
||||
// emit @a
|
||||
// emit (@a, @b)
|
||||
// emit @a, "x", "y"
|
||||
// emit (@a, @b), "x", "y"
|
||||
// First argument (single or in parentheses) must be non-indexed
|
||||
// oosvar/localvar/fieldname/map, so we can use their names as keys in the
|
||||
// emitted record.
|
||||
|
||||
func (this *RootNode) buildEmitXStatementNode(
|
||||
astNode *dsl.ASTNode,
|
||||
isEmitP bool,
|
||||
) (IExecutable, error) {
|
||||
nchild := len(astNode.Children)
|
||||
lib.InternalCodingErrorIf(nchild != 1 && nchild != 2)
|
||||
|
||||
var names []string = nil
|
||||
var emitEvaluables []IEvaluable = nil
|
||||
var indexEvaluables []IEvaluable = nil
|
||||
|
||||
emittablesNode := astNode.Children[0]
|
||||
if emittablesNode.Type == dsl.NodeTypeEmittableList {
|
||||
// Lashed: emit (@a, @b), "x"
|
||||
numEmittables := len(emittablesNode.Children)
|
||||
names = make([]string, numEmittables)
|
||||
emitEvaluables = make([]IEvaluable, numEmittables)
|
||||
for i, emittableNode := range emittablesNode.Children {
|
||||
name, emitEvaluable, err := this.buildEmittableNode(emittableNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names[i] = name
|
||||
emitEvaluables[i] = emitEvaluable
|
||||
}
|
||||
|
||||
} else {
|
||||
// Non-lashed: emit @a, "x"
|
||||
names = make([]string, 1)
|
||||
emitEvaluables = make([]IEvaluable, 1)
|
||||
name, emitEvaluable, err := this.buildEmittableNode(emittablesNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names[0] = name
|
||||
emitEvaluables[0] = emitEvaluable
|
||||
}
|
||||
|
||||
if nchild == 2 { // There are "x","y" present
|
||||
keysNode := astNode.Children[1]
|
||||
lib.InternalCodingErrorIf(keysNode.Type != dsl.NodeTypeEmitKeys)
|
||||
numKeys := len(keysNode.Children)
|
||||
indexEvaluables = make([]IEvaluable, numKeys)
|
||||
for i, keyNode := range keysNode.Children {
|
||||
indexEvaluable, err := this.BuildEvaluableNode(keyNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
indexEvaluables[i] = indexEvaluable
|
||||
}
|
||||
}
|
||||
|
||||
emitxStatementNode := &EmitXStatementNode{
|
||||
names: names,
|
||||
emitEvaluables: emitEvaluables,
|
||||
indexEvaluables: indexEvaluables,
|
||||
isEmitP: isEmitP,
|
||||
}
|
||||
|
||||
if len(emitEvaluables) == 1 {
|
||||
if nchild == 1 {
|
||||
emitxStatementNode.executorFunc = emitxStatementNode.executeNonLashedNonIndexed
|
||||
} else {
|
||||
emitxStatementNode.executorFunc = emitxStatementNode.executeNonLashedIndexed
|
||||
}
|
||||
} else {
|
||||
if nchild == 1 {
|
||||
emitxStatementNode.executorFunc = emitxStatementNode.executeLashedNonIndexed
|
||||
} else {
|
||||
emitxStatementNode.executorFunc = emitxStatementNode.executeLashedIndexed
|
||||
}
|
||||
}
|
||||
|
||||
return emitxStatementNode, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// This is a helper method for deciding whether an emittable node is a named
|
||||
// variable or a map.
|
||||
|
||||
func (this *RootNode) buildEmittableNode(
|
||||
astNode *dsl.ASTNode,
|
||||
) (name string, emitEvaluable IEvaluable, err error) {
|
||||
name = "_"
|
||||
emitEvaluable = nil
|
||||
err = nil
|
||||
|
||||
if astNode.Type == dsl.NodeTypeDirectOosvarValue {
|
||||
name = string(astNode.Token.Lit)
|
||||
} else if astNode.Type == dsl.NodeTypeLocalVariable {
|
||||
name = string(astNode.Token.Lit)
|
||||
} else if astNode.Type == dsl.NodeTypeDirectFieldValue {
|
||||
name = string(astNode.Token.Lit)
|
||||
}
|
||||
|
||||
emitEvaluable, err = this.BuildEvaluableNode(astNode)
|
||||
|
||||
return name, emitEvaluable, err
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
func (this *EmitXStatementNode) Execute(state *State) (*BlockExitPayload, error) {
|
||||
return this.executorFunc(state)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Just one mqp or named variable being emitted.
|
||||
func (this *EmitXStatementNode) executeNonLashedNonIndexed(
|
||||
state *State,
|
||||
) (*BlockExitPayload, error) {
|
||||
emittable := this.emitEvaluables[0].Evaluate(state)
|
||||
if emittable.IsAbsent() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if emittable.IsMap() {
|
||||
// Emittable is map
|
||||
var newrec *types.Mlrmap = nil
|
||||
if this.isEmitP {
|
||||
newrec = types.NewMlrmapAsRecord()
|
||||
newrec.PutCopy(&this.names[0], &emittable)
|
||||
} else {
|
||||
newrec = emittable.Copy().GetMap()
|
||||
}
|
||||
state.OutputChannel <- types.NewRecordAndContext(
|
||||
newrec,
|
||||
state.Context.Copy(),
|
||||
)
|
||||
|
||||
} else {
|
||||
// Emittable is named variable
|
||||
newrec := types.NewMlrmapAsRecord()
|
||||
newrec.PutCopy(&this.names[0], &emittable)
|
||||
state.OutputChannel <- types.NewRecordAndContext(
|
||||
newrec,
|
||||
state.Context.Copy(),
|
||||
)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
func (this *EmitXStatementNode) executeNonLashedIndexed(
|
||||
state *State,
|
||||
) (*BlockExitPayload, error) {
|
||||
|
||||
primaryEmittable := this.emitEvaluables[0].Evaluate(state)
|
||||
if primaryEmittable.IsAbsent() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
func (this *EmitXStatementNode) executeLashedNonIndexed(
|
||||
state *State,
|
||||
) (*BlockExitPayload, error) {
|
||||
|
||||
newrec := types.NewMlrmapAsRecord()
|
||||
|
||||
for i, emitEvaluable := range this.emitEvaluables {
|
||||
emittable := emitEvaluable.Evaluate(state)
|
||||
if emittable.IsAbsent() {
|
||||
continue
|
||||
}
|
||||
|
||||
if emittable.IsMap() {
|
||||
// Emittable is map
|
||||
for pe := emittable.GetMap().Head; pe != nil; pe = pe.Next {
|
||||
newrec.PutCopy(pe.Key, pe.Value)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Emittable is named variable
|
||||
newrec.PutCopy(&this.names[i], &emittable)
|
||||
}
|
||||
}
|
||||
|
||||
state.OutputChannel <- types.NewRecordAndContext(
|
||||
newrec,
|
||||
state.Context.Copy(),
|
||||
)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
func (this *EmitXStatementNode) executeLashedIndexed(
|
||||
state *State,
|
||||
) (*BlockExitPayload, error) {
|
||||
|
||||
primaryEmittable := this.emitEvaluables[0].Evaluate(state)
|
||||
if primaryEmittable.IsAbsent() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
105
go/src/miller/dsl/cst/emitf.go
Normal file
105
go/src/miller/dsl/cst/emitf.go
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
// ================================================================
|
||||
// This handles emitf statements. This produces new records (in addition to $*)
|
||||
// into the output record stream.
|
||||
// ================================================================
|
||||
|
||||
package cst
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"miller/dsl"
|
||||
"miller/lib"
|
||||
"miller/types"
|
||||
)
|
||||
|
||||
// ================================================================
|
||||
// Examples:
|
||||
// emitf @a
|
||||
// emitf @a, @b
|
||||
//
|
||||
// Each argument must be a non-indexed oosvar/localvar/fieldname, so we can use
|
||||
// their names as keys in the emitted record. These restrictions are enforced
|
||||
// in the CST logic, to keep this parser/AST logic simpler.
|
||||
|
||||
type EmitFStatementNode struct {
|
||||
emitfNames []string
|
||||
emitfEvaluables []IEvaluable
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// $ mlr -n put -v 'emitf a,$b,@c'
|
||||
// DSL EXPRESSION:
|
||||
// emitf a,$b,@c
|
||||
// RAW AST:
|
||||
// * statement block
|
||||
// * dump statement "emitf"
|
||||
// * local variable "a"
|
||||
// * direct field value "b"
|
||||
// * direct oosvar value "c"
|
||||
|
||||
func (this *RootNode) BuildEmitFStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
|
||||
lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmitFStatement)
|
||||
n := len(astNode.Children)
|
||||
lib.InternalCodingErrorIf(n < 1)
|
||||
|
||||
emitfNames := make([]string, n)
|
||||
emitfEvaluables := make([]IEvaluable, n)
|
||||
for i, childNode := range astNode.Children {
|
||||
emitfName, err := getNameFromNamedNode(childNode, "emitf")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
emitfEvaluable, err := this.BuildEvaluableNode(childNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
emitfNames[i] = emitfName
|
||||
emitfEvaluables[i] = emitfEvaluable
|
||||
}
|
||||
return &EmitFStatementNode{
|
||||
emitfNames: emitfNames,
|
||||
emitfEvaluables: emitfEvaluables,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *EmitFStatementNode) Execute(state *State) (*BlockExitPayload, error) {
|
||||
newrec := types.NewMlrmapAsRecord()
|
||||
for i, emitfEvaluable := range this.emitfEvaluables {
|
||||
emitfName := this.emitfNames[i]
|
||||
emitfValue := emitfEvaluable.Evaluate(state)
|
||||
|
||||
if !emitfValue.IsAbsent() {
|
||||
newrec.PutCopy(&emitfName, &emitfValue)
|
||||
}
|
||||
}
|
||||
state.OutputChannel <- types.NewRecordAndContext(
|
||||
newrec,
|
||||
state.Context.Copy(),
|
||||
)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Gets the name of a non-indexed oosvar, localvar, or field name; otherwise,
|
||||
// returns error.
|
||||
//
|
||||
// TODO: support indirects like 'emitf @[x."_sum"]'
|
||||
|
||||
func getNameFromNamedNode(astNode *dsl.ASTNode, description string) (string, error) {
|
||||
if astNode.Type == dsl.NodeTypeDirectOosvarValue {
|
||||
return string(astNode.Token.Lit), nil
|
||||
} else if astNode.Type == dsl.NodeTypeLocalVariable {
|
||||
return string(astNode.Token.Lit), nil
|
||||
} else if astNode.Type == dsl.NodeTypeDirectFieldValue {
|
||||
return string(astNode.Token.Lit), nil
|
||||
}
|
||||
return "", errors.New(
|
||||
fmt.Sprintf(
|
||||
"%s: can't get name of node type \"%s\" for %s.",
|
||||
os.Args[0], string(astNode.Type), description,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -46,6 +46,8 @@ type IExecutable interface {
|
|||
Execute(state *State) (*BlockExitPayload, error)
|
||||
}
|
||||
|
||||
type Executor func(state *State) (*BlockExitPayload, error)
|
||||
|
||||
// ================================================================
|
||||
// This is for any left-hand side (LHS or Lvalue) of an assignment statement.
|
||||
type IAssignable interface {
|
||||
|
|
|
|||
14
go/todo.txt
14
go/todo.txt
|
|
@ -10,7 +10,20 @@ TOP OF LIST:
|
|||
* put -d/-D -> put -h
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
* emitp/emitf
|
||||
* to support (from C impl):
|
||||
md_emittable ::= MD_TOKEN_ALL
|
||||
md_emittable ::= MD_TOKEN_FULL_OOSVAR
|
||||
md_emittable ::= md_oosvar_keylist
|
||||
md_emittable ::= md_nonindexed_local_variable
|
||||
md_emittable ::= md_indexed_local_variable
|
||||
md_emittable ::= MD_TOKEN_FULL_SREC
|
||||
md_emittable ::= md_map_literal
|
||||
md_emittable ::= md_fcn_or_subr_call
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
* stats1 verb
|
||||
! mlrrc
|
||||
|
||||
|
|
@ -437,4 +450,3 @@ i https://en.wikipedia.org/wiki/Delimiter#Delimiter_collision
|
|||
> joinBucketKeeper & joinBucket need to be privatized
|
||||
> rewrite join-bucket-keeper.go entirely
|
||||
> also needs UT per se (not just regression)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue