1implement Sed; 2 3# 4# partial sed implementation borrowed from plan9 sed. 5# 6 7include "sys.m"; 8 sys: Sys; 9include "draw.m"; 10include "arg.m"; 11 arg: Arg; 12include "bufio.m"; 13 bufio: Bufio; 14 Iobuf: import bufio; 15include "string.m"; 16 str: String; 17include "regex.m"; 18 regex: Regex; 19 Re: import regex; 20 21Sed : module { 22 init: fn(ctxt: ref Draw->Context, argv: list of string); 23}; 24 25 26false, true: con iota; 27bool: type int; 28 29Addr: adt { 30 pick { 31 None => 32 Dollar => 33 Line => 34 line: int; 35 Regex => 36 re: Re; 37 } 38}; 39 40Sedcom: adt { 41 command: fn(c: self ref Sedcom); 42 executable: fn(c: self ref Sedcom) : int; 43 44 ad1, ad2: ref Addr; 45 negfl: bool; 46 active: int; 47 48 pick { 49 S => 50 gfl, pfl: int; 51 re: Re; 52 b: ref Iobuf; 53 rhs: string; 54 D or CD or P or Q or EQ or G or CG or H or CH or N or CN or X or CP or L=> 55 A or C or I => 56 text: string; 57 R => 58 filename: string; 59 W => 60 b: ref Iobuf; 61 Y => 62 map: list of (int, int); 63 B or T or Lab => 64 lab: string; 65 } 66}; 67 68dflag := false; 69nflag := false; 70gflag := false; 71sflag := 0; 72 73delflag := 0; 74dolflag := 0; 75fhead := 0; 76files: list of string; 77fout: ref Iobuf; 78infile: ref Iobuf; 79jflag := 0; 80lastregex: Re; 81linebuf: string; 82filename := ""; 83lnum := 0; 84peekc := 0; 85 86holdsp := ""; 87patsp := ""; 88 89cmds: list of ref Sedcom; 90appendlist: list of ref Sedcom; 91bufioflush: list of ref Iobuf; 92 93init(nil: ref Draw->Context, args: list of string) 94{ 95 sys = load Sys Sys->PATH; 96 97 if ((arg = load Arg Arg->PATH) == nil) 98 fatal(sys->sprint("could not load %s: %r", Arg->PATH)); 99 100 if ((bufio = load Bufio Bufio->PATH) == nil) 101 fatal(sys->sprint("could not load %s: %r", Bufio->PATH)); 102 103 if ((str = load String String->PATH) == nil) 104 fatal(sys->sprint("could not load %s: %r", String->PATH)); 105 106 if ((regex = load Regex Regex->PATH) == nil) 107 fatal(sys->sprint("could not load %s: %r", Regex->PATH)); 108 109 arg->init(args); 110 111 compfl := 0; 112 while ((c := arg->opt()) != 0) 113 case c { 114 'n' => 115 nflag = true; 116 'g' => 117 gflag = true; 118 'e' => 119 if ((s := arg->arg()) == nil) 120 usage(); 121 filename = ""; 122 cmds = compile(bufio->sopen(s + "\n"), cmds); 123 compfl = 1; 124 'f' => if ((filename = arg->arg()) == nil) 125 usage(); 126 b := bufio->open(filename, bufio->OREAD); 127 if (b == nil) 128 fatal(sys->sprint("couldn't open '%s': %r", filename)); 129 cmds = compile(b, cmds); 130 compfl = 1; 131 'd' => 132 dflag = true; 133 * => 134 usage(); 135 } 136 args = arg->argv(); 137 if (compfl == 0) { 138 if (len args == 0) 139 fatal("missing pattern"); 140 filename = ""; 141 cmds = compile(bufio->sopen(hd args + "\n"), cmds); 142 args = tl args; 143 } 144 145 # reverse command list, we could compile addresses here if required 146 l: list of ref Sedcom; 147 for (p := cmds; p != nil; p = tl p) { 148 l = hd p :: l; 149 } 150 cmds = l; 151 152 # add files to file list (and reverse to get in right order) 153 f: list of string; 154 if (len args == 0) 155 f = "" :: f; 156 else for (; len args != 0; args = tl args) 157 f = hd args :: f; 158 for (;f != nil; f = tl f) 159 files = hd f :: files; 160 161 if ((fout = bufio->fopen(sys->fildes(1), bufio->OWRITE)) == nil) 162 fatal(sys->sprint("couldn't buffer stdout: %r")); 163 bufioflush = fout :: bufioflush; 164 lnum = 0; 165 execute(cmds); 166 exits(nil); 167} 168 169depth := 0; 170maxdepth: con 20; 171cmdend := array [maxdepth] of string; 172cmdcnt := array [maxdepth] of int; 173 174compile(b: ref Iobuf, l: list of ref Sedcom) : list of ref Sedcom 175{ 176 lnum = 1; 177 178nextline: 179 for (;;) { 180 err: int; 181 (err, linebuf) = getline(b); 182 if (err < 0) 183 break; 184 185 s := linebuf; 186 187 do { 188 rep: ref Sedcom; 189 ad1, ad2: ref Addr; 190 negfl := 0; 191 192 if (s != "") 193 s = str->drop(s, " \t;"); 194 195 if (s == "" || s[0] == '#') 196 continue nextline; 197 198 # read addresses 199 (s, ad1) = address(s); 200 pick a := ad1 { 201 None => 202 ad2 = ref Addr.None(); 203 * => 204 if (s != "" && (s[0] == ',' || s[0] == ';')) { 205 (s, ad2) = address(s[1:]); 206 } 207 else { 208 ad2 = ref Addr.None(); 209 } 210 } 211 212 s = str->drop(s, " \t"); 213 214 if (s != "" && str->in(s[0], "!")) { 215 negfl = true; 216 s = str->drop(s, "!"); 217 } 218 s = str->drop(s, " \t"); 219 if (s == "") 220 break; 221 c := s[0]; s = s[1:]; 222 223 # mop up commands that got two addresses but only want one. 224 case c { 225 'a' or 'c' or 'q' or '=' or 'i' => 226 if (tagof ad2 != tagof Addr.None) 227 fatal(sys->sprint("only one address allowed: '%s'", 228 linebuf)); 229 } 230 231 case c { 232 * => 233 fatal(sys->sprint("unrecognised command: '%s' (%c)", 234 linebuf, c)); 235 'a' => 236 if (s != "" && s[0] == '\\') 237 s = s[1:]; 238 if (s == "" || s[0] != '\n') 239 fatal("unexpected characters in a command: " + s); 240 rep = ref Sedcom.A (ad1, ad2, negfl, 0, s[1:]); 241 s = ""; 242 'c' => 243 if (s != "" && s[0] == '\\') 244 s = s[1:]; 245 if (s == "" || s[0] != '\n') 246 fatal("unexpected characters in c command: " + s); 247 rep = ref Sedcom.C (ad1, ad2, negfl, 0, s[1:]); 248 s = ""; 249 'i' => 250 if (s != "" && s[0] == '\\') 251 s = s[1:]; 252 if (s == "" || s[0] != '\n') 253 fatal("unexpected characters in i command: " + s); 254 rep = ref Sedcom.I (ad1, ad2, negfl, 0, s[1:]); 255 s = ""; 256 'r' => 257 s = str->drop(s, " \t"); 258 rep = ref Sedcom.R (ad1, ad2, negfl, 0, s); 259 s = ""; 260 'w' => 261 if (s != "") 262 s = str->drop(s, " \t"); 263 if (s == "") 264 fatal("no filename in w command: " + linebuf); 265 bo := bufio->open(s, bufio->OWRITE); 266 if (bo == nil) 267 bo = bufio->create(s, bufio->OWRITE, 8r666); 268 if (bo == nil) 269 fatal(sys->sprint("can't create output file: '%s'", s)); 270 bufioflush = bo :: bufioflush; 271 rep = ref Sedcom.W (ad1, ad2, negfl, 0, bo); 272 s = ""; 273 274 'd' => 275 rep = ref Sedcom.D (ad1, ad2, negfl, 0); 276 'D' => 277 rep = ref Sedcom.CD (ad1, ad2, negfl, 0); 278 'p' => 279 rep = ref Sedcom.P (ad1, ad2, negfl, 0); 280 'P' => 281 rep = ref Sedcom.CP (ad1, ad2, negfl, 0); 282 'q' => 283 rep = ref Sedcom.Q (ad1, ad2, negfl, 0); 284 '=' => 285 rep = ref Sedcom.EQ (ad1, ad2, negfl, 0); 286 'g' => 287 rep = ref Sedcom.G (ad1, ad2, negfl, 0); 288 'G' => 289 rep = ref Sedcom.CG (ad1, ad2, negfl, 0); 290 'h' => 291 rep = ref Sedcom.H (ad1, ad2, negfl, 0); 292 'H' => 293 rep = ref Sedcom.CH (ad1, ad2, negfl, 0); 294 'n' => 295 rep = ref Sedcom.N (ad1, ad2, negfl, 0); 296 'N' => 297 rep = ref Sedcom.CN (ad1, ad2, negfl, 0); 298 'x' => 299 rep = ref Sedcom.X (ad1, ad2, negfl, 0); 300 'l' => 301 rep = ref Sedcom.L (ad1, ad2, negfl, 0); 302 'y' => 303 if (s == "") 304 fatal("expected args: " + linebuf); 305 seof := s[0:1]; 306 s = s[1:]; 307 if (s == "") 308 fatal("no lhs: " + linebuf); 309 (lhs, s2) := str->splitl(s, seof); 310 if (s2 == "") 311 fatal("no lhs terminator: " + linebuf); 312 s2 = s2[1:]; 313 (rhs, s4) := str->splitl(s2, seof); 314 if (s4 == "") 315 fatal("no rhs: " + linebuf); 316 s = s4[1:]; 317 if (len lhs != len rhs) 318 fatal("y command needs same length sets: " + linebuf); 319 map: list of (int, int); 320 for (i := 0; i < len lhs; i++) 321 map = (lhs[i], rhs[i]) :: map; 322 rep = ref Sedcom.Y (ad1, ad2, negfl, 0, map); 323 's' => 324 seof := s[0:1]; 325 re: Re; 326 (re, s) = recomp(s); 327 rhs: string; 328 (s, rhs) = compsub(seof + s); 329 330 gfl := gflag; 331 pfl := 0; 332 333 if (s != "" && s[0] == 'g') { 334 gfl = 1; 335 s = s[1:]; 336 } 337 if (s != "" && s[0] == 'p') { 338 pfl = 1; 339 s = s[1:]; 340 } 341 if (s != "" && s[0] == 'P') { 342 pfl = 2; 343 s = s[1:]; 344 } 345 346 b: ref Iobuf = nil; 347 if (s != "" && s[0] == 'w') { 348 s = s[1:]; 349 if (s != "") 350 s = str->drop(s, " \t"); 351 if (s == "") 352 fatal("no filename in s with w: " + linebuf); 353 b = bufio->open(s, bufio->OWRITE); 354 if (b == nil) 355 b = bufio->create(s, bufio->OWRITE, 8r666); 356 if (b == nil) 357 fatal(sys->sprint("can't create output file: '%s'", s)); 358 bufioflush = b :: bufioflush; 359 s = ""; 360 } 361 rep = ref Sedcom.S (ad1, ad2, negfl, 0, gfl, pfl, re, b, rhs); 362 ':' => 363 if (s != "") 364 s = str->drop(s, " \t"); 365 (lab, s1) := str->splitl(s, " \t;#"); 366 s = s1; 367 if (lab == "") 368 fatal(sys->sprint("null label: '%s'", linebuf)); 369 if (findlabel(lab)) 370 fatal(sys->sprint("duplicate label: '%s'", lab)); 371 rep = ref Sedcom.Lab (ad1, ad2, negfl, 0, lab); 372 'b' or 't' => 373 if (s != "") 374 s = str->drop(s, " \t"); 375 (lab, s1) := str->splitl(s, " \t;#"); 376 s = s1; 377 if (c == 'b') 378 rep = ref Sedcom.B (ad1, ad2, negfl, 0, lab); 379 else 380 rep = ref Sedcom.T (ad1, ad2, negfl, 0, lab); 381 '{' => 382 # replace { with branch to }. 383 lab := mklab(depth); 384 depth++; 385 rep = ref Sedcom.B (ad1, ad2, !negfl, 0, lab); 386 s = ";" + s; 387 '}' => 388 if (tagof ad1 != tagof Addr.None) 389 fatal("did not expect address:" + linebuf); 390 if (--depth < 0) 391 fatal("too many }'s: " + linebuf); 392 lab := mklab(depth); 393 cmdcnt[depth]++; 394 rep = ref Sedcom.Lab ( ad1, ad2, negfl, 0, lab); 395 s = ";" + s; 396 } 397 398 l = rep :: l; 399 } while (s != nil && str->in(s[0], ";{}")); 400 401 if (s != nil) 402 fatal("leftover junk: " + s); 403 } 404 return l; 405} 406 407findlabel(lab: string) : bool 408{ 409 for (l := cmds; l != nil; l = tl l) 410 pick x := hd l { 411 Lab => 412 if (x.lab == lab) 413 return true; 414 } 415 return false; 416} 417 418mklab(depth: int): string 419{ 420 return "_" + string cmdcnt[depth] + "_" + string depth; 421} 422 423Sedcom.command(c: self ref Sedcom) 424{ 425 pick x := c { 426 S => 427 m: bool; 428 (m, patsp) = substitute(x, patsp); 429 if (m) { 430 case x.pfl { 431 0 => 432 ; 433 1 => 434 fout.puts(patsp + "\n"); 435 * => 436 l: string; 437 (l, patsp) = str->splitl(patsp, "\n"); 438 fout.puts(l + "\n"); 439 break; 440 } 441 if (x.b != nil) 442 x.b.puts(patsp + "\n"); 443 } 444 P => 445 fout.puts(patsp + "\n"); 446 CP => 447 (s, nil) := str->splitl(patsp, "\n"); 448 fout.puts(s + "\n"); 449 A => 450 appendlist = c :: appendlist; 451 R => 452 appendlist = c :: appendlist; 453 C => 454 delflag++; 455 if (c.active == 1) 456 fout.puts(x.text + "\n"); 457 I => 458 fout.puts(x.text + "\n"); 459 W => 460 x.b.puts(patsp + "\n"); 461 G => 462 patsp = holdsp; 463 CG => 464 patsp += holdsp; 465 H => 466 holdsp = patsp; 467 CH => 468 holdsp += patsp; 469 X => 470 (holdsp, patsp) = (patsp, holdsp); 471 Y => 472 # yes this is O(N²). 473 for (i := 0; i < len patsp; i++) 474 for (h := x.map; h != nil; h = tl h) { 475 (s, d) := hd h; 476 if (patsp[i] == s) 477 patsp[i] = d; 478 } 479 D => 480 delflag++; 481 CD => 482 # loose upto \n. 483 (s1, s2) := str->splitl(patsp, "\n"); 484 if (s2 == nil) 485 patsp = s1; 486 else if (len s2 > 1) 487 patsp = s2[1:]; 488 else 489 patsp = ""; 490 jflag++; 491 Q => 492 if (!nflag) 493 fout.puts(patsp + "\n"); 494 arout(); 495 exits(nil); 496 N => 497 if (!nflag) 498 fout.puts(patsp + "\n"); 499 arout(); 500 n: int; 501 (patsp, n) = gline(); 502 if (n < 0) 503 delflag++; 504 CN => 505 arout(); 506 (ns, n) := gline(); 507 if (n < 0) 508 delflag++; 509 patsp += "\n" + ns; 510 EQ => 511 fout.puts(sys->sprint("%d\n", lnum)); 512 Lab => 513 # labels don't do anything. 514 B => 515 jflag = true; 516 T => 517 if (sflag) { 518 sflag = false; 519 jflag = true; 520 } 521 L => 522 col := 0; 523 cc := 0; 524 for (i := 0; i < len patsp; i++) { 525 s := ""; 526 cc = patsp[i]; 527 if (cc >= 16r20 && cc < 16r7F && cc != '\n') 528 s[len s] = cc; 529 else 530 s = trans(cc); 531 for (j := 0; j < len s; j++) { 532 fout.putc(s[j]); 533 if (col++ > 71) { 534 fout.puts("\\\n"); 535 col = 0; 536 } 537 } 538 } 539 if (cc == ' ') 540 fout.puts("\\n"); 541 fout.putc('\n'); 542 * => 543 fatal("unhandled command"); 544 } 545} 546 547trans(ch: int) : string 548{ 549 case ch { 550 '\b' => 551 return "\\b"; 552 '\n' => 553 return "\\n"; 554 '\r' => 555 return "\\r"; 556 '\t' => 557 return "\\t"; 558 '\\' => 559 return "\\\\"; 560 * => 561 return sys->sprint("\\u%.4ux", ch); 562 } 563} 564 565getline(b: ref Iobuf) : (int, string) 566{ 567 w : string; 568 569 lnum++; 570 571 while ((c := b.getc()) != bufio->EOF) { 572 r := c; 573 if (r == '\\') { 574 w[len w] = r; 575 if ((c = b.getc()) == bufio->EOF) 576 break; 577 r = c; 578 } 579 else if (r == '\n') 580 return (1, w); 581 w[len w] = r; 582 } 583 return (-1, w); 584} 585 586address(s: string) : (string, ref Addr) 587{ 588 case s[0] { 589 '$' => 590 return (s[1:], ref Addr.Dollar()); 591 '/' => 592 (r, s1) := recomp(s); 593 if (r == nil) 594 r = lastregex; 595 if (r == nil) 596 fatal("First RE in address may not be null"); 597 return (s1, ref Addr.Regex(r)); 598 '0' to '9' => 599 (lno, ls) := str->toint(s, 10); 600 if (lno == 0) 601 fatal("line no 0 is illegal address"); 602 return (ls, ref Addr.Line(lno)); 603 * => 604 return (s, ref Addr.None()); 605 } 606} 607 608recomp(s :string) : (Re, string) 609{ 610 expbuf := ""; 611 612 seof := s[0]; s = s[1:]; 613 if (s[0] == seof) 614 return (nil, s[1:]); # // 615 616 c := s[0]; s = s[1:]; 617 do { 618 if (c == '\0' || c == '\n') 619 fatal("too much text: " + linebuf); 620 if (c == '\\') { 621 expbuf[len expbuf] = c; 622 c = s[0]; s = s[1:]; 623 if (c == 'n') 624 c = '\n'; 625 } 626 expbuf[len expbuf] = c; 627 c = s[0]; s = s[1:]; 628 } while (c != seof); 629 630 (r, err) := regex->compile(expbuf, 1); 631 if (r == nil) 632 fatal(sys->sprint("%s '%s'", err, expbuf)); 633 634 lastregex = r; 635 636 return (r, s); 637} 638 639compsub(s: string): (string, string) 640{ 641 seof := s[0]; 642 rhs := ""; 643 for (i := 1; i < len s; i++) { 644 r := s[i]; 645 if (r == seof) 646 break; 647 if (r == '\\') { 648 rhs[len rhs] = r; 649 if(++i >= len s) 650 break; 651 r = s[i]; 652 } 653 rhs[len rhs] = r; 654 } 655 if (i >= len s) 656 fatal(sys->sprint("no closing %c in replacement text: %s", seof, linebuf)); 657 return (s[i+1:], rhs); 658} 659 660execute(l: list of ref Sedcom) 661{ 662 for (;;) { 663 n: int; 664 665 (patsp, n) = gline(); 666 if (n < 0) 667 break; 668 669cmdloop: 670 for (p := l; p != nil;) { 671 c := hd p; 672 if (!c.executable()) { 673 p = tl p; 674 continue; 675 } 676 677 c.command(); 678 679 if (delflag) 680 break; 681 if (jflag) { 682 jflag = 0; 683 pick x := c { 684 B or T => 685 if (p == nil) 686 break cmdloop; 687 for (p = l; p != nil; p = tl p) { 688 pick cc := hd p { 689 Lab => 690 if (cc.lab == x.lab) 691 continue cmdloop; 692 } 693 } 694 break cmdloop; # unmatched branch => end of script 695 * => 696 # don't branch. 697 } 698 } 699 else 700 p = tl p; 701 } 702 if (!nflag && !delflag) 703 fout.puts(patsp + "\n"); 704 arout(); 705 delflag = 0; 706 } 707} 708 709Sedcom.executable(c: self ref Sedcom) : int 710{ 711 if (c.active) { 712 if (c.active == 1) 713 c.active = 2; 714 pick x := c.ad2 { 715 None => 716 c.active = 0; 717 Dollar => 718 return !c.negfl; 719 Line => 720 if (lnum <= x.line) { 721 if (x.line == lnum) 722 c.active = 0; 723 return !c.negfl; 724 } 725 c.active = 0; 726 return c.negfl; 727 Regex => 728 if (match(x.re, patsp)) 729 c.active = false; 730 return !c.negfl; 731 } 732 } 733 pick x := c.ad1 { 734 None => 735 return !c.negfl; 736 Dollar => 737 if (dolflag) 738 return !c.negfl; 739 Line => 740 if (x.line == lnum) { 741 c.active = 1; 742 return !c.negfl; 743 } 744 Regex => 745 if (match(x.re, patsp)) { 746 c.active = 1; 747 return !c.negfl; 748 } 749 } 750 return c.negfl; 751} 752 753arout() 754{ 755 a: list of ref Sedcom; 756 757 while (appendlist != nil) { 758 a = hd appendlist :: a; 759 appendlist = tl appendlist; 760 } 761 762 for (; a != nil; a = tl a) 763 pick x := hd a { 764 A => 765 fout.puts(x.text + "\n"); 766 R => 767 if ((b := bufio->open(x.filename, bufio->OREAD)) == nil) 768 fatal(sys->sprint("couldn't open '%s'", x.filename)); 769 while ((c := b.getc()) != bufio->EOF) 770 fout.putc(c); 771 b.close(); 772 * => 773 fatal("unexpected command on appendlist"); 774 } 775} 776 777match(re: Re, s: string) : bool 778{ 779 return re != nil && regex->execute(re, s) != nil; 780} 781 782substitute(c: ref Sedcom.S, s: string) : (bool, string) 783{ 784 if (!match(c.re, s)) 785 return (false, s); 786 sflag = true; 787 start := 0; 788 789 # Beware of infinite loops: 's/$/i/g', 's/a/aa/g', 's/^/a/g' 790 do { 791 se := (start, len s); 792 if ((m := regex->executese(c.re, s, se, true, true)) == nil) 793 break; 794 (l, r) := m[0]; 795 rep := ""; 796 for (i := 0; i < len c.rhs; i++){ 797 if (c.rhs[i] != '\\' || i+1 == len c.rhs){ 798 if (c.rhs[i] == '&') 799 rep += s[l: r]; 800 else 801 rep[len rep] = c.rhs[i]; 802 }else { 803 i++; 804 case c.rhs[i] { 805 '0' to '9' => 806 n := c.rhs[i] - '0'; 807 # elide if too big 808 if (n < len m) { 809 (beg, end) := m[n]; 810 rep += s[beg:end]; 811 } 812 'n' => 813 rep[len rep] = '\n'; 814 * => 815 rep[len rep] = c.rhs[i]; 816 } 817 } 818 } 819 s = s[0:l] + rep + s[r:]; 820 start = l + len rep; 821 if(r == l) 822 start++; 823 } while (c.gfl); 824 return (true, s); 825} 826 827gline() : (string, int) 828{ 829 if (infile == nil && opendatafile() < 0) 830 return (nil, -1); 831 832 sflag = false; 833 lnum++; 834 835 s := ""; 836 do { 837 c := peekc; 838 if (c == 0) 839 c = infile.getc(); 840 for (; c != bufio->EOF; c = infile.getc()) { 841 if (c == '\n') { 842 if ((peekc = infile.getc()) == bufio->EOF) 843 if (fhead == 0) 844 dolflag = 1; 845 return (s, 1); 846 } 847 s[len s] = c; 848 } 849 if (len s != 0) { 850 peekc = bufio->EOF; 851 if (fhead == 0) 852 dolflag = 1; 853 return (s, 1); 854 } 855 peekc = 0; 856 infile = nil; 857 } while (opendatafile() > 0); 858 infile = nil; 859 return (nil, -1); 860} 861 862opendatafile() : int 863{ 864 if (files == nil) 865 return -1; 866 if (hd files != nil) { 867 if ((infile = bufio->open(hd files, bufio->OREAD)) == nil) 868 fatal(sys->sprint("can't open '%s'", hd files)); 869 } 870 else if ((infile = bufio->fopen(sys->fildes(0), bufio->OREAD)) == nil) 871 fatal("can't buffer stdin"); 872 873 files = tl files; 874 return 1; 875} 876 877dbg(s: string) 878{ 879 if (dflag) 880 sys->print("dbg: %s\n", s); 881} 882 883usage() 884{ 885 sys->fprint(stderr(), "usage: %s [-ngd] [-e expr] [-f file] [expr] [file...]\n", 886 arg->progname()); 887 exits("usage"); 888} 889 890fatal(s: string) 891{ 892 f := filename; 893 if (f == nil) 894 f = "<stdin>"; 895 sys->fprint(stderr(), "%s:%d %s\n", f, lnum, s); 896 exits("error"); 897} 898 899exits(e: string) 900{ 901 for(; bufioflush != nil; bufioflush = tl bufioflush) 902 (hd bufioflush).flush(); 903 if (e != nil) 904 raise "fail:" + e; 905 exit; 906} 907 908stderr() : ref Sys->FD 909{ 910 return sys->fildes(2); 911} 912