From 72bd3f7716667e82d79cd1644bf9d1666cc5bdad Mon Sep 17 00:00:00 2001 From: John Kerl Date: Sun, 27 Dec 2020 15:14:10 -0500 Subject: [PATCH] emitx iterate --- go/src/miller/cli/mlrcli_usage.go | 68 +++---- go/src/miller/dsl/cst/emit.go | 233 ----------------------- go/src/miller/dsl/cst/emit_emitp.go | 279 ++++++++++++++++++++++++++++ go/src/miller/dsl/cst/emitf.go | 105 +++++++++++ go/src/miller/dsl/cst/types.go | 2 + go/todo.txt | 14 +- 6 files changed, 433 insertions(+), 268 deletions(-) delete mode 100644 go/src/miller/dsl/cst/emit.go create mode 100644 go/src/miller/dsl/cst/emit_emitp.go create mode 100644 go/src/miller/dsl/cst/emitf.go diff --git a/go/src/miller/cli/mlrcli_usage.go b/go/src/miller/cli/mlrcli_usage.go index 8300eeb29..06ab8d5aa 100644 --- a/go/src/miller/cli/mlrcli_usage.go +++ b/go/src/miller/cli/mlrcli_usage.go @@ -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) { diff --git a/go/src/miller/dsl/cst/emit.go b/go/src/miller/dsl/cst/emit.go deleted file mode 100644 index d1ddb8a22..000000000 --- a/go/src/miller/dsl/cst/emit.go +++ /dev/null @@ -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), - ), - ) -} diff --git a/go/src/miller/dsl/cst/emit_emitp.go b/go/src/miller/dsl/cst/emit_emitp.go new file mode 100644 index 000000000..c93307beb --- /dev/null +++ b/go/src/miller/dsl/cst/emit_emitp.go @@ -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 +} diff --git a/go/src/miller/dsl/cst/emitf.go b/go/src/miller/dsl/cst/emitf.go new file mode 100644 index 000000000..478dc0748 --- /dev/null +++ b/go/src/miller/dsl/cst/emitf.go @@ -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, + ), + ) +} diff --git a/go/src/miller/dsl/cst/types.go b/go/src/miller/dsl/cst/types.go index 0cc739bf0..17c590875 100644 --- a/go/src/miller/dsl/cst/types.go +++ b/go/src/miller/dsl/cst/types.go @@ -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 { diff --git a/go/todo.txt b/go/todo.txt index b68034cd2..37ee35943 100644 --- a/go/todo.txt +++ b/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) -