1implement Sh; 2 3include "sys.m"; 4 sys: Sys; 5 sprint: import sys; 6include "draw.m"; 7include "bufio.m"; 8 bufio: Bufio; 9include "string.m"; 10 str: String; 11include "filepat.m"; 12 filepat: Filepat; 13include "env.m"; 14 env: Env; 15include "sh.m"; 16 myself: Sh; 17 myselfbuiltin: Shellbuiltin; 18 19YYSTYPE: adt { 20 node: ref Node; 21 word: string; 22 23 redir: ref Redir; 24 optype: int; 25}; 26 27YYLEX: adt { 28 lval: YYSTYPE; 29 err: string; # if error has occurred 30 errline: int; # line it occurred on. 31 path: string; # name of file that's being read. 32 33 # free caret state 34 wasdollar: int; 35 atendword: int; 36 eof: int; 37 cbuf: array of int; # last chars read 38 ncbuf: int; # number of chars in cbuf 39 40 f: ref Bufio->Iobuf; 41 s: string; 42 strpos: int; # string pos/cbuf index 43 44 linenum: int; 45 prompt: string; 46 lastnl: int; 47 48 initstring: fn(s: string): ref YYLEX; 49 initfile: fn(fd: ref Sys->FD, path: string): ref YYLEX; 50 lex: fn(l: self ref YYLEX): int; 51 error: fn(l: self ref YYLEX, err: string); 52 getc: fn(l: self ref YYLEX): int; 53 ungetc: fn(l: self ref YYLEX); 54 55 EOF: con -1; 56}; 57 58Options: adt { 59 lflag, 60 nflag: int; 61 ctxtflags: int; 62 carg: string; 63}; 64 65 66 # module definition is in shell.m 67DUP: con 57346; 68REDIR: con 57347; 69WORD: con 57348; 70OP: con 57349; 71END: con 57350; 72ERROR: con 57351; 73ANDAND: con 57352; 74OROR: con 57353; 75YYEOFCODE: con 1; 76YYERRCODE: con 2; 77YYMAXDEPTH: con 200; 78 79 80 81EPERM: con "permission denied"; 82EPIPE: con "write on closed pipe"; 83 84LIBSHELLRC: con "/lib/sh/profile"; 85BUILTINPATH: con "/dis/sh"; 86 87DEBUG: con 0; 88 89ENVSEP: con 0; # word seperator in external environment 90ENVHASHSIZE: con 7; # XXX profile usage of this... 91OAPPEND: con 16r80000; # make sure this doesn't clash with O* constants in sys.m 92OMASK: con 7; 93 94usage() 95{ 96 sys->fprint(stderr(), "usage: sh [-ilexn] [-c command] [file [arg...]]\n"); 97 raise "fail:usage"; 98} 99 100badmodule(path: string) 101{ 102 sys->fprint(sys->fildes(2), "sh: cannot load %s: %r\n", path); 103 raise "fail:bad module" ; 104} 105 106initialise() 107{ 108 if (sys == nil) { 109 sys = load Sys Sys->PATH; 110 111 filepat = load Filepat Filepat->PATH; 112 if (filepat == nil) badmodule(Filepat->PATH); 113 114 str = load String String->PATH; 115 if (str == nil) badmodule(String->PATH); 116 117 bufio = load Bufio Bufio->PATH; 118 if (bufio == nil) badmodule(Bufio->PATH); 119 120 myself = load Sh "$self"; 121 if (myself == nil) badmodule("$self(Sh)"); 122 123 myselfbuiltin = load Shellbuiltin "$self"; 124 if (myselfbuiltin == nil) badmodule("$self(Shellbuiltin)"); 125 126 env = load Env Env->PATH; 127 } 128} 129blankopts: Options; 130init(drawcontext: ref Draw->Context, argv: list of string) 131{ 132 initialise(); 133 opts := blankopts; 134 if (argv != nil) { 135 if ((hd argv)[0] == '-') 136 opts.lflag++; 137 argv = tl argv; 138 } 139 140 interactive := 0; 141loop: while (argv != nil && hd argv != nil && (hd argv)[0] == '-') { 142 for (i := 1; i < len hd argv; i++) { 143 c := (hd argv)[i]; 144 case c { 145 'i' => 146 interactive = Context.INTERACTIVE; 147 'l' => 148 opts.lflag++; # login (read $home/lib/profile) 149 'n' => 150 opts.nflag++; # don't fork namespace 151 'e' => 152 opts.ctxtflags |= Context.ERROREXIT; 153 'x' => 154 opts.ctxtflags |= Context.EXECPRINT; 155 'c' => 156 arg: string; 157 if (i < len hd argv - 1) { 158 arg = (hd argv)[i + 1:]; 159 } else if (tl argv == nil || hd tl argv == "") { 160 usage(); 161 } else { 162 arg = hd tl argv; 163 argv = tl argv; 164 } 165 argv = tl argv; 166 opts.carg = arg; 167 continue loop; 168 } 169 } 170 argv = tl argv; 171 } 172 173 sys->pctl(Sys->FORKFD, nil); 174 if (!opts.nflag) 175 sys->pctl(Sys->FORKNS, nil); 176 ctxt := Context.new(drawcontext); 177 ctxt.setoptions(opts.ctxtflags, 1); 178 179 # if login shell, run standard init script 180 if (opts.lflag) 181 runscript(ctxt, LIBSHELLRC, nil, 0); 182 if (opts.carg != nil) { 183 status := ctxt.run(stringlist2list("{" + opts.carg + "}" :: argv), !interactive); 184 if (!interactive) { 185 if (status != nil) 186 raise "fail:" + status; 187 exit; 188 } 189 setstatus(ctxt, status); 190 } 191 if (argv == nil) { 192 if (isconsole(sys->fildes(0))) 193 interactive |= ctxt.INTERACTIVE; 194 ctxt.setoptions(interactive, 1); 195 runfile(ctxt, sys->fildes(0), "stdin", nil); 196 } else { 197 ctxt.setoptions(interactive, 1); 198 runscript(ctxt, hd argv, stringlist2list(tl argv), 1); 199 } 200} 201 202parse(s: string): (ref Node, string) 203{ 204 initialise(); 205 206 lex := YYLEX.initstring(s); 207 208 return doparse(lex, "", 0); 209} 210 211system(drawctxt: ref Draw->Context, cmd: string): string 212{ 213 initialise(); 214 { 215 (n, err) := parse(cmd); 216 if (err != nil) 217 return err; 218 if (n == nil) 219 return nil; 220 return Context.new(drawctxt).run(ref Listnode(n, nil) :: nil, 0); 221 } exception e { 222 "fail:*" => 223 return failurestatus(e); 224 } 225} 226 227run(drawctxt: ref Draw->Context, argv: list of string): string 228{ 229 initialise(); 230 { 231 return Context.new(drawctxt).run(stringlist2list(argv), 0); 232 } exception e { 233 "fail:*" => 234 return failurestatus(e); 235 } 236} 237 238isconsole(fd: ref Sys->FD): int 239{ 240 (ok1, d1) := sys->fstat(fd); 241 (ok2, d2) := sys->stat("/dev/cons"); 242 if (ok1 < 0 || ok2 < 0) 243 return 0; 244 return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path; 245} 246 247runscript(ctxt: ref Context, path: string, args: list of ref Listnode, reporterr: int) 248{ 249 { 250 fd := sys->open(path, Sys->OREAD); 251 if (fd != nil) 252 runfile(ctxt, fd, path, args); 253 else if (reporterr) 254 ctxt.fail("bad script path", sys->sprint("sh: cannot open %s: %r", path)); 255 } exception { 256 "fail:*" => 257 if(!reporterr) 258 return; 259 raise; 260 } 261} 262 263runfile(ctxt: ref Context, fd: ref Sys->FD, path: string, args: list of ref Listnode) 264{ 265 ctxt.push(); 266 { 267 ctxt.setlocal("0", stringlist2list(path :: nil)); 268 ctxt.setlocal("*", args); 269 lex := YYLEX.initfile(fd, path); 270 if (DEBUG) debug(sprint("parse(interactive == %d)", (ctxt.options() & ctxt.INTERACTIVE) != 0)); 271 prompt := "" :: "" :: nil; 272 laststatus: string; 273 while (!lex.eof) { 274 interactive := ctxt.options() & ctxt.INTERACTIVE; 275 if (interactive) { 276 prompt = list2stringlist(ctxt.get("prompt")); 277 if (prompt == nil) 278 prompt = "; " :: "" :: nil; 279 280 sys->fprint(stderr(), "%s", hd prompt); 281 if (tl prompt == nil) { 282 prompt = hd prompt :: "" :: nil; 283 } 284 } 285 (n, err) := doparse(lex, hd tl prompt, !interactive); 286 if (err != nil) { 287 sys->fprint(stderr(), "sh: %s\n", err); 288 if (!interactive) 289 raise "fail:parse error"; 290 } else if (n != nil) { 291 if (interactive) { 292 { 293 laststatus = walk(ctxt, n, 0); 294 } exception e2 { 295 "fail:*" => 296 laststatus = failurestatus(e2); 297 } 298 } else 299 laststatus = walk(ctxt, n, 0); 300 setstatus(ctxt, laststatus); 301 if ((ctxt.options() & ctxt.ERROREXIT) && laststatus != nil) 302 break; 303 } 304 } 305 if (laststatus != nil) 306 raise "fail:" + laststatus; 307 ctxt.pop(); 308 } 309 exception { 310 "fail:*" => 311 ctxt.pop(); 312 raise; 313 } 314} 315 316nonexistent(e: string): int 317{ 318 errs := array[] of {"does not exist", "directory entry not found"}; 319 for (i := 0; i < len errs; i++){ 320 j := len errs[i]; 321 if (j <= len e && e[len e-j:] == errs[i]) 322 return 1; 323 } 324 return 0; 325} 326 327Redirword: adt { 328 fd: ref Sys->FD; 329 w: string; 330 r: Redir; 331}; 332 333Redirlist: adt { 334 r: list of Redirword; 335}; 336 337pipe2cmd(n: ref Node): ref Node 338{ 339 if (n == nil || n.ntype != n_PIPE) 340 return n; 341 return mk(n_ADJ, mk(n_BLOCK,n,nil), mk(n_VAR,ref Node(n_WORD,nil,nil,"*",nil),nil)); 342} 343 344walk(ctxt: ref Context, n: ref Node, last: int): string 345{ 346 if (DEBUG) debug(sprint("walking: %s", cmd2string(n))); 347 # avoid tail recursion stack explosion 348 while (n != nil && n.ntype == n_SEQ) { 349 status := walk(ctxt, n.left, 0); 350 if (ctxt.options() & ctxt.ERROREXIT && status != nil) 351 raise "fail:" + status; 352 setstatus(ctxt, status); 353 n = n.right; 354 } 355 if (n == nil) 356 return nil; 357 case (n.ntype) { 358 n_PIPE => 359 return waitfor(ctxt, walkpipeline(ctxt, n, nil, -1)); 360 n_ASSIGN or n_LOCAL => 361 assign(ctxt, n); 362 return nil; 363 * => 364 bg := 0; 365 if (n.ntype == n_NOWAIT) { 366 bg = 1; 367 n = pipe2cmd(n.left); 368 } 369 370 redirs := ref Redirlist(nil); 371 line := glob(glom(ctxt, n, redirs, nil)); 372 373 if (bg) { 374 startchan := chan of (int, ref Expropagate); 375 spawn runasync(ctxt, 1, line, redirs, startchan); 376 (pid, nil) := <-startchan; 377 redirs = nil; 378 if (DEBUG) debug("started background process "+ string pid); 379 ctxt.set("apid", ref Listnode(nil, string pid) :: nil); 380 return nil; 381 } else { 382 return runsync(ctxt, line, redirs, last); 383 } 384 } 385} 386 387assign(ctxt: ref Context, n: ref Node): list of ref Listnode 388{ 389 redirs := ref Redirlist; 390 val: list of ref Listnode; 391 if (n.right != nil && (n.right.ntype == n_ASSIGN || n.right.ntype == n_LOCAL)) 392 val = assign(ctxt, n.right); 393 else 394 val = glob(glom(ctxt, n.right, redirs, nil)); 395 vars := glom(ctxt, n.left, redirs, nil); 396 if (vars == nil) 397 ctxt.fail("bad assign", "sh: nil variable name"); 398 if (redirs.r != nil) 399 ctxt.fail("bad assign", "sh: redirections not allowed in assignment"); 400 tval := val; 401 for (; vars != nil; vars = tl vars) { 402 vname := deglob((hd vars).word); 403 if (vname == nil) 404 ctxt.fail("bad assign", "sh: bad variable name"); 405 v: list of ref Listnode = nil; 406 if (tl vars == nil) 407 v = tval; 408 else if (tval != nil) 409 v = hd tval :: nil; 410 if (n.ntype == n_ASSIGN) 411 ctxt.set(vname, v); 412 else 413 ctxt.setlocal(vname, v); 414 if (tval != nil) 415 tval = tl tval; 416 } 417 return val; 418} 419 420walkpipeline(ctxt: ref Context, n: ref Node, wrpipe: ref Sys->FD, wfdno: int): list of int 421{ 422 if (n == nil) 423 return nil; 424 425 fds := array[2] of ref Sys->FD; 426 pids: list of int; 427 rfdno := -1; 428 if (n.ntype == n_PIPE) { 429 if (sys->pipe(fds) == -1) 430 ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r")); 431 nwfdno := -1; 432 if (n.redir != nil) { 433 (fd1, fd2) := (n.redir.fd2, n.redir.fd1); 434 if (fd2 == -1) 435 (fd1, fd2) = (fd2, fd1); 436 (nwfdno, rfdno) = (fd2, fd1); 437 } 438 pids = walkpipeline(ctxt, n.left, fds[1], nwfdno); 439 fds[1] = nil; 440 n = n.right; 441 } 442 r := ref Redirlist(nil); 443 rlist := glob(glom(ctxt, n, r, nil)); 444 if (fds[0] != nil) { 445 if (rfdno == -1) 446 rfdno = 0; 447 r.r = Redirword(fds[0], nil, Redir(Sys->OREAD, rfdno, -1)) :: r.r; 448 } 449 if (wrpipe != nil) { 450 if (wfdno == -1) 451 wfdno = 1; 452 r.r = Redirword(wrpipe, nil, Redir(Sys->OWRITE, wfdno, -1)) :: r.r; 453 } 454 startchan := chan of (int, ref Expropagate); 455 spawn runasync(ctxt, 1, rlist, r, startchan); 456 (pid, nil) := <-startchan; 457 if (DEBUG) debug("started pipe process "+string pid); 458 return pid :: pids; 459} 460 461makeredir(f: string, mode: int, fd: int): Redirword 462{ 463 return Redirword(nil, f, Redir(mode, fd, -1)); 464} 465 466glom(ctxt: ref Context, n: ref Node, redirs: ref Redirlist, onto: list of ref Listnode) 467 : list of ref Listnode 468{ 469 if (n == nil) return nil; 470 471 if (n.ntype != n_ADJ) 472 return listjoin(glomoperation(ctxt, n, redirs), onto); 473 474 nlist := glom(ctxt, n.right, redirs, onto); 475 476 if (n.left.ntype != n_ADJ) { 477 # if it's a terminal node 478 nlist = listjoin(glomoperation(ctxt, n.left, redirs), nlist); 479 } else 480 nlist = glom(ctxt, n.left, redirs, nlist); 481 return nlist; 482} 483 484listjoin(left, right: list of ref Listnode): list of ref Listnode 485{ 486 l: list of ref Listnode; 487 for (; left != nil; left = tl left) 488 l = hd left :: l; 489 for (; l != nil; l = tl l) 490 right = hd l :: right; 491 return right; 492} 493 494pipecmd(ctxt: ref Context, cmd: list of ref Listnode, redir: ref Redir): ref Sys->FD 495{ 496 if(redir.fd2 != -1 || (redir.rtype & OAPPEND)) 497 ctxt.fail("bad redir", "sh: bad redirection"); 498 r := *redir; 499 case redir.rtype { 500 Sys->OREAD => 501 r.rtype = Sys->OWRITE; 502 Sys->OWRITE => 503 r.rtype = Sys->OREAD; 504 } 505 506 p := array[2] of ref Sys->FD; 507 if(sys->pipe(p) == -1) 508 ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r")); 509 startchan := chan of (int, ref Expropagate); 510 spawn runasync(ctxt, 1, cmd, ref Redirlist((p[1], nil, r) :: nil), startchan); 511 p[1] = nil; 512 <-startchan; 513 return p[0]; 514} 515 516glomoperation(ctxt: ref Context, n: ref Node, redirs: ref Redirlist): list of ref Listnode 517{ 518 if (n == nil) 519 return nil; 520 521 nlist: list of ref Listnode; 522 case n.ntype { 523 n_WORD => 524 nlist = ref Listnode(nil, n.word) :: nil; 525 n_REDIR => 526 wlist := glob(glom(ctxt, n.left, ref Redirlist(nil), nil)); 527 if (len wlist != 1) 528 ctxt.fail("bad redir", "sh: single redirection operand required"); 529 if((hd wlist).cmd != nil){ 530 fd := pipecmd(ctxt, wlist, n.redir); 531 redirs.r = Redirword(fd, nil, (n.redir.rtype, fd.fd, -1)) :: redirs.r; 532 nlist = ref Listnode(nil, "/fd/"+string fd.fd) :: nil; 533 }else{ 534 redirs.r = Redirword(nil, (hd wlist).word, *n.redir) :: redirs.r; 535 } 536 n_DUP => 537 redirs.r = Redirword(nil, "", *n.redir) :: redirs.r; 538 n_LIST => 539 nlist = glom(ctxt, n.left, redirs, nil); 540 n_CONCAT => 541 nlist = concat(ctxt, glom(ctxt, n.left, redirs, nil), glom(ctxt, n.right, redirs, nil)); 542 n_VAR or n_SQUASH or n_COUNT => 543 arg := glom(ctxt, n.left, ref Redirlist(nil), nil); 544 if (len arg == 1 && (hd arg).cmd != nil) 545 nlist = subsbuiltin(ctxt, (hd arg).cmd.left); 546 else if (len arg != 1 || (hd arg).word == nil) 547 ctxt.fail("bad $ arg", "sh: bad variable name"); 548 else 549 nlist = ctxt.get(deglob((hd arg).word)); 550 case n.ntype { 551 n_VAR =>; 552 n_COUNT => 553 nlist = ref Listnode(nil, string len nlist) :: nil; 554 n_SQUASH => 555 # XXX could squash with first char of $ifs, perhaps 556 nlist = ref Listnode(nil, squash(list2stringlist(nlist), " ")) :: nil; 557 } 558 n_BQ or n_BQ2 => 559 arg := glom(ctxt, n.left, ref Redirlist(nil), nil); 560 seps := ""; 561 if (n.ntype == n_BQ) { 562 seps = squash(list2stringlist(ctxt.get("ifs")), ""); 563 if (seps == nil) 564 seps = " \t\n\r"; 565 } 566 (nlist, nil) = bq(ctxt, glob(arg), seps); 567 n_BLOCK => 568 nlist = ref Listnode(n, "") :: nil; 569 n_ASSIGN or n_LOCAL => 570 ctxt.fail("bad assign", "sh: assignment in invalid context"); 571 * => 572 panic("bad node type "+string n.ntype+" in glomop"); 573 } 574 return nlist; 575} 576 577subsbuiltin(ctxt: ref Context, n: ref Node): list of ref Listnode 578{ 579 if (n == nil || n.ntype == n_SEQ || 580 n.ntype == n_PIPE || n.ntype == n_NOWAIT) 581 ctxt.fail("bad $ arg", "sh: invalid argument to ${} operator"); 582 r := ref Redirlist; 583 cmd := glob(glom(ctxt, n, r, nil)); 584 if (r.r != nil) 585 ctxt.fail("bad $ arg", "sh: redirection not allowed in substitution"); 586 r = nil; 587 if (cmd == nil || (hd cmd).word == nil || (hd cmd).cmd != nil) 588 ctxt.fail("bad $ arg", "sh: bad builtin name"); 589 590 (nil, bmods) := findbuiltin(ctxt.env.sbuiltins, (hd cmd).word); 591 if (bmods == nil) 592 ctxt.fail("builtin not found", 593 sys->sprint("sh: builtin %s not found", (hd cmd).word)); 594 return (hd bmods)->runsbuiltin(ctxt, myself, cmd); 595} 596 597 598getbq(nil: ref Context, fd: ref Sys->FD, seps: string): list of ref Listnode 599{ 600 buf := array[Sys->ATOMICIO] of byte; 601 buflen := 0; 602 while ((n := sys->read(fd, buf[buflen:], len buf - buflen)) > 0) { 603 buflen += n; 604 if (buflen == len buf) { 605 nbuf := array[buflen * 2] of byte; 606 nbuf[0:] = buf[0:]; 607 buf = nbuf; 608 } 609 } 610 l: list of string; 611 if (seps != nil) 612 (nil, l) = sys->tokenize(string buf[0:buflen], seps); 613 else 614 l = string buf[0:buflen] :: nil; 615 buf = nil; 616 return stringlist2list(l); 617} 618 619bq(ctxt: ref Context, cmd: list of ref Listnode, seps: string): (list of ref Listnode, string) 620{ 621 fds := array[2] of ref Sys->FD; 622 if (sys->pipe(fds) == -1) 623 ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r")); 624 625 r := rdir(fds[1]); 626 fds[1] = nil; 627 startchan := chan of (int, ref Expropagate); 628 spawn runasync(ctxt, 0, cmd, r, startchan); 629 (exepid, exprop) := <-startchan; 630 r = nil; 631 bqlist := getbq(ctxt, fds[0], seps); 632 waitfor(ctxt, exepid :: nil); 633 if (exprop.name != nil) 634 raise exprop.name; 635 return (bqlist, nil); 636} 637 638rdir(fd: ref Sys->FD): ref Redirlist 639{ 640 return ref Redirlist(Redirword(fd, nil, Redir(Sys->OWRITE, 1, -1)) :: nil); 641} 642 643 644concatwords(p1, p2: ref Listnode): ref Listnode 645{ 646 if (p1.word == nil && p1.cmd != nil) 647 p1.word = cmd2string(p1.cmd); 648 if (p2.word == nil && p2.cmd != nil) 649 p2.word = cmd2string(p2.cmd); 650 return ref Listnode(nil, p1.word + p2.word); 651} 652 653concat(ctxt: ref Context, nl1, nl2: list of ref Listnode): list of ref Listnode 654{ 655 if (nl1 == nil || nl2 == nil) { 656 if (nl1 == nil && nl2 == nil) 657 return nil; 658 ctxt.fail("bad concatenation", "sh: null list in concatenation"); 659 } 660 661 ret: list of ref Listnode; 662 if (tl nl1 == nil || tl nl2 == nil) { 663 for (p1 := nl1; p1 != nil; p1 = tl p1) 664 for (p2 := nl2; p2 != nil; p2 = tl p2) 665 ret = concatwords(hd p1, hd p2) :: ret; 666 } else { 667 if (len nl1 != len nl2) 668 ctxt.fail("bad concatenation", "sh: lists of differing sizes can't be concatenated"); 669 while (nl1 != nil) { 670 ret = concatwords(hd nl1, hd nl2) :: ret; 671 (nl1, nl2) = (tl nl1, tl nl2); 672 } 673 } 674 return revlist(ret); 675} 676 677Expropagate: adt { 678 name: string; 679}; 680 681runasync(ctxt: ref Context, copyenv: int, argv: list of ref Listnode, redirs: ref Redirlist, 682 startchan: chan of (int, ref Expropagate)) 683{ 684 status: string; 685 686 pid := sys->pctl(sys->FORKFD, nil); 687 if (DEBUG) debug(sprint("in async (len redirs: %d)", len redirs.r)); 688 ctxt = ctxt.copy(copyenv); 689 exprop := ref Expropagate; 690 { 691 newfdl := doredirs(ctxt, redirs); 692 redirs = nil; 693 if (newfdl != nil) 694 sys->pctl(Sys->NEWFD, newfdl); 695 # stop the old waitfd from holding the intermediate 696 # file descriptor group open. 697 ctxt.waitfd = waitfd(); 698 # N.B. it's important that the sync is done here, not 699 # before doredirs, as otherwise there's some sort of 700 # race condition that leads to pipe non-completion. 701 startchan <-= (pid, exprop); 702 startchan = nil; 703 status = ctxt.run(argv, copyenv); 704 } exception e { 705 "fail:*" => 706 exprop.name = e; 707 if (startchan != nil) 708 startchan <-= (pid, exprop); 709 raise e; 710 } 711 if (status != nil) { 712 # don't propagate bad status as an exception. 713 raise "fail:" + status; 714 } 715} 716 717runsync(ctxt: ref Context, argv: list of ref Listnode, 718 redirs: ref Redirlist, last: int): string 719{ 720 if (DEBUG) debug(sys->sprint("in sync (len redirs: %d; last: %d)", len redirs.r, last)); 721 if (redirs.r != nil && !last) { 722 # a new process is required to shield redirection side effects 723 startchan := chan of (int, ref Expropagate); 724 spawn runasync(ctxt, 0, argv, redirs, startchan); 725 (pid, exprop) := <-startchan; 726 redirs = nil; 727 r := waitfor(ctxt, pid :: nil); 728 if (exprop.name != nil) 729 raise exprop.name; 730 return r; 731 } else { 732 newfdl := doredirs(ctxt, redirs); 733 redirs = nil; 734 if (newfdl != nil) 735 sys->pctl(Sys->NEWFD, newfdl); 736 return ctxt.run(argv, last); 737 } 738} 739 740absolute(p: string): int 741{ 742 if (len p < 2) 743 return 0; 744 if (p[0] == '/' || p[0] == '#') 745 return 1; 746 if (len p < 3 || p[0] != '.') 747 return 0; 748 if (p[1] == '/') 749 return 1; 750 if (p[1] == '.' && p[2] == '/') 751 return 1; 752 return 0; 753} 754 755runexternal(ctxt: ref Context, args: list of ref Listnode, last: int): string 756{ 757 progname := (hd args).word; 758 disfile := 0; 759 if (len progname >= 4 && progname[len progname-4:] == ".dis") 760 disfile = 1; 761 pathlist: list of string; 762 if (absolute(progname)) 763 pathlist = list of {""}; 764 else if ((pl := ctxt.get("path")) != nil) 765 pathlist = list2stringlist(pl); 766 else 767 pathlist = list of {"/dis", "."}; 768 769 err := ""; 770 do { 771 path: string; 772 if (hd pathlist != "") 773 path = hd pathlist + "/" + progname; 774 else 775 path = progname; 776 777 npath := path; 778 if (!disfile) 779 npath += ".dis"; 780 mod := load Command npath; 781 if (mod != nil) { 782 argv := list2stringlist(args); 783 export(ctxt.env.localenv); 784 785 if (last) { 786 { 787 sys->pctl(Sys->NEWFD, ctxt.keepfds); 788 mod->init(ctxt.drawcontext, argv); 789 exit; 790 } exception e { 791 EPIPE => 792 return EPIPE; 793 "fail:*" => 794 return failurestatus(e); 795 } 796 } 797 extstart := chan of int; 798 spawn externalexec(mod, ctxt.drawcontext, argv, extstart, ctxt.keepfds); 799 pid := <-extstart; 800 if (DEBUG) debug("started external externalexec; pid is "+string pid); 801 return waitfor(ctxt, pid :: nil); 802 } 803 err = sys->sprint("%r"); 804 if (nonexistent(err)) { 805 # try and run it as a shell script 806 if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) { 807 (ok, info) := sys->fstat(fd); 808 # make permission checking more accurate later 809 if (ok == 0 && (info.mode & Sys->DMDIR) == 0 810 && (info.mode & 8r111) != 0) 811 return runhashpling(ctxt, fd, path, tl args, last); 812 }; 813 err = sys->sprint("%r"); 814 } 815 pathlist = tl pathlist; 816 } while (pathlist != nil && nonexistent(err)); 817 diagnostic(ctxt, sys->sprint("%s: %s", progname, err)); 818 return err; 819} 820 821failurestatus(e: string): string 822{ 823 s := e[5:]; 824 while(s != nil && (s[0] == ' ' || s[0] == '\t')) 825 s = s[1:]; 826 if(s != nil) 827 return s; 828 return "failed"; 829} 830 831runhashpling(ctxt: ref Context, fd: ref Sys->FD, 832 path: string, argv: list of ref Listnode, last: int): string 833{ 834 header := array[1024] of byte; 835 n := sys->read(fd, header, len header); 836 for (i := 0; i < n; i++) 837 if (header[i] == byte '\n') 838 break; 839 if (i == n || i < 3 || header[0] != byte('#') || header[1] != byte('!')) { 840 diagnostic(ctxt, "bad script header on " + path); 841 return "bad header"; 842 } 843 (nil, args) := sys->tokenize(string header[2:i], " \t"); 844 if (args == nil) { 845 diagnostic(ctxt, "empty header on " + path); 846 return "bad header"; 847 } 848 header = nil; 849 fd = nil; 850 nargs: list of ref Listnode; 851 for (; args != nil; args = tl args) 852 nargs = ref Listnode(nil, hd args) :: nargs; 853 nargs = ref Listnode(nil, path) :: nargs; 854 for (; argv != nil; argv = tl argv) 855 nargs = hd argv :: nargs; 856 return runexternal(ctxt, revlist(nargs), last); 857} 858 859runblock(ctxt: ref Context, args: list of ref Listnode, last: int): string 860{ 861 # block execute (we know that hd args represents a block) 862 cmd := (hd args).cmd; 863 if (cmd == nil) { 864 # parse block from first argument 865 lex := YYLEX.initstring((hd args).word); 866 867 err: string; 868 (cmd, err) = doparse(lex, "", 0); 869 if (cmd == nil) 870 ctxt.fail("parse error", "sh: "+err); 871 872 (hd args).cmd = cmd; 873 } 874 # now we've got a parsed block 875 ctxt.push(); 876 { 877 ctxt.setlocal("0", hd args :: nil); 878 ctxt.setlocal("*", tl args); 879 if (cmd != nil && cmd.ntype == n_BLOCK) 880 cmd = cmd.left; 881 status := walk(ctxt, cmd, last); 882 ctxt.pop(); 883 return status; 884 } exception { 885 "fail:*" => 886 ctxt.pop(); 887 raise; 888 } 889} 890 891trybuiltin(ctxt: ref Context, args: list of ref Listnode, lseq: int) 892 : (int, string) 893{ 894 (nil, bmods) := findbuiltin(ctxt.env.builtins, (hd args).word); 895 if (bmods == nil) 896 return (0, nil); 897 return (1, (hd bmods)->runbuiltin(ctxt, myself, args, lseq)); 898} 899 900keepfdstr(ctxt: ref Context): string 901{ 902 s := ""; 903 for (f := ctxt.keepfds; f != nil; f = tl f) { 904 s += string hd f; 905 if (tl f != nil) 906 s += ","; 907 } 908 return s; 909} 910 911externalexec(mod: Command, 912 drawcontext: ref Draw->Context, argv: list of string, startchan: chan of int, keepfds: list of int) 913{ 914 if (DEBUG) debug(sprint("externalexec(%s,... [%d args])", hd argv, len argv)); 915 sys->pctl(Sys->NEWFD, keepfds); 916 startchan <-= sys->pctl(0, nil); 917 { 918 mod->init(drawcontext, argv); 919 } 920 exception { 921 EPIPE => 922 raise "fail:" + EPIPE; 923 } 924} 925 926dup(ctxt: ref Context, fd1, fd2: int): int 927{ 928 # shuffle waitfd out of the way if it's being attacked 929 if (ctxt.waitfd.fd == fd2) { 930 ctxt.waitfd = waitfd(); 931 if (ctxt.waitfd.fd == fd2) 932 panic(sys->sprint("reopen of waitfd gave same fd (%d)", ctxt.waitfd.fd)); 933 } 934 return sys->dup(fd1, fd2); 935} 936 937doredirs(ctxt: ref Context, redirs: ref Redirlist): list of int 938{ 939 if (redirs.r == nil) 940 return nil; 941 keepfds := ctxt.keepfds; 942 rl := redirs.r; 943 redirs = nil; 944 for (; rl != nil; rl = tl rl) { 945 (rfd, path, (mode, fd1, fd2)) := hd rl; 946 if (path == nil && rfd == nil) { 947 # dup 948 if (fd1 == -1 || fd2 == -1) 949 ctxt.fail("bad redir", "sh: invalid dup"); 950 951 if (dup(ctxt, fd2, fd1) == -1) 952 ctxt.fail("bad redir", sys->sprint("sh: cannot dup: %r")); 953 keepfds = fd1 :: keepfds; 954 continue; 955 } 956 # redir 957 if (fd1 == -1) { 958 if ((mode & OMASK) == Sys->OWRITE) 959 fd1 = 1; 960 else 961 fd1 = 0; 962 } 963 if (rfd == nil) { 964 (append, omode) := (mode & OAPPEND, mode & ~OAPPEND); 965 err := ""; 966 case mode { 967 Sys->OREAD => 968 rfd = sys->open(path, omode); 969 Sys->OWRITE | OAPPEND or 970 Sys->ORDWR => 971 rfd = sys->open(path, omode); 972 err = sprint("%r"); 973 if (rfd == nil && nonexistent(err)) { 974 rfd = sys->create(path, omode, 8r666); 975 err = nil; 976 } 977 Sys->OWRITE => 978 rfd = sys->create(path, omode, 8r666); 979 err = sprint("%r"); 980 if (rfd == nil && err == EPERM) { 981 # try open; can't create on a file2chan (pipe) 982 rfd = sys->open(path, omode); 983 nerr := sprint("%r"); 984 if(!nonexistent(nerr)) 985 err = nerr; 986 } 987 } 988 if (rfd == nil) { 989 if (err == nil) 990 err = sprint("%r"); 991 ctxt.fail("bad redir", sys->sprint("sh: cannot open %s: %s", path, err)); 992 } 993 if (append) 994 sys->seek(rfd, big 0, Sys->SEEKEND); # not good enough, but alright for some purposes. 995 } 996 # XXX what happens if rfd.fd == fd1? 997 # it probably gets closed automatically... which is not what we want! 998 dup(ctxt, rfd.fd, fd1); 999 keepfds = fd1 :: keepfds; 1000 } 1001 ctxt.keepfds = keepfds; 1002 return ctxt.waitfd.fd :: keepfds; 1003} 1004 1005 1006waitfd(): ref Sys->FD 1007{ 1008 wf := string sys->pctl(0, nil) + "/wait"; 1009 waitfd := sys->open("#p/"+wf, Sys->OREAD); 1010 if (waitfd == nil) 1011 waitfd = sys->open("/prog/"+wf, Sys->OREAD); 1012 if (waitfd == nil) 1013 panic(sys->sprint("cannot open wait file: %r")); 1014 return waitfd; 1015} 1016 1017waitfor(ctxt: ref Context, pids: list of int): string 1018{ 1019 if (pids == nil) 1020 return nil; 1021 status := array[len pids] of string; 1022 wcount := len status; 1023 buf := array[Sys->WAITLEN] of byte; 1024 onebad := 0; 1025 for(;;){ 1026 n := sys->read(ctxt.waitfd, buf, len buf); 1027 if(n < 0) 1028 panic(sys->sprint("error on wait read: %r")); 1029 (who, line, s) := parsewaitstatus(ctxt, string buf[0:n]); 1030 if (s != nil) { 1031 if (len s >= 5 && s[0:5] == "fail:") 1032 s = failurestatus(s); 1033 else 1034 diagnostic(ctxt, line); 1035 } 1036 for ((i, pl) := (0, pids); pl != nil; (i, pl) = (i+1, tl pl)) 1037 if (who == hd pl) 1038 break; 1039 if (i < len status) { 1040 # wait returns two records for a killed process... 1041 if (status[i] == nil || s != "killed") { 1042 onebad += s != nil; 1043 status[i] = s; 1044 if (wcount-- <= 1) 1045 break; 1046 } 1047 } 1048 } 1049 if (!onebad) 1050 return nil; 1051 r := status[len status - 1]; 1052 for (i := len status - 2; i >= 0; i--) 1053 r += "|" + status[i]; 1054 return r; 1055} 1056 1057parsewaitstatus(ctxt: ref Context, status: string): (int, string, string) 1058{ 1059 for (i := 0; i < len status; i++) 1060 if (status[i] == ' ') 1061 break; 1062 if (i == len status - 1 || status[i+1] != '"') 1063 ctxt.fail("bad wait read", 1064 sys->sprint("sh: bad exit status '%s'", status)); 1065 1066 for (i+=2; i < len status; i++) 1067 if (status[i] == '"') 1068 break; 1069 if (i > len status - 2 || status[i+1] != ':') 1070 ctxt.fail("bad wait read", 1071 sys->sprint("sh: bad exit status '%s'", status)); 1072 1073 return (int status, status, status[i+2:]); 1074} 1075 1076panic(s: string) 1077{ 1078 sys->fprint(stderr(), "sh panic: %s\n", s); 1079 raise "panic"; 1080} 1081 1082diagnostic(ctxt: ref Context, s: string) 1083{ 1084 if (ctxt.options() & Context.VERBOSE) 1085 sys->fprint(stderr(), "sh: %s\n", s); 1086} 1087 1088 1089Context.new(drawcontext: ref Draw->Context): ref Context 1090{ 1091 initialise(); 1092 if (env != nil) 1093 env->clone(); 1094 ctxt := ref Context( 1095 ref Environment( 1096 ref Builtins(nil, 0), 1097 ref Builtins(nil, 0), 1098 nil, 1099 newlocalenv(nil) 1100 ), 1101 waitfd(), 1102 drawcontext, 1103 0 :: 1 :: 2 :: nil 1104 ); 1105 myselfbuiltin->initbuiltin(ctxt, myself); 1106 ctxt.env.localenv.flags = ctxt.VERBOSE; 1107 for (vl := ctxt.get("autoload"); vl != nil; vl = tl vl) 1108 if ((hd vl).cmd == nil && (hd vl).word != nil) 1109 loadmodule(ctxt, (hd vl).word); 1110 return ctxt; 1111} 1112 1113Context.copy(ctxt: self ref Context, copyenv: int): ref Context 1114{ 1115 # XXX could check to see that we are definitely in a 1116 # new process, because there'll be problems if not (two processes 1117 # simultaneously reading the same wait file) 1118 nctxt := ref Context(ctxt.env, waitfd(), ctxt.drawcontext, ctxt.keepfds); 1119 1120 if (copyenv) { 1121 if (env != nil) 1122 env->clone(); 1123 nctxt.env = ref Environment( 1124 copybuiltins(ctxt.env.sbuiltins), 1125 copybuiltins(ctxt.env.builtins), 1126 ctxt.env.bmods, 1127 copylocalenv(ctxt.env.localenv) 1128 ); 1129 } 1130 return nctxt; 1131} 1132 1133Context.set(ctxt: self ref Context, name: string, val: list of ref Listnode) 1134{ 1135 e := ctxt.env.localenv; 1136 idx := hashfn(name, len e.vars); 1137 for (;;) { 1138 v := hashfind(e.vars, idx, name); 1139 if (v == nil) { 1140 if (e.pushed == nil) { 1141 flags := Var.CHANGED; 1142 if (noexport(name)) 1143 flags |= Var.NOEXPORT; 1144 hashadd(e.vars, idx, ref Var(name, val, flags)); 1145 return; 1146 } 1147 } else { 1148 v.val = val; 1149 v.flags |= Var.CHANGED; 1150 return; 1151 } 1152 e = e.pushed; 1153 } 1154} 1155 1156Context.get(ctxt: self ref Context, name: string): list of ref Listnode 1157{ 1158 if (name == nil) 1159 return nil; 1160 1161 idx := -1; 1162 # cope with $1, $2, etc 1163 if (name[0] > '0' && name[0] <= '9') { 1164 i: int; 1165 for (i = 0; i < len name; i++) 1166 if (name[i] < '0' || name[i] > '9') 1167 break; 1168 if (i >= len name) { 1169 idx = int name - 1; 1170 name = "*"; 1171 } 1172 } 1173 1174 v := varfind(ctxt.env.localenv, name); 1175 if (v != nil) { 1176 if (idx != -1) 1177 return index(v.val, idx); 1178 return v.val; 1179 } 1180 return nil; 1181} 1182 1183Context.envlist(ctxt: self ref Context): list of (string, list of ref Listnode) 1184{ 1185 t := array[ENVHASHSIZE] of list of ref Var; 1186 for (e := ctxt.env.localenv; e != nil; e = e.pushed) { 1187 for (i := 0; i < len e.vars; i++) { 1188 for (vl := e.vars[i]; vl != nil; vl = tl vl) { 1189 v := hd vl; 1190 idx := hashfn(v.name, len e.vars); 1191 if (hashfind(t, idx, v.name) == nil) 1192 hashadd(t, idx, v); 1193 } 1194 } 1195 } 1196 1197 l: list of (string, list of ref Listnode); 1198 for (i := 0; i < ENVHASHSIZE; i++) { 1199 for (vl := t[i]; vl != nil; vl = tl vl) { 1200 v := hd vl; 1201 l = (v.name, v.val) :: l; 1202 } 1203 } 1204 return l; 1205} 1206 1207Context.setlocal(ctxt: self ref Context, name: string, val: list of ref Listnode) 1208{ 1209 e := ctxt.env.localenv; 1210 idx := hashfn(name, len e.vars); 1211 v := hashfind(e.vars, idx, name); 1212 if (v == nil) { 1213 flags := Var.CHANGED; 1214 if (noexport(name)) 1215 flags |= Var.NOEXPORT; 1216 hashadd(e.vars, idx, ref Var(name, val, flags)); 1217 } else { 1218 v.val = val; 1219 v.flags |= Var.CHANGED; 1220 } 1221} 1222 1223 1224Context.push(ctxt: self ref Context) 1225{ 1226 ctxt.env.localenv = newlocalenv(ctxt.env.localenv); 1227} 1228 1229Context.pop(ctxt: self ref Context) 1230{ 1231 if (ctxt.env.localenv.pushed == nil) 1232 panic("unbalanced contexts in shell environment"); 1233 else { 1234 oldv := ctxt.env.localenv.vars; 1235 ctxt.env.localenv = ctxt.env.localenv.pushed; 1236 for (i := 0; i < len oldv; i++) { 1237 for (vl := oldv[i]; vl != nil; vl = tl vl) { 1238 if ((v := varfind(ctxt.env.localenv, (hd vl).name)) != nil) 1239 v.flags |= Var.CHANGED; 1240 else 1241 ctxt.set((hd vl).name, nil); 1242 } 1243 } 1244 } 1245} 1246 1247Context.run(ctxt: self ref Context, args: list of ref Listnode, last: int): string 1248{ 1249 if (args == nil || ((hd args).cmd == nil && (hd args).word == nil)) 1250 return nil; 1251 cmd := hd args; 1252 if (cmd.cmd != nil || cmd.word[0] == '{') # } 1253 return runblock(ctxt, args, last); 1254 1255 if (ctxt.options() & ctxt.EXECPRINT) 1256 sys->fprint(stderr(), "%s\n", quoted(args, 0)); 1257 (doneit, status) := trybuiltin(ctxt, args, last); 1258 if (!doneit) 1259 status = runexternal(ctxt, args, last); 1260 1261 return status; 1262} 1263 1264Context.addmodule(ctxt: self ref Context, name: string, mod: Shellbuiltin) 1265{ 1266 mod->initbuiltin(ctxt, myself); 1267 ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods; 1268} 1269 1270Context.addbuiltin(c: self ref Context, name: string, mod: Shellbuiltin) 1271{ 1272 addbuiltin(c.env.builtins, name, mod); 1273} 1274 1275Context.removebuiltin(c: self ref Context, name: string, mod: Shellbuiltin) 1276{ 1277 removebuiltin(c.env.builtins, name, mod); 1278} 1279 1280Context.addsbuiltin(c: self ref Context, name: string, mod: Shellbuiltin) 1281{ 1282 addbuiltin(c.env.sbuiltins, name, mod); 1283} 1284 1285Context.removesbuiltin(c: self ref Context, name: string, mod: Shellbuiltin) 1286{ 1287 removebuiltin(c.env.sbuiltins, name, mod); 1288} 1289 1290varfind(e: ref Localenv, name: string): ref Var 1291{ 1292 idx := hashfn(name, len e.vars); 1293 for (; e != nil; e = e.pushed) 1294 for (vl := e.vars[idx]; vl != nil; vl = tl vl) 1295 if ((hd vl).name == name) 1296 return hd vl; 1297 return nil; 1298} 1299 1300Context.fail(ctxt: self ref Context, ename: string, err: string) 1301{ 1302 if (ctxt.options() & Context.VERBOSE) 1303 sys->fprint(stderr(), "%s\n", err); 1304 raise "fail:" + ename; 1305} 1306 1307Context.setoptions(ctxt: self ref Context, flags, on: int): int 1308{ 1309 old := ctxt.env.localenv.flags; 1310 if (on) 1311 ctxt.env.localenv.flags |= flags; 1312 else 1313 ctxt.env.localenv.flags &= ~flags; 1314 return old; 1315} 1316 1317Context.options(ctxt: self ref Context): int 1318{ 1319 return ctxt.env.localenv.flags; 1320} 1321 1322hashfn(s: string, n: int): int 1323{ 1324 h := 0; 1325 m := len s; 1326 for(i:=0; i<m; i++){ 1327 h = 65599*h+s[i]; 1328 } 1329 return (h & 16r7fffffff) % n; 1330} 1331 1332hashfind(ht: array of list of ref Var, idx: int, n: string): ref Var 1333{ 1334 for (ent := ht[idx]; ent != nil; ent = tl ent) 1335 if ((hd ent).name == n) 1336 return hd ent; 1337 return nil; 1338} 1339 1340hashadd(ht: array of list of ref Var, idx: int, v: ref Var) 1341{ 1342 ht[idx] = v :: ht[idx]; 1343} 1344 1345copylocalenv(e: ref Localenv): ref Localenv 1346{ 1347 nvars := array[len e.vars] of list of ref Var; 1348 flags := e.flags; 1349 for (; e != nil; e = e.pushed) 1350 for (i := 0; i < len nvars; i++) 1351 for (vl := e.vars[i]; vl != nil; vl = tl vl) { 1352 idx := hashfn((hd vl).name, len nvars); 1353 if (hashfind(nvars, idx, (hd vl).name) == nil) 1354 hashadd(nvars, idx, ref *(hd vl)); 1355 } 1356 return ref Localenv(nvars, nil, flags); 1357} 1358 1359newlocalenv(pushed: ref Localenv): ref Localenv 1360{ 1361 e := ref Localenv(array[ENVHASHSIZE] of list of ref Var, pushed, 0); 1362 if (pushed == nil && env != nil) { 1363 for (vl := env->getall(); vl != nil; vl = tl vl) { 1364 (name, val) := hd vl; 1365 hashadd(e.vars, hashfn(name, len e.vars), ref Var(name, envstringtoval(val), 0)); 1366 } 1367 } 1368 if (pushed != nil) 1369 e.flags = pushed.flags; 1370 return e; 1371} 1372 1373copybuiltins(b: ref Builtins): ref Builtins 1374{ 1375 nb := ref Builtins(array[b.n] of (string, list of Shellbuiltin), b.n); 1376 nb.ba[0:] = b.ba[0:b.n]; 1377 return nb; 1378} 1379 1380findbuiltin(b: ref Builtins, name: string): (int, list of Shellbuiltin) 1381{ 1382 lo := 0; 1383 hi := b.n - 1; 1384 while (lo <= hi) { 1385 mid := (lo + hi) / 2; 1386 (bname, bmod) := b.ba[mid]; 1387 if (name < bname) 1388 hi = mid - 1; 1389 else if (name > bname) 1390 lo = mid + 1; 1391 else 1392 return (mid, bmod); 1393 } 1394 return (lo, nil); 1395} 1396 1397removebuiltin(b: ref Builtins, name: string, mod: Shellbuiltin) 1398{ 1399 (n, bmods) := findbuiltin(b, name); 1400 if (bmods == nil) 1401 return; 1402 if (hd bmods == mod) { 1403 if (tl bmods != nil) 1404 b.ba[n] = (name, tl bmods); 1405 else { 1406 b.ba[n:] = b.ba[n+1:b.n]; 1407 b.ba[--b.n] = (nil, nil); 1408 } 1409 } 1410} 1411 1412addbuiltin(b: ref Builtins, name: string, mod: Shellbuiltin) 1413{ 1414 if (mod == nil || (name == "builtin" && mod != myselfbuiltin)) 1415 return; 1416 (n, bmods) := findbuiltin(b, name); 1417 if (bmods != nil) { 1418 if (hd bmods == myselfbuiltin) 1419 b.ba[n] = (name, mod :: bmods); 1420 else 1421 b.ba[n] = (name, mod :: nil); 1422 } else { 1423 if (b.n == len b.ba) { 1424 nb := array[b.n + 10] of (string, list of Shellbuiltin); 1425 nb[0:] = b.ba[0:b.n]; 1426 b.ba = nb; 1427 } 1428 b.ba[n+1:] = b.ba[n:b.n]; 1429 b.ba[n] = (name, mod :: nil); 1430 b.n++; 1431 } 1432} 1433 1434removebuiltinmod(b: ref Builtins, mod: Shellbuiltin) 1435{ 1436 j := 0; 1437 for (i := 0; i < b.n; i++) { 1438 (name, bmods) := b.ba[i]; 1439 if (hd bmods == mod) 1440 bmods = tl bmods; 1441 if (bmods != nil) 1442 b.ba[j++] = (name, bmods); 1443 } 1444 b.n = j; 1445 for (; j < i; j++) 1446 b.ba[j] = (nil, nil); 1447} 1448 1449export(e: ref Localenv) 1450{ 1451 if (env == nil) 1452 return; 1453 if (e.pushed != nil) 1454 export(e.pushed); 1455 1456 for (i := 0; i < len e.vars; i++) { 1457 for (vl := e.vars[i]; vl != nil; vl = tl vl) { 1458 v := hd vl; 1459 # a bit inefficient: a local variable will get several putenvs. 1460 if ((v.flags & Var.CHANGED) && !(v.flags & Var.NOEXPORT)) { 1461 setenv(v.name, v.val); 1462 v.flags &= ~Var.CHANGED; 1463 } 1464 } 1465 } 1466} 1467 1468noexport(name: string): int 1469{ 1470 case name { 1471 "0" or "*" or "status" => return 1; 1472 } 1473 return 0; 1474} 1475 1476index(val: list of ref Listnode, k: int): list of ref Listnode 1477{ 1478 for (; k > 0 && val != nil; k--) 1479 val = tl val; 1480 if (val != nil) 1481 val = hd val :: nil; 1482 return val; 1483} 1484 1485getenv(name: string): list of ref Listnode 1486{ 1487 if (env == nil) 1488 return nil; 1489 return envstringtoval(env->getenv(name)); 1490} 1491 1492envstringtoval(v: string): list of ref Listnode 1493{ 1494 return stringlist2list(str->unquoted(v)); 1495} 1496 1497XXXenvstringtoval(v: string): list of ref Listnode 1498{ 1499 if (len v == 0) 1500 return nil; 1501 start := len v; 1502 val: list of ref Listnode; 1503 for (i := start - 1; i >= 0; i--) { 1504 if (v[i] == ENVSEP) { 1505 val = ref Listnode(nil, v[i+1:start]) :: val; 1506 start = i; 1507 } 1508 } 1509 return ref Listnode(nil, v[0:start]) :: val; 1510} 1511 1512setenv(name: string, val: list of ref Listnode) 1513{ 1514 if (env == nil) 1515 return; 1516 env->setenv(name, quoted(val, 1)); 1517} 1518 1519 1520containswildchar(s: string): int 1521{ 1522 # try and avoid being fooled by GLOB characters in quoted 1523 # text. we'll only be fooled if the GLOB char is followed 1524 # by a wildcard char, or another GLOB. 1525 for (i := 0; i < len s; i++) { 1526 if (s[i] == GLOB && i < len s - 1) { 1527 case s[i+1] { 1528 '*' or '[' or '?' or GLOB => 1529 return 1; 1530 } 1531 } 1532 } 1533 return 0; 1534} 1535 1536patquote(word: string): string 1537{ 1538 outword := ""; 1539 for (i := 0; i < len word; i++) { 1540 case word[i] { 1541 '[' or '*' or '?' or '\\' => 1542 outword[len outword] = '\\'; 1543 GLOB => 1544 i++; 1545 if (i >= len word) 1546 return outword; 1547 if(word[i] == '[' && i < len word - 1 && word[i+1] == '~') 1548 word[i+1] = '^'; 1549 } 1550 outword[len outword] = word[i]; 1551 } 1552 return outword; 1553} 1554 1555deglob(s: string): string 1556{ 1557 j := 0; 1558 for (i := 0; i < len s; i++) { 1559 if (s[i] != GLOB) { 1560 if (i != j) # a worthy optimisation??? 1561 s[j] = s[i]; 1562 j++; 1563 } 1564 } 1565 if (i == j) 1566 return s; 1567 return s[0:j]; 1568} 1569 1570glob(nl: list of ref Listnode): list of ref Listnode 1571{ 1572 new: list of ref Listnode; 1573 while (nl != nil) { 1574 n := hd nl; 1575 if (containswildchar(n.word)) { 1576 qword := patquote(n.word); 1577 files := filepat->expand(qword); 1578 if (files == nil) 1579 files = deglob(n.word) :: nil; 1580 while (files != nil) { 1581 new = ref Listnode(nil, hd files) :: new; 1582 files = tl files; 1583 } 1584 } else 1585 new = n :: new; 1586 nl = tl nl; 1587 } 1588 ret := revlist(new); 1589 return ret; 1590} 1591 1592 1593list2stringlist(nl: list of ref Listnode): list of string 1594{ 1595 ret: list of string = nil; 1596 1597 while (nl != nil) { 1598 newel: string; 1599 el := hd nl; 1600 if (el.word != nil || el.cmd == nil) 1601 newel = el.word; 1602 else 1603 el.word = newel = cmd2string(el.cmd); 1604 ret = newel::ret; 1605 nl = tl nl; 1606 } 1607 1608 sl := revstringlist(ret); 1609 return sl; 1610} 1611 1612stringlist2list(sl: list of string): list of ref Listnode 1613{ 1614 ret: list of ref Listnode; 1615 1616 while (sl != nil) { 1617 ret = ref Listnode(nil, hd sl) :: ret; 1618 sl = tl sl; 1619 } 1620 return revlist(ret); 1621} 1622 1623revstringlist(l: list of string): list of string 1624{ 1625 t: list of string; 1626 1627 while(l != nil) { 1628 t = hd l :: t; 1629 l = tl l; 1630 } 1631 return t; 1632} 1633 1634revlist(l: list of ref Listnode): list of ref Listnode 1635{ 1636 t: list of ref Listnode; 1637 1638 while(l != nil) { 1639 t = hd l :: t; 1640 l = tl l; 1641 } 1642 return t; 1643} 1644 1645 1646fdassignstr(isassign: int, redir: ref Redir): string 1647{ 1648 l: string = nil; 1649 if (redir.fd1 >= 0) 1650 l = string redir.fd1; 1651 1652 if (isassign) { 1653 r: string = nil; 1654 if (redir.fd2 >= 0) 1655 r = string redir.fd2; 1656 return "[" + l + "=" + r + "]"; 1657 } 1658 return "[" + l + "]"; 1659} 1660 1661redirstr(rtype: int): string 1662{ 1663 case rtype { 1664 * or 1665 Sys->OREAD => return "<"; 1666 Sys->OWRITE => return ">"; 1667 Sys->OWRITE|OAPPEND => return ">>"; 1668 Sys->ORDWR => return "<>"; 1669 } 1670} 1671 1672cmd2string(n: ref Node): string 1673{ 1674 if (n == nil) 1675 return ""; 1676 1677 s: string; 1678 case n.ntype { 1679 n_BLOCK => s = "{" + cmd2string(n.left) + "}"; 1680 n_VAR => s = "$" + cmd2string(n.left); 1681 # XXX can this ever occur? 1682 if (n.right != nil) 1683 s += "(" + cmd2string(n.right) + ")"; 1684 n_SQUASH => s = "$\"" + cmd2string(n.left); 1685 n_COUNT => s = "$#" + cmd2string(n.left); 1686 n_BQ => s = "`" + cmd2string(n.left); 1687 n_BQ2 => s = "\"" + cmd2string(n.left); 1688 n_REDIR => s = redirstr(n.redir.rtype); 1689 if (n.redir.fd1 != -1) 1690 s += fdassignstr(0, n.redir); 1691 s += cmd2string(n.left); 1692 n_DUP => s = redirstr(n.redir.rtype) + fdassignstr(1, n.redir); 1693 n_LIST => s = "(" + cmd2string(n.left) + ")"; 1694 n_SEQ => s = cmd2string(n.left) + ";" + cmd2string(n.right); 1695 n_NOWAIT => s = cmd2string(n.left) + "&"; 1696 n_CONCAT => s = cmd2string(n.left) + "^" + cmd2string(n.right); 1697 n_PIPE => s = cmd2string(n.left) + "|"; 1698 if (n.redir != nil && (n.redir.fd1 != -1 || n.redir.fd2 != -1)) 1699 s += fdassignstr(n.redir.fd2 != -1, n.redir); 1700 s += cmd2string(n.right); 1701 n_ASSIGN => s = cmd2string(n.left) + "=" + cmd2string(n.right); 1702 n_LOCAL => s = cmd2string(n.left) + ":=" + cmd2string(n.right); 1703 n_ADJ => s = cmd2string(n.left) + " " + cmd2string(n.right); 1704 n_WORD => s = quote(n.word, 1); 1705 * => s = sys->sprint("unknown%d", n.ntype); 1706 } 1707 return s; 1708} 1709 1710quote(s: string, glob: int): string 1711{ 1712 needquote := 0; 1713 t := ""; 1714 for (i := 0; i < len s; i++) { 1715 case s[i] { 1716 '{' or '}' or '(' or ')' or '`' or '&' or ';' or '=' or '>' or '<' or '#' or 1717 '|' or '*' or '[' or '?' or '$' or '^' or ' ' or '\t' or '\n' or '\r' => 1718 needquote = 1; 1719 '\'' => 1720 t[len t] = '\''; 1721 needquote = 1; 1722 GLOB => 1723 if (glob) { 1724 if (i < len s - 1) 1725 i++; 1726 } 1727 } 1728 t[len t] = s[i]; 1729 } 1730 if (needquote || t == nil) 1731 t = "'" + t + "'"; 1732 return t; 1733} 1734 1735squash(l: list of string, sep: string): string 1736{ 1737 if (l == nil) 1738 return nil; 1739 s := hd l; 1740 for (l = tl l; l != nil; l = tl l) 1741 s += sep + hd l; 1742 return s; 1743} 1744 1745debug(s: string) 1746{ 1747 if (DEBUG) sys->fprint(stderr(), "%s\n", string sys->pctl(0, nil) + ": " + s); 1748} 1749 1750 1751initbuiltin(c: ref Context, nil: Sh): string 1752{ 1753 names := array[] of {"load", "unload", "loaded", "builtin", "syncenv", "whatis", "run", "exit", "@"}; 1754 for (i := 0; i < len names; i++) 1755 c.addbuiltin(names[i], myselfbuiltin); 1756 c.addsbuiltin("loaded", myselfbuiltin); 1757 c.addsbuiltin("quote", myselfbuiltin); 1758 c.addsbuiltin("bquote", myselfbuiltin); 1759 c.addsbuiltin("unquote", myselfbuiltin); 1760 c.addsbuiltin("builtin", myselfbuiltin); 1761 return nil; 1762} 1763 1764whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string 1765{ 1766 return nil; 1767} 1768 1769runsbuiltin(ctxt: ref Context, nil: Sh, argv: list of ref Listnode): list of ref Listnode 1770{ 1771 case (hd argv).word { 1772 "loaded" => return sbuiltin_loaded(ctxt, argv); 1773 "bquote" => return sbuiltin_quote(ctxt, argv, 0); 1774 "quote" => return sbuiltin_quote(ctxt, argv, 1); 1775 "unquote" => return sbuiltin_unquote(ctxt, argv); 1776 "builtin" => return sbuiltin_builtin(ctxt, argv); 1777 } 1778 return nil; 1779} 1780 1781runbuiltin(ctxt: ref Context, nil: Sh, args: list of ref Listnode, lseq: int): string 1782{ 1783 status := ""; 1784 name := (hd args).word; 1785 case name { 1786 "load" => status = builtin_load(ctxt, args, lseq); 1787 "loaded" => status = builtin_loaded(ctxt, args, lseq); 1788 "unload" => status = builtin_unload(ctxt, args, lseq); 1789 "builtin" => status = builtin_builtin(ctxt, args, lseq); 1790 "whatis" => status = builtin_whatis(ctxt, args, lseq); 1791 "run" => status = builtin_run(ctxt, args, lseq); 1792 "exit" => status = builtin_exit(ctxt, args, lseq); 1793 "syncenv" => export(ctxt.env.localenv); 1794 "@" => status = builtin_subsh(ctxt, args, lseq); 1795 } 1796 return status; 1797} 1798 1799sbuiltin_loaded(ctxt: ref Context, nil: list of ref Listnode): list of ref Listnode 1800{ 1801 v: list of ref Listnode; 1802 for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) { 1803 (name, nil) := hd bl; 1804 v = ref Listnode(nil, name) :: v; 1805 } 1806 return v; 1807} 1808 1809sbuiltin_quote(nil: ref Context, argv: list of ref Listnode, quoteblocks: int): list of ref Listnode 1810{ 1811 return ref Listnode(nil, quoted(tl argv, quoteblocks)) :: nil; 1812} 1813 1814sbuiltin_builtin(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode 1815{ 1816 if (args == nil || tl args == nil) 1817 builtinusage(ctxt, "builtin command [args ...]"); 1818 name := (hd tl args).word; 1819 (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name); 1820 for (; mods != nil; mods = tl mods) 1821 if (hd mods == myselfbuiltin) 1822 return (hd mods)->runsbuiltin(ctxt, myself, tl args); 1823 ctxt.fail("builtin not found", sys->sprint("sh: builtin %s not found", name)); 1824 return nil; 1825} 1826 1827sbuiltin_unquote(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode 1828{ 1829 argv = tl argv; 1830 if (argv == nil || tl argv != nil) 1831 builtinusage(ctxt, "unquote arg"); 1832 1833 arg := (hd argv).word; 1834 if (arg == nil && (hd argv).cmd != nil) 1835 arg = cmd2string((hd argv).cmd); 1836 return stringlist2list(str->unquoted(arg)); 1837} 1838 1839getself(): Shellbuiltin 1840{ 1841 return myselfbuiltin; 1842} 1843 1844builtinusage(ctxt: ref Context, s: string) 1845{ 1846 ctxt.fail("usage", "sh: usage: " + s); 1847} 1848 1849builtin_exit(nil: ref Context, nil: list of ref Listnode, nil: int): string 1850{ 1851 # XXX using this primitive can cause 1852 # environment stack not to be popped properly. 1853 exit; 1854} 1855 1856builtin_subsh(ctxt: ref Context, args: list of ref Listnode, nil: int): string 1857{ 1858 if (tl args == nil) 1859 return nil; 1860 startchan := chan of (int, ref Expropagate); 1861 spawn runasync(ctxt, 0, tl args, ref Redirlist, startchan); 1862 (exepid, exprop) := <-startchan; 1863 status := waitfor(ctxt, exepid :: nil); 1864 if (exprop.name != nil) 1865 raise exprop.name; 1866 return status; 1867} 1868 1869builtin_loaded(ctxt: ref Context, nil: list of ref Listnode, nil: int): string 1870{ 1871 b := ctxt.env.builtins; 1872 for (i := 0; i < b.n; i++) { 1873 (name, bmods) := b.ba[i]; 1874 sys->print("%s\t%s\n", name, modname(ctxt, hd bmods)); 1875 } 1876 b = ctxt.env.sbuiltins; 1877 for (i = 0; i < b.n; i++) { 1878 (name, bmods) := b.ba[i]; 1879 sys->print("${%s}\t%s\n", name, modname(ctxt, hd bmods)); 1880 } 1881 return nil; 1882} 1883 1884builtin_load(ctxt: ref Context, args: list of ref Listnode, nil: int): string 1885{ 1886 if (tl args == nil || (hd tl args).word == nil) 1887 builtinusage(ctxt, "load path..."); 1888 args = tl args; 1889 if (args == nil) 1890 builtinusage(ctxt, "load path..."); 1891 for (; args != nil; args = tl args) { 1892 s := loadmodule(ctxt, (hd args).word); 1893 if (s != nil) 1894 raise "fail:" + s; 1895 } 1896 return nil; 1897} 1898 1899builtin_unload(ctxt: ref Context, args: list of ref Listnode, nil: int): string 1900{ 1901 if (tl args == nil) 1902 builtinusage(ctxt, "unload path..."); 1903 status := ""; 1904 for (args = tl args; args != nil; args = tl args) 1905 if ((s := unloadmodule(ctxt, (hd args).word)) != nil) 1906 status = s; 1907 return status; 1908} 1909 1910builtin_run(ctxt: ref Context, args: list of ref Listnode, nil: int): string 1911{ 1912 if (tl args == nil || (hd tl args).word == nil) 1913 builtinusage(ctxt, "run path"); 1914 ctxt.push(); 1915 { 1916 ctxt.setoptions(ctxt.INTERACTIVE, 0); 1917 runscript(ctxt, (hd tl args).word, tl tl args, 1); 1918 ctxt.pop(); 1919 return nil; 1920 } exception e { 1921 "fail:*" => 1922 ctxt.pop(); 1923 return failurestatus(e); 1924 } 1925} 1926 1927builtin_whatis(ctxt: ref Context, args: list of ref Listnode, nil: int): string 1928{ 1929 if (len args < 2) 1930 builtinusage(ctxt, "whatis name ..."); 1931 err := ""; 1932 for (args = tl args; args != nil; args = tl args) 1933 if ((e := whatisit(ctxt, hd args)) != nil) 1934 err = e; 1935 return err; 1936} 1937 1938whatisit(ctxt: ref Context, el: ref Listnode): string 1939{ 1940 if (el.cmd != nil) { 1941 sys->print("%s\n", cmd2string(el.cmd)); 1942 return nil; 1943 } 1944 found := 0; 1945 name := el.word; 1946 if (name != nil && name[0] == '{') { #} 1947 sys->print("%s\n", name); 1948 return nil;; 1949 } 1950 if (name == nil) 1951 return nil; # XXX questionable 1952 w: string; 1953 val := ctxt.get(name); 1954 if (val != nil) { 1955 found++; 1956 w += sys->sprint("%s=%s\n", quote(name, 0), quoted(val, 0)); 1957 } 1958 (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name); 1959 if (mods != nil) { 1960 mod := hd mods; 1961 if (mod == myselfbuiltin) 1962 w += "${builtin " + name + "}\n"; 1963 else { 1964 mw := mod->whatis(ctxt, myself, name, Shellbuiltin->SBUILTIN); 1965 if (mw == nil) 1966 mw = "${" + name + "}"; 1967 w += "load " + modname(ctxt, mod) + "; " + mw + "\n"; 1968 } 1969 found++; 1970 } 1971 (nil, mods) = findbuiltin(ctxt.env.builtins, name); 1972 if (mods != nil) { 1973 mod := hd mods; 1974 if (mod == myselfbuiltin) 1975 sys->print("builtin %s\n", name); 1976 else { 1977 mw := mod->whatis(ctxt, myself, name, Shellbuiltin->BUILTIN); 1978 if (mw == nil) 1979 mw = name; 1980 w += "load " + modname(ctxt, mod) + "; " + mw + "\n"; 1981 } 1982 found++; 1983 } else { 1984 disfile := 0; 1985 if (len name >= 4 && name[len name-4:] == ".dis") 1986 disfile = 1; 1987 pathlist: list of string; 1988 if (len name >= 2 && (name[0] == '/' || name[0:2] == "./")) 1989 pathlist = list of {""}; 1990 else if ((pl := ctxt.get("path")) != nil) 1991 pathlist = list2stringlist(pl); 1992 else 1993 pathlist = list of {"/dis", "."}; 1994 1995 foundpath := ""; 1996 while (pathlist != nil) { 1997 path: string; 1998 if (hd pathlist != "") 1999 path = hd pathlist + "/" + name; 2000 else 2001 path = name; 2002 if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) { 2003 if (executable(sys->fstat(fd), 8r111)) { 2004 foundpath = path; 2005 break; 2006 } 2007 } 2008 if (!disfile) 2009 path += ".dis"; 2010 if (executable(sys->stat(path), 8r444)) { 2011 foundpath = path; 2012 break; 2013 } 2014 pathlist = tl pathlist; 2015 } 2016 if (foundpath != nil) 2017 w += foundpath + "\n"; 2018 } 2019 for (bmods := ctxt.env.bmods; bmods != nil; bmods = tl bmods) { 2020 (modname, mod) := hd bmods; 2021 if ((mw := mod->whatis(ctxt, myself, name, Shellbuiltin->OTHER)) != nil) 2022 w += "load " + modname + "; " + mw + "\n"; 2023 } 2024 if (w == nil) { 2025 sys->fprint(stderr(), "%s: not found\n", name); 2026 return "not found"; 2027 } 2028 sys->print("%s", w); 2029 return nil; 2030} 2031 2032builtin_builtin(ctxt: ref Context, args: list of ref Listnode, last: int): string 2033{ 2034 if (len args < 2) 2035 builtinusage(ctxt, "builtin command [args ...]"); 2036 name := (hd tl args).word; 2037 if (name == nil || name[0] == '{') { 2038 diagnostic(ctxt, name + " not found"); 2039 return "not found"; 2040 } 2041 (nil, mods) := findbuiltin(ctxt.env.builtins, name); 2042 for (; mods != nil; mods = tl mods) 2043 if (hd mods == myselfbuiltin) 2044 return (hd mods)->runbuiltin(ctxt, myself, tl args, last); 2045 if (ctxt.options() & ctxt.EXECPRINT) 2046 sys->fprint(stderr(), "%s\n", quoted(tl args, 0)); 2047 return runexternal(ctxt, tl args, last); 2048} 2049 2050modname(ctxt: ref Context, mod: Shellbuiltin): string 2051{ 2052 for (ml := ctxt.env.bmods; ml != nil; ml = tl ml) { 2053 (bname, bmod) := hd ml; 2054 if (bmod == mod) 2055 return bname; 2056 } 2057 return "builtin"; 2058} 2059 2060loadmodule(ctxt: ref Context, name: string): string 2061{ 2062 # avoid loading the same module twice (it's convenient 2063 # to have load be a null-op if the module required is already loaded) 2064 for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) { 2065 (bname, nil) := hd bl; 2066 if (bname == name) 2067 return nil; 2068 } 2069 path := name; 2070 if (len path < 4 || path[len path-4:] != ".dis") 2071 path += ".dis"; 2072 if (path[0] != '/' && path[0:2] != "./") 2073 path = BUILTINPATH + "/" + path; 2074 mod := load Shellbuiltin path; 2075 if (mod == nil) { 2076 diagnostic(ctxt, sys->sprint("load: cannot load %s: %r", path)); 2077 return "bad module"; 2078 } 2079 s := mod->initbuiltin(ctxt, myself); 2080 ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods; 2081 if (s != nil) { 2082 unloadmodule(ctxt, name); 2083 diagnostic(ctxt, "load: module init failed: " + s); 2084 } 2085 return s; 2086} 2087 2088unloadmodule(ctxt: ref Context, name: string): string 2089{ 2090 bl: list of (string, Shellbuiltin); 2091 mod: Shellbuiltin; 2092 for (cl := ctxt.env.bmods; cl != nil; cl = tl cl) { 2093 (bname, bmod) := hd cl; 2094 if (bname == name) 2095 mod = bmod; 2096 else 2097 bl = hd cl :: bl; 2098 } 2099 if (mod == nil) { 2100 diagnostic(ctxt, sys->sprint("module %s not found", name)); 2101 return "not found"; 2102 } 2103 for (ctxt.env.bmods = nil; bl != nil; bl = tl bl) 2104 ctxt.env.bmods = hd bl :: ctxt.env.bmods; 2105 removebuiltinmod(ctxt.env.builtins, mod); 2106 removebuiltinmod(ctxt.env.sbuiltins, mod); 2107 return nil; 2108} 2109 2110executable(s: (int, Sys->Dir), mode: int): int 2111{ 2112 (ok, info) := s; 2113 return ok != -1 && (info.mode & Sys->DMDIR) == 0 2114 && (info.mode & mode) != 0; 2115} 2116 2117quoted(val: list of ref Listnode, quoteblocks: int): string 2118{ 2119 s := ""; 2120 for (; val != nil; val = tl val) { 2121 el := hd val; 2122 if (el.cmd == nil || (quoteblocks && el.word != nil)) 2123 s += quote(el.word, 0); 2124 else { 2125 cmd := cmd2string(el.cmd); 2126 if (quoteblocks) 2127 cmd = quote(cmd, 0); 2128 s += cmd; 2129 } 2130 if (tl val != nil) 2131 s[len s] = ' '; 2132 } 2133 return s; 2134} 2135 2136setstatus(ctxt: ref Context, val: string): string 2137{ 2138 ctxt.setlocal("status", ref Listnode(nil, val) :: nil); 2139 return val; 2140} 2141 2142 2143doparse(l: ref YYLEX, prompt: string, showline: int): (ref Node, string) 2144{ 2145 l.prompt = prompt; 2146 l.err = nil; 2147 l.lval.node = nil; 2148 yyparse(l); 2149 l.lastnl = 0; # don't print secondary prompt next time 2150 if (l.err != nil) { 2151 s: string; 2152 if (l.err == nil) 2153 l.err = "unknown error"; 2154 if (l.errline > 0 && showline) 2155 s = sys->sprint("%s:%d: %s", l.path, l.errline, l.err); 2156 else 2157 s = l.path + ": parse error: " + l.err; 2158 return (nil, s); 2159 } 2160 return (l.lval.node, nil); 2161} 2162 2163blanklex: YYLEX; # for hassle free zero initialisation 2164 2165YYLEX.initstring(s: string): ref YYLEX 2166{ 2167 ret := ref blanklex; 2168 ret.s = s; 2169 ret.path="internal"; 2170 ret.strpos = 0; 2171 return ret; 2172} 2173 2174YYLEX.initfile(fd: ref Sys->FD, path: string): ref YYLEX 2175{ 2176 lex := ref blanklex; 2177 lex.f = bufio->fopen(fd, bufio->OREAD); 2178 lex.path = path; 2179 lex.cbuf = array[2] of int; # number of characters of pushback 2180 lex.linenum = 1; 2181 lex.prompt = ""; 2182 return lex; 2183} 2184 2185YYLEX.error(l: self ref YYLEX, s: string) 2186{ 2187 if (l.err == nil) { 2188 l.err = s; 2189 l.errline = l.linenum; 2190 } 2191} 2192 2193NOTOKEN: con -1; 2194 2195YYLEX.lex(l: self ref YYLEX): int 2196{ 2197 # the following are allowed a free caret: 2198 # $, word and quoted word; 2199 # also, allowed chrs in unquoted word following dollar are [a-zA-Z0-9*_] 2200 endword := 0; 2201 wasdollar := 0; 2202 tok := NOTOKEN; 2203 while (tok == NOTOKEN) { 2204 case c := l.getc() { 2205 l.EOF => 2206 tok = END; 2207 '\n' => 2208 tok = '\n'; 2209 '\r' or '\t' or ' ' => 2210 ; 2211 '#' => 2212 while ((c = l.getc()) != '\n' && c != l.EOF) 2213 ; 2214 l.ungetc(); 2215 ';' => tok = ';'; 2216 '&' => 2217 c = l.getc(); 2218 if(c == '&') 2219 tok = ANDAND; 2220 else{ 2221 l.ungetc(); 2222 tok = '&'; 2223 } 2224 '^' => tok = '^'; 2225 '{' => tok = '{'; 2226 '}' => tok = '}'; 2227 ')' => tok = ')'; 2228 '(' => tok = '('; 2229 '=' => (tok, l.lval.optype) = ('=', n_ASSIGN); 2230 '$' => 2231 if (l.atendword) { 2232 l.ungetc(); 2233 tok = '^'; 2234 break; 2235 } 2236 case (c = l.getc()) { 2237 '#' => 2238 l.lval.optype = n_COUNT; 2239 '"' => 2240 l.lval.optype = n_SQUASH; 2241 * => 2242 l.ungetc(); 2243 l.lval.optype = n_VAR; 2244 } 2245 tok = OP; 2246 wasdollar = 1; 2247 '"' or '`'=> 2248 if (l.atendword) { 2249 tok = '^'; 2250 l.ungetc(); 2251 break; 2252 } 2253 tok = OP; 2254 if (c == '"') 2255 l.lval.optype = n_BQ2; 2256 else 2257 l.lval.optype = n_BQ; 2258 '>' or '<' => 2259 rtype: int; 2260 nc := l.getc(); 2261 if (nc == '>') { 2262 if (c == '>') 2263 rtype = Sys->OWRITE | OAPPEND; 2264 else 2265 rtype = Sys->ORDWR; 2266 nc = l.getc(); 2267 } else if (c == '>') 2268 rtype = Sys->OWRITE; 2269 else 2270 rtype = Sys->OREAD; 2271 tok = REDIR; 2272 if (nc == '[') { 2273 (tok, l.lval.redir) = readfdassign(l); 2274 if (tok == ERROR) 2275 (l.err, l.errline) = ("syntax error in redirection", l.linenum); 2276 } else { 2277 l.ungetc(); 2278 l.lval.redir = ref Redir(-1, -1, -1); 2279 } 2280 if (l.lval.redir != nil) 2281 l.lval.redir.rtype = rtype; 2282 '|' => 2283 tok = '|'; 2284 l.lval.redir = nil; 2285 if ((c = l.getc()) == '[') { 2286 (tok, l.lval.redir) = readfdassign(l); 2287 if (tok == ERROR) { 2288 (l.err, l.errline) = ("syntax error in pipe redirection", l.linenum); 2289 return tok; 2290 } 2291 tok = '|'; 2292 } else if(c == '|') 2293 tok = OROR; 2294 else 2295 l.ungetc(); 2296 2297 '\'' => 2298 if (l.atendword) { 2299 l.ungetc(); 2300 tok = '^'; 2301 break; 2302 } 2303 startline := l.linenum; 2304 s := ""; 2305 for(;;) { 2306 while ((nc := l.getc()) != '\'' && nc != l.EOF) 2307 s[len s] = nc; 2308 if (nc == l.EOF) { 2309 (l.err, l.errline) = ("unterminated string literal", startline); 2310 return ERROR; 2311 } 2312 if (l.getc() != '\'') { 2313 l.ungetc(); 2314 break; 2315 } 2316 s[len s] = '\''; # 'xxx''yyy' becomes WORD(xxx'yyy) 2317 } 2318 l.lval.word = s; 2319 tok = WORD; 2320 endword = 1; 2321 2322 * => 2323 if (c == ':') { 2324 if (l.getc() == '=') { 2325 tok = '='; 2326 l.lval.optype = n_LOCAL; 2327 break; 2328 } 2329 l.ungetc(); 2330 } 2331 if (l.atendword) { 2332 l.ungetc(); 2333 tok = '^'; 2334 break; 2335 } 2336 allowed: string; 2337 if (l.wasdollar) 2338 allowed = "a-zA-Z0-9*_"; 2339 else 2340 allowed = "^\n \t\r|$'#<>;^(){}`&=\""; 2341 word := ""; 2342 loop: do { 2343 case c { 2344 '*' or '?' or '[' or GLOB => 2345 word[len word] = GLOB; 2346 ':' => 2347 nc := l.getc(); 2348 l.ungetc(); 2349 if (nc == '=') 2350 break loop; 2351 } 2352 word[len word] = c; 2353 } while ((c = l.getc()) != l.EOF && str->in(c, allowed)); 2354 l.ungetc(); 2355 l.lval.word = word; 2356 tok = WORD; 2357 endword = 1; 2358 } 2359 l.atendword = endword; 2360 l.wasdollar = wasdollar; 2361 } 2362 return tok; 2363} 2364 2365tokstr(t: int): string 2366{ 2367 s: string; 2368 case t { 2369 '\n' => s = "'\\n'"; 2370 33 to 127 => s = sprint("'%c'", t); 2371 DUP=> s = "DUP"; 2372 REDIR =>s = "REDIR"; 2373 WORD => s = "WORD"; 2374 OP => s = "OP"; 2375 END => s = "END"; 2376 ERROR=> s = "ERROR"; 2377 * => 2378 s = "<unknowntok"+ string t + ">"; 2379 } 2380 return s; 2381} 2382 2383YYLEX.ungetc(lex: self ref YYLEX) 2384{ 2385 lex.strpos--; 2386 if (lex.f != nil) { 2387 lex.ncbuf++; 2388 if (lex.strpos < 0) 2389 lex.strpos = len lex.cbuf - 1; 2390 } 2391} 2392 2393YYLEX.getc(lex: self ref YYLEX): int 2394{ 2395 if (lex.eof) # EOF sticks 2396 return lex.EOF; 2397 c: int; 2398 if (lex.f != nil) { 2399 if (lex.ncbuf > 0) { 2400 c = lex.cbuf[lex.strpos++]; 2401 if (lex.strpos >= len lex.cbuf) 2402 lex.strpos = 0; 2403 lex.ncbuf--; 2404 } else { 2405 if (lex.lastnl && lex.prompt != nil) 2406 sys->fprint(stderr(), "%s", lex.prompt); 2407 c = bufio->lex.f.getc(); 2408 if (c == bufio->ERROR || c == bufio->EOF) { 2409 lex.eof = 1; 2410 c = lex.EOF; 2411 } else if (c == '\n') 2412 lex.linenum++; 2413 lex.lastnl = (c == '\n'); 2414 lex.cbuf[lex.strpos++] = c; 2415 if (lex.strpos >= len lex.cbuf) 2416 lex.strpos = 0; 2417 } 2418 } else { 2419 if (lex.strpos >= len lex.s) { 2420 lex.eof = 1; 2421 c = lex.EOF; 2422 } else 2423 c = lex.s[lex.strpos++]; 2424 } 2425 return c; 2426} 2427 2428readnum(lex: ref YYLEX): int 2429{ 2430 sum := nc := 0; 2431 while ((c := lex.getc()) >= '0' && c <= '9') { 2432 sum = (sum * 10) + (c - '0'); 2433 nc++; 2434 } 2435 lex.ungetc(); 2436 if (nc == 0) 2437 return -1; 2438 return sum; 2439} 2440 2441readfdassign(lex: ref YYLEX): (int, ref Redir) 2442{ 2443 n1 := readnum(lex); 2444 if ((c := lex.getc()) != '=') { 2445 if (c == ']') 2446 return (REDIR, ref Redir(-1, n1, -1)); 2447 2448 return (ERROR, nil); 2449 } 2450 n2 := readnum(lex); 2451 if (lex.getc() != ']') 2452 return (ERROR, nil); 2453 return (DUP, ref Redir(-1, n1, n2)); 2454} 2455 2456mkseq(left, right: ref Node): ref Node 2457{ 2458 if (left != nil && right != nil) 2459 return mk(n_SEQ, left, right); 2460 else if (left == nil) 2461 return right; 2462 return left; 2463} 2464 2465mk(ntype: int, left, right: ref Node): ref Node 2466{ 2467 return ref Node(ntype, left, right, nil, nil); 2468} 2469 2470stderr(): ref Sys->FD 2471{ 2472 return sys->fildes(2); 2473} 2474yyexca := array[] of {-1, 0, 2475 8, 17, 2476 10, 17, 2477 11, 17, 2478 12, 17, 2479 14, 17, 2480 15, 17, 2481 16, 17, 2482 -2, 0, 2483-1, 1, 2484 1, -1, 2485 -2, 0, 2486}; 2487YYNPROD: con 45; 2488YYPRIVATE: con 57344; 2489yytoknames: array of string; 2490yystates: array of string; 2491yydebug: con 0; 2492YYLAST: con 93; 2493yyact := array[] of { 2494 12, 10, 15, 4, 5, 40, 8, 11, 9, 7, 2495 30, 31, 54, 6, 50, 35, 34, 32, 33, 21, 2496 36, 38, 34, 41, 43, 22, 29, 3, 28, 13, 2497 14, 16, 17, 20, 37, 42, 1, 23, 45, 51, 2498 44, 47, 48, 18, 39, 19, 41, 43, 56, 30, 2499 31, 46, 58, 57, 59, 60, 49, 13, 14, 16, 2500 17, 53, 13, 14, 16, 17, 2, 52, 0, 16, 2501 17, 18, 27, 19, 16, 17, 18, 52, 19, 0, 2502 26, 18, 0, 19, 24, 25, 18, 26, 19, 0, 2503 55, 24, 25, 2504}; 2505yypact := array[] of { 2506 25,-1000, 11, 11, 69, 58, 18, 14,-1000, 58, 2507 58,-1000, 5,-1000, 68,-1000,-1000, 68,-1000, 58, 2508-1000,-1000,-1000,-1000,-1000,-1000, 58,-1000, 58,-1000, 2509 -1,-1000,-1000, 68,-1000, -1,-1000, -5, 63,-1000, 2510 -9, 76, 58,-1000, 18, 14, 53,-1000, 58, 63, 2511-1000, -1,-1000, 53,-1000,-1000,-1000,-1000,-1000, -1, 2512-1000, 2513}; 2514yypgo := array[] of { 2515 0, 1, 0, 44, 8, 6, 36, 7, 35, 4, 2516 9, 2, 66, 5, 34, 13, 3, 33, 21, 2517}; 2518yyr1 := array[] of { 2519 0, 6, 6, 17, 17, 12, 12, 13, 13, 9, 2520 9, 8, 8, 16, 16, 15, 15, 10, 10, 10, 2521 5, 5, 5, 5, 7, 7, 7, 1, 1, 4, 2522 4, 4, 14, 14, 3, 3, 3, 2, 2, 11, 2523 11, 11, 11, 18, 18, 2524}; 2525yyr2 := array[] of { 2526 0, 2, 2, 1, 1, 1, 2, 1, 2, 2, 2527 2, 1, 2, 1, 3, 1, 3, 0, 1, 4, 2528 1, 2, 1, 1, 3, 3, 2, 1, 2, 1, 2529 2, 2, 1, 2, 2, 3, 3, 1, 4, 1, 2530 2, 3, 3, 0, 2, 2531}; 2532yychk := array[] of { 2533-1000, -6, -12, 2, -16, -9, -15, -10, -5, -4, 2534 -1, -7, -2, 4, 5, -11, 6, 7, 18, 20, 2535 -17, 8, 14, -17, 15, 16, 11, -12, 10, 12, 2536 -2, -1, -5, 13, 17, -2, -11, -14, -18, -3, 2537 -13, -16, -8, -9, -15, -10, -18, -7, -4, -18, 2538 19, -2, 14, -18, 21, 14, -13, -5, -11, -2, 2539 -1, 2540}; 2541yydef := array[] of { 2542 -2, -2, 0, 0, 5, 17, 13, 15, 18, 20, 2543 22, 23, 29, 27, 0, 37, 39, 0, 43, 17, 2544 1, 3, 4, 2, 9, 10, 17, 6, 17, 43, 2545 30, 31, 21, 26, 43, 28, 40, 0, 32, 43, 2546 0, 7, 17, 11, 14, 16, 0, 24, 25, 0, 2547 41, 34, 44, 33, 42, 12, 8, 19, 38, 35, 2548 36, 2549}; 2550yytok1 := array[] of { 2551 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2552 14, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2553 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2554 3, 3, 3, 3, 3, 3, 3, 3, 16, 3, 2555 18, 19, 3, 3, 3, 3, 3, 3, 3, 3, 2556 3, 3, 3, 3, 3, 3, 3, 3, 3, 15, 2557 3, 13, 3, 3, 3, 3, 3, 3, 3, 3, 2558 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2559 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2560 3, 3, 3, 3, 17, 3, 3, 3, 3, 3, 2561 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2562 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2563 3, 3, 3, 20, 12, 21, 2564}; 2565yytok2 := array[] of { 2566 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 2567}; 2568yytok3 := array[] of { 2569 0 2570}; 2571 2572YYSys: module 2573{ 2574 FD: adt 2575 { 2576 fd: int; 2577 }; 2578 fildes: fn(fd: int): ref FD; 2579 fprint: fn(fd: ref FD, s: string, *): int; 2580}; 2581 2582yysys: YYSys; 2583yystderr: ref YYSys->FD; 2584 2585YYFLAG: con -1000; 2586 2587 2588yytokname(yyc: int): string 2589{ 2590 if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil) 2591 return yytoknames[yyc-1]; 2592 return "<"+string yyc+">"; 2593} 2594 2595yystatname(yys: int): string 2596{ 2597 if(yys >= 0 && yys < len yystates && yystates[yys] != nil) 2598 return yystates[yys]; 2599 return "<"+string yys+">\n"; 2600} 2601 2602yylex1(yylex: ref YYLEX): int 2603{ 2604 c : int; 2605 yychar := yylex.lex(); 2606 if(yychar <= 0) 2607 c = yytok1[0]; 2608 else if(yychar < len yytok1) 2609 c = yytok1[yychar]; 2610 else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2) 2611 c = yytok2[yychar-YYPRIVATE]; 2612 else{ 2613 n := len yytok3; 2614 c = 0; 2615 for(i := 0; i < n; i+=2) { 2616 if(yytok3[i+0] == yychar) { 2617 c = yytok3[i+1]; 2618 break; 2619 } 2620 } 2621 if(c == 0) 2622 c = yytok2[1]; # unknown char 2623 } 2624 if(yydebug >= 3) 2625 yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c)); 2626 return c; 2627} 2628 2629YYS: adt 2630{ 2631 yyv: YYSTYPE; 2632 yys: int; 2633}; 2634 2635yyparse(yylex: ref YYLEX): int 2636{ 2637 if(yydebug >= 1 && yysys == nil) { 2638 yysys = load YYSys "$Sys"; 2639 yystderr = yysys->fildes(2); 2640 } 2641 2642 yys := array[YYMAXDEPTH] of YYS; 2643 2644 yyval: YYSTYPE; 2645 yystate := 0; 2646 yychar := -1; 2647 yynerrs := 0; # number of errors 2648 yyerrflag := 0; # error recovery flag 2649 yyp := -1; 2650 yyn := 0; 2651 2652yystack: 2653 for(;;){ 2654 # put a state and value onto the stack 2655 if(yydebug >= 4) 2656 yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); 2657 2658 yyp++; 2659 if(yyp >= len yys) 2660 yys = (array[len yys * 2] of YYS)[0:] = yys; 2661 yys[yyp].yys = yystate; 2662 yys[yyp].yyv = yyval; 2663 2664 for(;;){ 2665 yyn = yypact[yystate]; 2666 if(yyn > YYFLAG) { # simple state 2667 if(yychar < 0) 2668 yychar = yylex1(yylex); 2669 yyn += yychar; 2670 if(yyn >= 0 && yyn < YYLAST) { 2671 yyn = yyact[yyn]; 2672 if(yychk[yyn] == yychar) { # valid shift 2673 yychar = -1; 2674 yyp++; 2675 if(yyp >= len yys) 2676 yys = (array[len yys * 2] of YYS)[0:] = yys; 2677 yystate = yyn; 2678 yys[yyp].yys = yystate; 2679 yys[yyp].yyv = yylex.lval; 2680 if(yyerrflag > 0) 2681 yyerrflag--; 2682 if(yydebug >= 4) 2683 yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); 2684 continue; 2685 } 2686 } 2687 } 2688 2689 # default state action 2690 yyn = yydef[yystate]; 2691 if(yyn == -2) { 2692 if(yychar < 0) 2693 yychar = yylex1(yylex); 2694 2695 # look through exception table 2696 for(yyxi:=0;; yyxi+=2) 2697 if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate) 2698 break; 2699 for(yyxi += 2;; yyxi += 2) { 2700 yyn = yyexca[yyxi]; 2701 if(yyn < 0 || yyn == yychar) 2702 break; 2703 } 2704 yyn = yyexca[yyxi+1]; 2705 if(yyn < 0){ 2706 yyn = 0; 2707 break yystack; 2708 } 2709 } 2710 2711 if(yyn != 0) 2712 break; 2713 2714 # error ... attempt to resume parsing 2715 if(yyerrflag == 0) { # brand new error 2716 yylex.error("syntax error"); 2717 yynerrs++; 2718 if(yydebug >= 1) { 2719 yysys->fprint(yystderr, "%s", yystatname(yystate)); 2720 yysys->fprint(yystderr, "saw %s\n", yytokname(yychar)); 2721 } 2722 } 2723 2724 if(yyerrflag != 3) { # incompletely recovered error ... try again 2725 yyerrflag = 3; 2726 2727 # find a state where "error" is a legal shift action 2728 while(yyp >= 0) { 2729 yyn = yypact[yys[yyp].yys] + YYERRCODE; 2730 if(yyn >= 0 && yyn < YYLAST) { 2731 yystate = yyact[yyn]; # simulate a shift of "error" 2732 if(yychk[yystate] == YYERRCODE) 2733 continue yystack; 2734 } 2735 2736 # the current yyp has no shift onn "error", pop stack 2737 if(yydebug >= 2) 2738 yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n", 2739 yys[yyp].yys, yys[yyp-1].yys ); 2740 yyp--; 2741 } 2742 # there is no state on the stack with an error shift ... abort 2743 yyn = 1; 2744 break yystack; 2745 } 2746 2747 # no shift yet; clobber input char 2748 if(yydebug >= 2) 2749 yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar)); 2750 if(yychar == YYEOFCODE) { 2751 yyn = 1; 2752 break yystack; 2753 } 2754 yychar = -1; 2755 # try again in the same state 2756 } 2757 2758 # reduction by production yyn 2759 if(yydebug >= 2) 2760 yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate)); 2761 2762 yypt := yyp; 2763 yyp -= yyr2[yyn]; 2764 yym := yyn; 2765 2766 # consult goto table to find next state 2767 yyn = yyr1[yyn]; 2768 yyg := yypgo[yyn]; 2769 yyj := yyg + yys[yyp].yys + 1; 2770 2771 if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn) 2772 yystate = yyact[yyg]; 2773 case yym { 2774 27751=> 2776{yylex.lval.node = yys[yypt-1].yyv.node; return 0;} 27772=> 2778{yylex.lval.node = nil; return 0;} 27795=> 2780yyval.node = yys[yyp+1].yyv.node; 27816=> 2782{yyval.node = mkseq(yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); } 27837=> 2784yyval.node = yys[yyp+1].yyv.node; 27858=> 2786{yyval.node = mkseq(yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); } 27879=> 2788{yyval.node = yys[yypt-1].yyv.node; } 278910=> 2790{yyval.node = ref Node(n_NOWAIT, yys[yypt-1].yyv.node, nil, nil, nil); } 279111=> 2792yyval.node = yys[yyp+1].yyv.node; 279312=> 2794{yyval.node = yys[yypt-1].yyv.node; } 279513=> 2796yyval.node = yys[yyp+1].yyv.node; 279714=> 2798{ 2799 yyval.node = mk(n_ADJ, 2800 mk(n_ADJ, 2801 ref Node(n_WORD,nil,nil,"or",nil), 2802 mk(n_BLOCK, yys[yypt-2].yyv.node, nil) 2803 ), 2804 mk(n_BLOCK,yys[yypt-0].yyv.node,nil) 2805 ); 2806 } 280715=> 2808yyval.node = yys[yyp+1].yyv.node; 280916=> 2810{ 2811 yyval.node = mk(n_ADJ, 2812 mk(n_ADJ, 2813 ref Node(n_WORD,nil,nil,"and",nil), 2814 mk(n_BLOCK, yys[yypt-2].yyv.node, nil) 2815 ), 2816 mk(n_BLOCK,yys[yypt-0].yyv.node,nil) 2817 ); 2818 } 281917=> 2820{yyval.node = nil;} 282118=> 2822yyval.node = yys[yyp+1].yyv.node; 282319=> 2824{yyval.node = ref Node(n_PIPE, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node, nil, yys[yypt-2].yyv.redir); } 282520=> 2826yyval.node = yys[yyp+1].yyv.node; 282721=> 2828{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); } 282922=> 2830yyval.node = yys[yyp+1].yyv.node; 283123=> 2832yyval.node = yys[yyp+1].yyv.node; 283324=> 2834{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); } 283525=> 2836{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); } 283726=> 2838{yyval.node = mk(yys[yypt-0].yyv.optype, yys[yypt-1].yyv.node, nil); } 283927=> 2840{yyval.node = ref Node(n_DUP, nil, nil, nil, yys[yypt-0].yyv.redir); } 284128=> 2842{yyval.node = ref Node(n_REDIR, yys[yypt-0].yyv.node, nil, nil, yys[yypt-1].yyv.redir); } 284329=> 2844yyval.node = yys[yyp+1].yyv.node; 284530=> 2846{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); } 284731=> 2848{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); } 284932=> 2850{yyval.node = nil;} 285133=> 2852yyval.node = yys[yyp+1].yyv.node; 285334=> 2854{yyval.node = yys[yypt-0].yyv.node; } 285535=> 2856{yyval.node = mk(n_ADJ, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); } 285736=> 2858{yyval.node = mk(n_ADJ, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); } 285937=> 2860yyval.node = yys[yyp+1].yyv.node; 286138=> 2862{yyval.node = mk(n_CONCAT, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node); } 286339=> 2864{yyval.node = ref Node(n_WORD, nil, nil, yys[yypt-0].yyv.word, nil); } 286540=> 2866{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-0].yyv.node, nil); } 286741=> 2868{yyval.node = mk(n_LIST, yys[yypt-1].yyv.node, nil); } 286942=> 2870{yyval.node = mk(n_BLOCK, yys[yypt-1].yyv.node, nil); } 2871 } 2872 } 2873 2874 return yyn; 2875} 2876