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