github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/compile_effect.go (about) 1 package eval 2 3 import ( 4 "fmt" 5 "os" 6 "sync" 7 8 "src.elv.sh/pkg/diag" 9 "src.elv.sh/pkg/eval/errs" 10 "src.elv.sh/pkg/eval/vals" 11 "src.elv.sh/pkg/eval/vars" 12 "src.elv.sh/pkg/fsutil" 13 "src.elv.sh/pkg/parse" 14 "src.elv.sh/pkg/parse/cmpd" 15 ) 16 17 // An operation with some side effects. 18 type effectOp interface{ exec(*Frame) Exception } 19 20 func (cp *compiler) chunkOp(n *parse.Chunk) effectOp { 21 return chunkOp{n.Range(), cp.pipelineOps(n.Pipelines)} 22 } 23 24 type chunkOp struct { 25 diag.Ranging 26 subops []effectOp 27 } 28 29 func (op chunkOp) exec(fm *Frame) Exception { 30 for _, subop := range op.subops { 31 exc := subop.exec(fm) 32 if exc != nil { 33 return exc 34 } 35 } 36 // Check for interrupts after the chunk. 37 // We also check for interrupts before each pipeline, so there is no 38 // need to check it before the chunk or after each pipeline. 39 if fm.IsInterrupted() { 40 return fm.errorp(op, ErrInterrupted) 41 } 42 return nil 43 } 44 45 func (cp *compiler) pipelineOp(n *parse.Pipeline) effectOp { 46 formOps := cp.formOps(n.Forms) 47 48 return &pipelineOp{n.Range(), n.Background, parse.SourceText(n), formOps} 49 } 50 51 func (cp *compiler) pipelineOps(ns []*parse.Pipeline) []effectOp { 52 ops := make([]effectOp, len(ns)) 53 for i, n := range ns { 54 ops[i] = cp.pipelineOp(n) 55 } 56 return ops 57 } 58 59 type pipelineOp struct { 60 diag.Ranging 61 bg bool 62 source string 63 subops []effectOp 64 } 65 66 const pipelineChanBufferSize = 32 67 68 func (op *pipelineOp) exec(fm *Frame) Exception { 69 if fm.IsInterrupted() { 70 return fm.errorp(op, ErrInterrupted) 71 } 72 73 if op.bg { 74 fm = fm.fork("background job" + op.source) 75 fm.intCh = nil 76 fm.background = true 77 fm.Evaler.addNumBgJobs(1) 78 } 79 80 nforms := len(op.subops) 81 82 var wg sync.WaitGroup 83 wg.Add(nforms) 84 excs := make([]Exception, nforms) 85 86 var nextIn *Port 87 88 // For each form, create a dedicated evalCtx and run asynchronously 89 for i, formOp := range op.subops { 90 hasChanInput := i > 0 91 newFm := fm.fork("[form op]") 92 if i > 0 { 93 newFm.ports[0] = nextIn 94 } 95 if i < nforms-1 { 96 // Each internal port pair consists of a (byte) pipe pair and a 97 // channel. 98 // os.Pipe sets O_CLOEXEC, which is what we want. 99 reader, writer, e := os.Pipe() 100 if e != nil { 101 return fm.errorpf(op, "failed to create pipe: %s", e) 102 } 103 ch := make(chan interface{}, pipelineChanBufferSize) 104 newFm.ports[1] = &Port{ 105 File: writer, Chan: ch, closeFile: true, closeChan: true} 106 nextIn = &Port{ 107 File: reader, Chan: ch, closeFile: true, closeChan: false} 108 } 109 thisOp := formOp 110 thisExc := &excs[i] 111 go func() { 112 exc := thisOp.exec(newFm) 113 newFm.Close() 114 if exc != nil { 115 *thisExc = exc 116 } 117 wg.Done() 118 if hasChanInput { 119 // If the command has channel input, drain it. This 120 // mitigates the effect of erroneous pipelines like 121 // "range 100 | cat"; without draining the pipeline will 122 // lock up. 123 for range newFm.InputChan() { 124 } 125 } 126 }() 127 } 128 129 if op.bg { 130 // Background job, wait for form termination asynchronously. 131 go func() { 132 wg.Wait() 133 fm.Evaler.addNumBgJobs(-1) 134 msg := "job " + op.source + " finished" 135 err := MakePipelineError(excs) 136 if err != nil { 137 msg += ", errors = " + err.Error() 138 } 139 if fm.Evaler.getNotifyBgJobSuccess() || err != nil { 140 fm.ErrorFile().WriteString(msg + "\n") 141 } 142 }() 143 return nil 144 } 145 wg.Wait() 146 return fm.errorp(op, MakePipelineError(excs)) 147 } 148 149 func (cp *compiler) formOp(n *parse.Form) effectOp { 150 var tempLValues []lvalue 151 var assignmentOps []effectOp 152 if len(n.Assignments) > 0 { 153 assignmentOps = cp.assignmentOps(n.Assignments) 154 if n.Head == nil { 155 cp.errorpf(n, `using the syntax of temporary assignment for non-temporary assignment is no longer supported; use "var" or "set" instead`) 156 return nopOp{} 157 } 158 for _, a := range n.Assignments { 159 lvalues := cp.parseIndexingLValue(a.Left) 160 tempLValues = append(tempLValues, lvalues.lvalues...) 161 } 162 logger.Println("temporary assignment of", len(n.Assignments), "pairs") 163 } 164 165 redirOps := cp.redirOps(n.Redirs) 166 body := cp.formBody(n) 167 168 return &formOp{n.Range(), tempLValues, assignmentOps, redirOps, body} 169 } 170 171 func (cp *compiler) formBody(n *parse.Form) formBody { 172 if n.Head == nil { 173 // Compiling an incomplete form node, return an empty body. 174 return formBody{} 175 } 176 177 // Determine if this form is a special command. 178 if head, ok := cmpd.StringLiteral(n.Head); ok { 179 special, _ := resolveCmdHeadInternally(cp, head, n.Head) 180 if special != nil { 181 specialOp := special(cp, n) 182 return formBody{specialOp: specialOp} 183 } 184 } 185 186 // Determine if the form is a legacy assignment form, by looking for an 187 // argument whose source is a literal "=". 188 for i, arg := range n.Args { 189 if parse.SourceText(arg) == "=" { 190 lhsNodes := make([]*parse.Compound, i+1) 191 lhsNodes[0] = n.Head 192 copy(lhsNodes[1:], n.Args[:i]) 193 lhs := cp.parseCompoundLValues(lhsNodes) 194 195 rhsOps := cp.compoundOps(n.Args[i+1:]) 196 var rhsRange diag.Ranging 197 if len(rhsOps) > 0 { 198 rhsRange = diag.MixedRanging(rhsOps[0], rhsOps[len(rhsOps)-1]) 199 } else { 200 rhsRange = diag.PointRanging(n.Range().To) 201 } 202 rhs := seqValuesOp{rhsRange, rhsOps} 203 204 return formBody{assignOp: &assignOp{n.Range(), lhs, rhs}} 205 } 206 } 207 208 var headOp valuesOp 209 if head, ok := cmpd.StringLiteral(n.Head); ok { 210 // Head is a literal string: resolve to function or external (special 211 // commands are already handled above). 212 if _, fnRef := resolveCmdHeadInternally(cp, head, n.Head); fnRef != nil { 213 headOp = variableOp{n.Head.Range(), false, head + FnSuffix, fnRef} 214 } else { 215 headOp = literalValues(n.Head, NewExternalCmd(head)) 216 } 217 } else { 218 // Head is not a literal string: evaluate as a normal expression. 219 headOp = cp.compoundOp(n.Head) 220 } 221 222 argOps := cp.compoundOps(n.Args) 223 optsOp := cp.mapPairs(n.Opts) 224 return formBody{ordinaryCmd: ordinaryCmd{headOp, argOps, optsOp}} 225 } 226 227 func (cp *compiler) formOps(ns []*parse.Form) []effectOp { 228 ops := make([]effectOp, len(ns)) 229 for i, n := range ns { 230 ops[i] = cp.formOp(n) 231 } 232 return ops 233 } 234 235 type formOp struct { 236 diag.Ranging 237 tempLValues []lvalue 238 tempAssignOps []effectOp 239 redirOps []effectOp 240 body formBody 241 } 242 243 type formBody struct { 244 // Exactly one field will be populated. 245 specialOp effectOp 246 assignOp effectOp 247 ordinaryCmd ordinaryCmd 248 } 249 250 type ordinaryCmd struct { 251 headOp valuesOp 252 argOps []valuesOp 253 optsOp *mapPairsOp 254 } 255 256 func (op *formOp) exec(fm *Frame) (errRet Exception) { 257 // fm here is always a sub-frame created in compiler.pipeline, so it can 258 // be safely modified. 259 260 // Temporary assignment. 261 if len(op.tempLValues) > 0 { 262 // There is a temporary assignment. 263 // Save variables. 264 var saveVars []vars.Var 265 var saveVals []interface{} 266 for _, lv := range op.tempLValues { 267 variable, err := derefLValue(fm, lv) 268 if err != nil { 269 return fm.errorp(op, err) 270 } 271 saveVars = append(saveVars, variable) 272 } 273 for i, v := range saveVars { 274 // TODO(xiaq): If the variable to save is a elemVariable, save 275 // the outermost variable instead. 276 if u := vars.HeadOfElement(v); u != nil { 277 v = u 278 saveVars[i] = v 279 } 280 val := v.Get() 281 saveVals = append(saveVals, val) 282 logger.Printf("saved %s = %s", v, val) 283 } 284 // Do assignment. 285 for _, subop := range op.tempAssignOps { 286 exc := subop.exec(fm) 287 if exc != nil { 288 return exc 289 } 290 } 291 // Defer variable restoration. Will be executed even if an error 292 // occurs when evaling other part of the form. 293 defer func() { 294 for i, v := range saveVars { 295 val := saveVals[i] 296 if val == nil { 297 // TODO(xiaq): Old value is nonexistent. We should delete 298 // the variable. However, since the compiler now doesn't 299 // delete it, we don't delete it in the evaler either. 300 val = "" 301 } 302 err := v.Set(val) 303 if err != nil { 304 errRet = fm.errorp(op, err) 305 } 306 logger.Printf("restored %s = %s", v, val) 307 } 308 }() 309 } 310 311 // Redirections. 312 for _, redirOp := range op.redirOps { 313 exc := redirOp.exec(fm) 314 if exc != nil { 315 return exc 316 } 317 } 318 319 if op.body.specialOp != nil { 320 return op.body.specialOp.exec(fm) 321 } 322 if op.body.assignOp != nil { 323 return op.body.assignOp.exec(fm) 324 } 325 326 // Ordinary command: evaluate head, arguments and options. 327 cmd := op.body.ordinaryCmd 328 329 // Special case: evaluating an incomplete form node. Return directly. 330 if cmd.headOp == nil { 331 return nil 332 } 333 334 headFn, err := evalForCommand(fm, cmd.headOp, "command") 335 if err != nil { 336 return fm.errorp(cmd.headOp, err) 337 } 338 339 var args []interface{} 340 for _, argOp := range cmd.argOps { 341 moreArgs, exc := argOp.exec(fm) 342 if exc != nil { 343 return exc 344 } 345 args = append(args, moreArgs...) 346 } 347 348 // TODO(xiaq): This conversion should be avoided. 349 convertedOpts := make(map[string]interface{}) 350 exc := cmd.optsOp.exec(fm, func(k, v interface{}) Exception { 351 if ks, ok := k.(string); ok { 352 convertedOpts[ks] = v 353 return nil 354 } 355 // TODO(xiaq): Point to the particular key. 356 return fm.errorp(op, errs.BadValue{ 357 What: "option key", Valid: "string", Actual: vals.Kind(k)}) 358 }) 359 if exc != nil { 360 return exc 361 } 362 363 fm.traceback = fm.addTraceback(op) 364 err = headFn.Call(fm, args, convertedOpts) 365 if exc, ok := err.(Exception); ok { 366 return exc 367 } 368 return &exception{err, fm.traceback} 369 } 370 371 func evalForCommand(fm *Frame, op valuesOp, what string) (Callable, error) { 372 value, err := evalForValue(fm, op, what) 373 if err != nil { 374 return nil, err 375 } 376 switch value := value.(type) { 377 case Callable: 378 return value, nil 379 case string: 380 if fsutil.DontSearch(value) { 381 return NewExternalCmd(value), nil 382 } 383 } 384 return nil, fm.errorp(op, errs.BadValue{ 385 What: what, 386 Valid: "callable or string containing slash", 387 Actual: vals.Kind(value)}) 388 } 389 390 func allTrue(vs []interface{}) bool { 391 for _, v := range vs { 392 if !vals.Bool(v) { 393 return false 394 } 395 } 396 return true 397 } 398 399 func (cp *compiler) assignmentOp(n *parse.Assignment) effectOp { 400 lhs := cp.parseIndexingLValue(n.Left) 401 rhs := cp.compoundOp(n.Right) 402 return &assignOp{n.Range(), lhs, rhs} 403 } 404 405 func (cp *compiler) assignmentOps(ns []*parse.Assignment) []effectOp { 406 ops := make([]effectOp, len(ns)) 407 for i, n := range ns { 408 ops[i] = cp.assignmentOp(n) 409 } 410 return ops 411 } 412 413 const defaultFileRedirPerm = 0644 414 415 // redir compiles a Redir into a op. 416 func (cp *compiler) redirOp(n *parse.Redir) effectOp { 417 var dstOp valuesOp 418 if n.Left != nil { 419 dstOp = cp.compoundOp(n.Left) 420 } 421 flag := makeFlag(n.Mode) 422 if flag == -1 { 423 // TODO: Record and get redirection sign position 424 cp.errorpf(n, "bad redirection sign") 425 } 426 return &redirOp{n.Range(), dstOp, cp.compoundOp(n.Right), n.RightIsFd, n.Mode, flag} 427 } 428 429 func (cp *compiler) redirOps(ns []*parse.Redir) []effectOp { 430 ops := make([]effectOp, len(ns)) 431 for i, n := range ns { 432 ops[i] = cp.redirOp(n) 433 } 434 return ops 435 } 436 437 func makeFlag(m parse.RedirMode) int { 438 switch m { 439 case parse.Read: 440 return os.O_RDONLY 441 case parse.Write: 442 return os.O_WRONLY | os.O_CREATE | os.O_TRUNC 443 case parse.ReadWrite: 444 return os.O_RDWR | os.O_CREATE 445 case parse.Append: 446 return os.O_WRONLY | os.O_CREATE | os.O_APPEND 447 default: 448 return -1 449 } 450 } 451 452 type redirOp struct { 453 diag.Ranging 454 dstOp valuesOp 455 srcOp valuesOp 456 srcIsFd bool 457 mode parse.RedirMode 458 flag int 459 } 460 461 type invalidFD struct{ fd int } 462 463 func (err invalidFD) Error() string { return fmt.Sprintf("invalid fd: %d", err.fd) } 464 465 // Returns a suitable dummy value for the channel part of the port when 466 // redirecting from or to a file, so that the read and write attempts fail 467 // silently (instead of blocking or panicking). 468 // 469 // TODO: Instead of letting read and write attempts fail silently, consider 470 // raising an exception instead. 471 func chanForFileRedir(mode parse.RedirMode) chan interface{} { 472 if mode == parse.Read { 473 // ClosedChan produces no values when reading. 474 return ClosedChan 475 } 476 // BlackholeChan discards all values written to it. 477 return BlackholeChan 478 } 479 480 func (op *redirOp) exec(fm *Frame) Exception { 481 var dst int 482 if op.dstOp == nil { 483 // No explicit FD destination specified; use default destinations 484 switch op.mode { 485 case parse.Read: 486 dst = 0 487 case parse.Write, parse.ReadWrite, parse.Append: 488 dst = 1 489 default: 490 return fm.errorpf(op, "bad RedirMode; parser bug") 491 } 492 } else { 493 // An explicit FD destination specified, evaluate it. 494 var err error 495 dst, err = evalForFd(fm, op.dstOp, false, "redirection destination") 496 if err != nil { 497 return fm.errorp(op, err) 498 } 499 } 500 501 growPorts(&fm.ports, dst+1) 502 fm.ports[dst].close() 503 504 if op.srcIsFd { 505 src, err := evalForFd(fm, op.srcOp, true, "redirection source") 506 if err != nil { 507 return fm.errorp(op, err) 508 } 509 switch { 510 case src == -1: 511 // close 512 fm.ports[dst] = &Port{} 513 case src >= len(fm.ports) || fm.ports[src] == nil: 514 return fm.errorp(op, invalidFD{src}) 515 default: 516 fm.ports[dst] = fm.ports[src].fork() 517 } 518 return nil 519 } 520 src, err := evalForValue(fm, op.srcOp, "redirection source") 521 if err != nil { 522 return fm.errorp(op, err) 523 } 524 switch src := src.(type) { 525 case string: 526 f, err := os.OpenFile(src, op.flag, defaultFileRedirPerm) 527 if err != nil { 528 return fm.errorpf(op, "failed to open file %s: %s", vals.Repr(src, vals.NoPretty), err) 529 } 530 fm.ports[dst] = &Port{File: f, closeFile: true, Chan: chanForFileRedir(op.mode)} 531 case vals.File: 532 fm.ports[dst] = &Port{File: src, closeFile: false, Chan: chanForFileRedir(op.mode)} 533 case vals.Pipe: 534 var f *os.File 535 switch op.mode { 536 case parse.Read: 537 f = src.ReadEnd 538 case parse.Write: 539 f = src.WriteEnd 540 default: 541 return fm.errorpf(op, "can only use < or > with pipes") 542 } 543 fm.ports[dst] = &Port{File: f, closeFile: false, Chan: chanForFileRedir(op.mode)} 544 default: 545 return fm.errorp(op.srcOp, errs.BadValue{ 546 What: "redirection source", 547 Valid: "string, file or pipe", Actual: vals.Kind(src)}) 548 } 549 return nil 550 } 551 552 // Makes the size of *ports at least n, adding nil's if necessary. 553 func growPorts(ports *[]*Port, n int) { 554 if len(*ports) >= n { 555 return 556 } 557 oldPorts := *ports 558 *ports = make([]*Port, n) 559 copy(*ports, oldPorts) 560 } 561 562 func evalForFd(fm *Frame, op valuesOp, closeOK bool, what string) (int, error) { 563 value, err := evalForValue(fm, op, what) 564 if err != nil { 565 return -1, err 566 } 567 switch value { 568 case "stdin": 569 return 0, nil 570 case "stdout": 571 return 1, nil 572 case "stderr": 573 return 2, nil 574 } 575 var fd int 576 if vals.ScanToGo(value, &fd) == nil { 577 return fd, nil 578 } else if value == "-" && closeOK { 579 return -1, nil 580 } 581 valid := "fd name or number" 582 if closeOK { 583 valid = "fd name or number or '-'" 584 } 585 return -1, fm.errorp(op, errs.BadValue{ 586 What: what, Valid: valid, Actual: vals.Repr(value, vals.NoPretty)}) 587 } 588 589 type seqOp struct{ subops []effectOp } 590 591 func (op seqOp) exec(fm *Frame) Exception { 592 for _, subop := range op.subops { 593 exc := subop.exec(fm) 594 if exc != nil { 595 return exc 596 } 597 } 598 return nil 599 } 600 601 type nopOp struct{} 602 603 func (nopOp) exec(fm *Frame) Exception { return nil }