1// ================================================================
2// This handles emit and emitp statements. These produce new records (in
3// addition to $*) into the output record stream.
4//
5// Some complications here are due to legacy. Emit statements existed in the
6// Miller DSL before there were for-loops. As a result, some of the
7// side-by-side emit syntaxes were invented (and supported) to allow things
8// that might have been more easily done with simpler emit syntax.
9// Nonetheless, those syntaxes are now supported and we need to support them.
10//
11// Examples for emit and emitp:
12//   emit @a
13//   emit (@a, @b)
14//   emit @a, "x", "y"
15//   emit (@a, @b), "x", "y"
16//
17// The first argument (single or in parentheses) must be non-indexed
18// oosvars/localvars/fieldnames, so we can use their names as keys in the
19// emitted record, or they must be maps. So the first complexity in this code
20// is, do we have a named variable or a map.
21//
22// The second complexity here is whether we have 'emit @a' or 'emit (@a, @b)'
23// -- the latter being the "lashed" variant. Here, the keys of the first
24// argument are used to drive indexing of the remaining arguments.
25//
26// The third complexlity here is whether we have the '"x", "y"' after the
27// emittables. These control how nested maps are used to generate multiple
28// records (via implicit looping).
29// ================================================================
30
31package cst
32
33import (
34	"errors"
35	"fmt"
36
37	"miller/src/dsl"
38	"miller/src/lib"
39	"miller/src/output"
40	"miller/src/runtime"
41	"miller/src/types"
42)
43
44// ================================================================
45// Shared by emit and emitp
46
47type tEmitToRedirectFunc func(
48	newrec *types.Mlrmap,
49	state *runtime.State,
50) error
51
52type EmitXStatementNode struct {
53	// These are "_" for maps like in 'emit {...}'; "x" for named variables like
54	// in 'emit @x'.
55	names []string
56
57	// Maps or named variables: the @a, @b parts.
58	emitEvaluables []IEvaluable
59
60	// The "x","y" parts.
61	indexEvaluables []IEvaluable
62
63	// Appropriate function to evaluate statements, depending on indexed or not.
64	executorFunc Executor
65
66	// Appropriate function to send record(s) to stdout, stderr, write-to-file,
67	// append-to-file, pipe-to-command, or insert into the record stream.
68	emitToRedirectFunc tEmitToRedirectFunc
69	// For file/pipe targets: 'emit > $a . ".dat", @x' -- the
70	// redirectorTargetEvaluable is the evaluable for '$a . ".dat"'.
71	redirectorTargetEvaluable IEvaluable
72	// For file/pipe targets: keeps track of file handles for various values of
73	// the redirectorTargetEvaluable expression.
74	outputHandlerManager output.OutputHandlerManager
75
76	// For code-reuse between executors.
77	isEmitP bool
78}
79
80func (this *RootNode) BuildEmitStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
81	lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmitStatement)
82	return this.buildEmitXStatementNode(astNode, false)
83}
84func (this *RootNode) BuildEmitPStatementNode(astNode *dsl.ASTNode) (IExecutable, error) {
85	lib.InternalCodingErrorIf(astNode.Type != dsl.NodeTypeEmitPStatement)
86	return this.buildEmitXStatementNode(astNode, true)
87}
88
89// ----------------------------------------------------------------
90// EMIT AND EMITP
91//
92// Examples:
93//   emit @a
94//   emit (@a, @b)
95//   emit @a, "x", "y"
96//   emit (@a, @b), "x", "y"
97// First argument (single or in parentheses) must be non-indexed
98// oosvar/localvar/fieldname/map, so we can use their names as keys in the
99// emitted record.
100
101func (this *RootNode) buildEmitXStatementNode(
102	astNode *dsl.ASTNode,
103	isEmitP bool,
104) (IExecutable, error) {
105	lib.InternalCodingErrorIf(len(astNode.Children) != 3)
106	emittablesNode := astNode.Children[0]
107	keysNode := astNode.Children[1]
108	redirectorNode := astNode.Children[2]
109
110	var names []string = nil
111	var emitEvaluables []IEvaluable = nil
112	var indexEvaluables []IEvaluable = nil
113
114	//  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
115	// Things to be emitted, e.g. $a and $b in 'emit > "foo.dat", $a, $b'.
116
117	// Non-lashed: emit @a, "x"
118	// Lashed: emit (@a, @b), "x"
119	numEmittables := len(emittablesNode.Children)
120	names = make([]string, numEmittables)
121	emitEvaluables = make([]IEvaluable, numEmittables)
122	for i, emittableNode := range emittablesNode.Children {
123		name, emitEvaluable, err := this.buildEmittableNode(emittableNode)
124		if err != nil {
125			return nil, err
126		}
127		names[i] = name
128		emitEvaluables[i] = emitEvaluable
129	}
130
131	//  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
132	// Indices (if any) on the emittables
133
134	isIndexed := false
135	if keysNode.Type != dsl.NodeTypeNoOp { // There are "x","y" present
136		lib.InternalCodingErrorIf(keysNode.Type != dsl.NodeTypeEmitKeys)
137		isIndexed = true
138		numKeys := len(keysNode.Children)
139		indexEvaluables = make([]IEvaluable, numKeys)
140		for i, keyNode := range keysNode.Children {
141			indexEvaluable, err := this.BuildEvaluableNode(keyNode)
142			if err != nil {
143				return nil, err
144			}
145			indexEvaluables[i] = indexEvaluable
146		}
147	}
148
149	retval := &EmitXStatementNode{
150		names:           names,
151		emitEvaluables:  emitEvaluables,
152		indexEvaluables: indexEvaluables,
153		isEmitP:         isEmitP,
154	}
155
156	if !isIndexed {
157		retval.executorFunc = retval.executeNonIndexed
158	} else {
159		retval.executorFunc = retval.executeIndexed
160	}
161
162	//  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
163	// Redirections and redirection targets (the thing after > >> |, if any).
164
165	if redirectorNode.Type == dsl.NodeTypeNoOp {
166		// No > >> or | was provided.
167		retval.emitToRedirectFunc = retval.emitToRecordStream
168	} else {
169		// There is > >> or | provided.
170		lib.InternalCodingErrorIf(redirectorNode.Children == nil)
171		lib.InternalCodingErrorIf(len(redirectorNode.Children) != 1)
172		redirectorTargetNode := redirectorNode.Children[0]
173		var err error = nil
174
175		if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStdout {
176			retval.emitToRedirectFunc = retval.emitToFileOrPipe
177			retval.outputHandlerManager = output.NewStdoutWriteHandlerManager(this.recordWriterOptions)
178			retval.redirectorTargetEvaluable = this.BuildStringLiteralNode("(stdout)")
179		} else if redirectorTargetNode.Type == dsl.NodeTypeRedirectTargetStderr {
180			retval.emitToRedirectFunc = retval.emitToFileOrPipe
181			retval.outputHandlerManager = output.NewStderrWriteHandlerManager(this.recordWriterOptions)
182			retval.redirectorTargetEvaluable = this.BuildStringLiteralNode("(stderr)")
183		} else {
184			retval.emitToRedirectFunc = retval.emitToFileOrPipe
185
186			retval.redirectorTargetEvaluable, err = this.BuildEvaluableNode(redirectorTargetNode)
187			if err != nil {
188				return nil, err
189			}
190
191			if redirectorNode.Type == dsl.NodeTypeRedirectWrite {
192				retval.outputHandlerManager = output.NewFileWritetHandlerManager(this.recordWriterOptions)
193			} else if redirectorNode.Type == dsl.NodeTypeRedirectAppend {
194				retval.outputHandlerManager = output.NewFileAppendHandlerManager(this.recordWriterOptions)
195			} else if redirectorNode.Type == dsl.NodeTypeRedirectPipe {
196				retval.outputHandlerManager = output.NewPipeWriteHandlerManager(this.recordWriterOptions)
197			} else {
198				return nil, errors.New(
199					fmt.Sprintf(
200						"%s: unhandled redirector node type %s.",
201						lib.MlrExeName(), string(redirectorNode.Type),
202					),
203				)
204			}
205		}
206	}
207
208	// Register this with the CST root node so that open file descriptrs can be
209	// closed, etc at end of stream.
210	if retval.outputHandlerManager != nil {
211		this.RegisterOutputHandlerManager(retval.outputHandlerManager)
212	}
213
214	return retval, nil
215}
216
217// ----------------------------------------------------------------
218// This is a helper method for deciding whether an emittable node is a named
219// variable or a map.
220
221func (this *RootNode) buildEmittableNode(
222	astNode *dsl.ASTNode,
223) (name string, emitEvaluable IEvaluable, err error) {
224	name = "_"
225	emitEvaluable = nil
226	err = nil
227
228	if astNode.Type == dsl.NodeTypeLocalVariable {
229		name = string(astNode.Token.Lit)
230	} else if astNode.Type == dsl.NodeTypeDirectOosvarValue {
231		name = string(astNode.Token.Lit)
232	} else if astNode.Type == dsl.NodeTypeDirectFieldValue {
233		name = string(astNode.Token.Lit)
234	}
235
236	// xxx temp
237	// ----------------------------------------------------------------
238	// Emittable
239	//   y LocalVariable
240	//
241	//   n FullOosvar
242	//   y DirectOosvarValue -- includes BracedOosvarValue
243	//  -> IndirectOosvarValue
244	//
245	//   n FullSrec
246	//   y DirectFieldValue -- includes BracedFieldValue
247	//  -> IndirectFieldValue
248	//
249	//   n MapLiteral
250	// ;
251	// ----------------------------------------------------------------
252
253	emitEvaluable, err = this.BuildEvaluableNode(astNode)
254
255	return name, emitEvaluable, err
256}
257
258// ================================================================
259func (this *EmitXStatementNode) Execute(state *runtime.State) (*BlockExitPayload, error) {
260	return this.executorFunc(state)
261}
262
263// ----------------------------------------------------------------
264func (this *EmitXStatementNode) executeNonIndexed(
265	state *runtime.State,
266) (*BlockExitPayload, error) {
267
268	newrec := types.NewMlrmapAsRecord()
269
270	for i, emitEvaluable := range this.emitEvaluables {
271		emittable := emitEvaluable.Evaluate(state)
272		if emittable.IsAbsent() {
273			continue
274		}
275
276		if this.isEmitP {
277			newrec.PutCopy(this.names[i], emittable)
278		} else {
279			if emittable.IsMap() {
280				newrec.Merge(emittable.GetMap())
281			} else {
282				newrec.PutCopy(this.names[i], emittable)
283			}
284		}
285	}
286
287	err := this.emitToRedirectFunc(newrec, state)
288
289	return nil, err
290}
291
292// ----------------------------------------------------------------
293func (this *EmitXStatementNode) executeIndexed(
294	state *runtime.State,
295) (*BlockExitPayload, error) {
296	emittableMaps := make([]*types.Mlrmap, len(this.emitEvaluables))
297	for i, emitEvaluable := range this.emitEvaluables {
298		emittable := emitEvaluable.Evaluate(state)
299		if emittable.IsAbsent() {
300			return nil, nil
301		}
302		if !emittable.IsMap() {
303			return nil, nil
304		}
305		emittableMaps[i] = emittable.GetMap()
306	}
307	indices := make([]*types.Mlrval, len(this.indexEvaluables))
308
309	// TODO: libify this
310	for i, _ := range this.indexEvaluables {
311		indices[i] = this.indexEvaluables[i].Evaluate(state)
312		if indices[i].IsAbsent() {
313			return nil, nil
314		}
315		if indices[i].IsError() {
316			// TODO: surface this more highly
317			return nil, nil
318		}
319	}
320
321	return this.executeIndexedAux(
322		this.names,
323		types.NewMlrmapAsRecord(),
324		emittableMaps,
325		indices,
326		state,
327	)
328}
329
330// Recurses over indices.
331func (this *EmitXStatementNode) executeIndexedAux(
332	mapNames []string,
333	templateRecord *types.Mlrmap,
334	emittableMaps []*types.Mlrmap,
335	indices []*types.Mlrval,
336	state *runtime.State,
337) (*BlockExitPayload, error) {
338	lib.InternalCodingErrorIf(len(indices) < 1)
339	index := indices[0]
340	indexString := index.String()
341
342	for pe := emittableMaps[0].Head; pe != nil; pe = pe.Next {
343		newrec := templateRecord.Copy()
344
345		indexValue := types.MlrvalFromString(pe.Key)
346		newrec.PutCopy(indexString, &indexValue)
347		indexValueString := indexValue.String()
348
349		nextLevels := make([]*types.Mlrval, len(emittableMaps))
350		nextLevelMaps := make([]*types.Mlrmap, len(emittableMaps))
351		for i, emittableMap := range emittableMaps {
352			if emittableMap != nil {
353				nextLevel := emittableMap.Get(indexValueString)
354				nextLevels[i] = nextLevel
355				// Can be nil for lashed indexing with heterogeneous data: e.g.
356				// @x={"a":1}; @y={"b":2}; emit (@x, @y), "a"
357				if nextLevel != nil && nextLevel.IsMap() {
358					nextLevelMaps[i] = nextLevel.GetMap()
359				} else {
360					nextLevelMaps[i] = nil
361				}
362			} else {
363				nextLevelMaps[i] = nil
364			}
365		}
366
367		if nextLevelMaps[0] != nil && len(indices) >= 2 {
368			// recurse
369			this.executeIndexedAux(
370				mapNames,
371				newrec,
372				nextLevelMaps,
373				indices[1:],
374				state,
375			)
376		} else {
377			// end of recursion
378			if this.isEmitP {
379				for i, nextLevel := range nextLevels {
380					if nextLevel != nil {
381						newrec.PutCopy(mapNames[i], nextLevel)
382					}
383				}
384			} else {
385				for i, nextLevel := range nextLevels {
386					if nextLevel != nil {
387						if nextLevel.IsMap() {
388							newrec.Merge(nextLevelMaps[i])
389						} else {
390							newrec.PutCopy(mapNames[i], nextLevel)
391						}
392					}
393				}
394			}
395
396			err := this.emitToRedirectFunc(newrec, state)
397			if err != nil {
398				return nil, err
399			}
400		}
401	}
402
403	return nil, nil
404}
405
406// ----------------------------------------------------------------
407func (this *EmitXStatementNode) emitToRecordStream(
408	outrec *types.Mlrmap,
409	state *runtime.State,
410) error {
411	// The output channel is always non-nil, except for the Miller REPL.
412	if state.OutputChannel != nil {
413		state.OutputChannel <- types.NewRecordAndContext(outrec, state.Context)
414	} else {
415		fmt.Println(outrec.String())
416	}
417	return nil
418}
419
420// ----------------------------------------------------------------
421func (this *EmitXStatementNode) emitToFileOrPipe(
422	outrec *types.Mlrmap,
423	state *runtime.State,
424) error {
425	redirectorTarget := this.redirectorTargetEvaluable.Evaluate(state)
426	if !redirectorTarget.IsString() {
427		return errors.New(
428			fmt.Sprintf(
429				"%s: output redirection yielded %s, not string.",
430				lib.MlrExeName(), redirectorTarget.GetTypeName(),
431			),
432		)
433	}
434	outputFileName := redirectorTarget.String()
435
436	return this.outputHandlerManager.WriteRecordAndContext(
437		types.NewRecordAndContext(outrec, state.Context),
438		outputFileName,
439	)
440}
441