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