bc.y (17679B)
1 %{ 2 #include <libgen.h> 3 #include <unistd.h> 4 5 #include <assert.h> 6 #include <ctype.h> 7 #include <errno.h> 8 #include <setjmp.h> 9 #include <stdarg.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 14 #include "arg.h" 15 #include "util.h" 16 17 #define DIGITS "0123456789ABCDEF" 18 #define NESTED_MAX 10 19 20 #define funid(f) ((f)[0] - 'a' + 1) 21 22 int yydebug; 23 24 typedef struct macro Macro; 25 26 struct macro { 27 int op; 28 int id; 29 int flowid; 30 int nested; 31 }; 32 33 static int yyerror(char *); 34 static int yylex(void); 35 36 static void quit(void); 37 static char *code(char *, ...); 38 static char *forcode(Macro *, char *, char *, char *, char *); 39 static char *whilecode(Macro *, char *, char *); 40 static char *ifcode(Macro *, char *, char *); 41 static char *funcode(Macro *, char *, char *, char *); 42 static char *param(char *, char *), *local(char *, char *); 43 static Macro *define(char *, char *); 44 static char *retcode(char *); 45 static char *brkcode(void); 46 static Macro *macro(int); 47 48 static char *ftn(char *); 49 static char *var(char *); 50 static char *ary(char *); 51 static void writeout(char *); 52 53 static char *yytext, *buff, *unwind; 54 static char *filename; 55 static FILE *filep; 56 static int lineno, nerr, flowid; 57 static jmp_buf recover; 58 static int nested, inhome; 59 static Macro macros[NESTED_MAX]; 60 int cflag, dflag, lflag, sflag; 61 62 %} 63 64 %union { 65 char *str; 66 char id[2]; 67 Macro *macro; 68 } 69 70 %token <id> ID 71 %token <str> STRING NUMBER 72 %token <str> EQOP '+' '-' '*' '/' '%' '^' INCDEC 73 %token HOME LOOP 74 %token DOT 75 %token EQ 76 %token LE 77 %token GE 78 %token NE 79 %token DEF 80 %token BREAK 81 %token QUIT 82 %token LENGTH 83 %token RETURN 84 %token FOR 85 %token IF 86 %token WHILE 87 %token SQRT 88 %token SCALE 89 %token IBASE 90 %token OBASE 91 %token AUTO 92 %token PRINT 93 94 %type <str> item statlst scolonlst 95 %type <str> function assign nexpr expr exprstat rel stat ary cond 96 %type <str> autolst arglst parlst 97 %type <str> params param locals local 98 %type <macro> def if for while 99 100 %right '=' EQOP 101 %left '+' '-' 102 %left '*' '/' '%' 103 %right '^' 104 105 %start program 106 107 %% 108 109 program : 110 | item program 111 ; 112 113 item : scolonlst '\n' {writeout($1);} 114 | function {writeout($1);} 115 ; 116 117 function : def parlst '{' '\n' autolst statlst '}' {$$ = funcode($1, $2, $5, $6);} 118 ; 119 120 scolonlst: {$$ = code("");} 121 | stat 122 | scolonlst ';' stat {$$ = code("%s%s", $1, $3);} 123 | scolonlst ';' 124 ; 125 126 statlst : {$$ = code("");} 127 | stat 128 | statlst '\n' stat {$$ = code("%s%s", $1, $3);} 129 | statlst ';' stat {$$ = code("%s%s", $1, $3);} 130 | statlst '\n' 131 | statlst ';' 132 ; 133 134 stat : exprstat 135 | PRINT expr {$$ = code("%sps.", $2);} 136 | PRINT STRING {$$ = code("[%s]P", $2);} 137 | PRINT STRING ',' expr {$$ = code("[%s]P%sps.", $2, $4);} 138 | STRING {$$ = code("[%s]P", $1);} 139 | BREAK {$$ = brkcode();} 140 | QUIT {quit();} 141 | RETURN {$$ = retcode(code(" 0"));} 142 | RETURN '(' expr ')' {$$ = retcode($3);} 143 | RETURN '(' ')' {$$ = retcode(code(" 0"));} 144 | while cond stat {$$ = whilecode($1, $2, $3);} 145 | if cond stat {$$ = ifcode($1, $2, $3);} 146 | '{' statlst '}' {$$ = $2;} 147 | for '(' expr ';' rel ';' expr ')' stat {$$ = forcode($1, $3, $5, $7, $9);} 148 ; 149 150 while : WHILE {$$ = macro(LOOP);} 151 ; 152 153 if : IF {$$ = macro(IF);} 154 ; 155 156 for : FOR {$$ = macro(LOOP);} 157 ; 158 159 def : DEF ID {$$ = macro(DEF);} 160 ; 161 162 parlst : '(' ')' {$$ = code("");} 163 | '(' params ')' {$$ = $2;} 164 ; 165 166 params : param {$$ = param(NULL, $1);} 167 | params ',' param {$$ = param($1, $3);} 168 ; 169 170 param : ID {$$ = var($1);} 171 | ID '[' ']' {$$ = ary($1);} 172 ; 173 174 autolst : {$$ = code("");} 175 | AUTO locals '\n' {$$ = $2;} 176 | AUTO locals ';' {$$ = $2;} 177 ; 178 179 locals : local {$$ = local(NULL, $1);} 180 | locals ',' local {$$ = local($1, $3);} 181 ; 182 183 local : ID {$$ = var($1);} 184 | ID '[' ']' {$$ = ary($1);} 185 ; 186 187 arglst : expr 188 | ID '[' ']' {$$ = code("%s", ary($1));} 189 | expr ',' arglst {$$ = code("%s%s", $1, $3);} 190 | ID '[' ']' ',' arglst {$$ = code("%s%s", ary($1), $5);} 191 ; 192 193 cond : '(' rel ')' {$$ = $2;} 194 ; 195 196 rel : expr {$$ = code("%s 0!=", $1);} 197 | expr EQ expr {$$ = code("%s%s=", $3, $1);} 198 | expr LE expr {$$ = code("%s%s!>", $3, $1);} 199 | expr GE expr {$$ = code("%s%s!<", $3, $1);} 200 | expr NE expr {$$ = code("%s%s!=", $3, $1);} 201 | expr '<' expr {$$ = code("%s%s<", $3, $1);} 202 | expr '>' expr {$$ = code("%s%s>", $3, $1);} 203 ; 204 205 exprstat: nexpr {$$ = code("%s%ss.", $1, code(sflag ? "" : "p"));} 206 | assign {$$ = code("%ss.", $1);} 207 ; 208 209 expr : nexpr 210 | assign 211 ; 212 213 nexpr : NUMBER {$$ = code(" %s", code($1));} 214 | ID {$$ = code("l%s", var($1));} 215 | DOT {$$ = code("l.");} 216 | SCALE {$$ = code("K");} 217 | IBASE {$$ = code("I");} 218 | OBASE {$$ = code("O");} 219 | ID ary {$$ = code("%s;%s", $2, ary($1));} 220 | '(' expr ')' {$$ = $2;} 221 | ID '(' arglst ')' {$$ = code("%sl%sx", $3, ftn($1));} 222 | ID '(' ')' {$$ = code("l%sx", ftn($1));} 223 | '-' expr {$$ = code("0%s-", $2);} 224 | expr '+' expr {$$ = code("%s%s+", $1, $3);} 225 | expr '-' expr {$$ = code("%s%s-", $1, $3);} 226 | expr '*' expr {$$ = code("%s%s*", $1, $3);} 227 | expr '/' expr {$$ = code("%s%s/", $1, $3);} 228 | expr '%' expr {$$ = code("%s%s%%", $1, $3);} 229 | expr '^' expr {$$ = code("%s%s^", $1, $3);} 230 | LENGTH '(' expr ')' {$$ = code("%sZ", $3);} 231 | SQRT '(' expr ')' {$$ = code("%sv", $3);} 232 | SCALE '(' expr ')' {$$ = code("%sX", $3);} 233 | INCDEC ID {$$ = code("l%s1%sds%s", var($2), code($1), var($2));} 234 | INCDEC SCALE {$$ = code("K1%sk", code($1));} 235 | INCDEC IBASE {$$ = code("I1%sdi", code($1));} 236 | INCDEC OBASE {$$ = code("O1%sdo", code($1));} 237 | INCDEC ID ary {$$ = code("%sdS_;%s1%sdL_:%s", $3, ary($2), code($1), ary($2));} 238 | ID INCDEC {$$ = code("l%sd1%ss%s", var($1), code($2), var($1));} 239 | SCALE INCDEC {$$ = code("Kd1%sk", code($2));} 240 | IBASE INCDEC {$$ = code("Id1%si", code($2));} 241 | OBASE INCDEC {$$ = code("Od1%so", code($2));} 242 | ID ary INCDEC {$$ = code("%sds.;%sd1%sl.:%s", $2, ary($1), code($3), ary($1));} 243 ; 244 245 assign : ID '=' expr {$$ = code("%sds%s", $3, var($1));} 246 | SCALE '=' expr {$$ = code("%sdk", $3);} 247 | IBASE '=' expr {$$ = code("%sdi", $3);} 248 | OBASE '=' expr {$$ = code("%sdo", $3);} 249 | ID ary '=' expr {$$ = code("%sd%s:%s", $4, $2, ary($1));} 250 | ID EQOP expr {$$ = code("%sl%s%sds%s", $3, var($1), code($2), var($1));} 251 | SCALE EQOP expr {$$ = code("%sK%sdk", $3, code($2));} 252 | IBASE EQOP expr {$$ = code("%sI%sdi", $3, code($2));} 253 | OBASE EQOP expr {$$ = code("%sO%sdo", $3, code($2));} 254 | ID ary EQOP expr {$$ = code("%s%sds.;%s%sdl.:s", $4, $2, ary($1), code($3), ary($1));} 255 ; 256 257 ary : '[' expr ']' {$$ = $2;} 258 ; 259 260 %% 261 static int 262 yyerror(char *s) 263 { 264 fprintf(stderr, "bc: %s:%d: %s\n", filename, lineno, s); 265 nerr++; 266 longjmp(recover, 1); 267 } 268 269 static void 270 writeout(char *s) 271 { 272 if (write(1, s, strlen(s)) < 0) 273 goto err; 274 if (write(1, (char[]){'\n'}, 1) < 0) 275 goto err; 276 free(s); 277 return; 278 279 err: 280 eprintf("writing to dc:"); 281 } 282 283 static char * 284 code(char *fmt, ...) 285 { 286 char *s, *t; 287 va_list ap; 288 int c, len, room; 289 290 va_start(ap, fmt); 291 room = BUFSIZ; 292 for (s = buff; *fmt; s += len) { 293 len = 1; 294 if ((c = *fmt++) != '%') 295 goto append; 296 297 switch (*fmt++) { 298 case 'd': 299 c = va_arg(ap, int); 300 len = snprintf(s, room, "%d", c); 301 if (len < 0 || len >= room) 302 goto err; 303 break; 304 case 'c': 305 c = va_arg(ap, int); 306 goto append; 307 case 's': 308 t = va_arg(ap, void *); 309 len = strlen(t); 310 if (len >= room) 311 goto err; 312 memcpy(s, t, len); 313 free(t); 314 break; 315 case '%': 316 append: 317 if (room <= 1) 318 goto err; 319 *s = c; 320 break; 321 default: 322 abort(); 323 } 324 325 room -= len; 326 } 327 va_end(ap); 328 329 *s = '\0'; 330 return estrdup(buff); 331 332 err: 333 eprintf("unable to code requested operation\n"); 334 } 335 336 static Macro * 337 macro(int op) 338 { 339 int preop; 340 Macro *d, *p; 341 342 if (nested == NESTED_MAX) 343 yyerror("too much nesting"); 344 345 d = ¯os[nested]; 346 d->op = op; 347 d->nested = nested++; 348 349 switch (op) { 350 case HOME: 351 d->id = 0; 352 d->flowid = flowid; 353 inhome = 1; 354 break; 355 case DEF: 356 inhome = 0; 357 d->id = funid(yytext); 358 d->flowid = macros[0].flowid; 359 break; 360 default: 361 assert(nested > 1); 362 preop = d[-1].op; 363 d->flowid = d[-1].flowid; 364 if (preop != HOME && preop != DEF) { 365 if (d->flowid == 255) 366 eprintf("too many control flow structures"); 367 d->flowid++; 368 } 369 d->id = d->flowid; 370 if (!inhome) { 371 /* populate reserved id */ 372 flowid = d->flowid; 373 for (p = d; p != macros; --p) 374 p[-1].flowid++; 375 } 376 break; 377 } 378 379 return d; 380 } 381 382 static char * 383 param(char *list, char *id) 384 { 385 char *i1, *i2; 386 387 i1 = estrdup(id); 388 i2 = estrdup(id); 389 free(id); 390 391 unwind = code(unwind ? "L%ss.%s" : "L%ss.", i1, unwind); 392 393 return code(list ? "S%s%s" : "S%s" , i2, list); 394 } 395 396 static char * 397 local(char *list, char *id) 398 { 399 char *i1, *i2; 400 401 i1 = estrdup(id); 402 i2 = estrdup(id); 403 free(id); 404 405 unwind = code(unwind ? "L%ss.%s" : "L%ss.", i1, unwind); 406 407 return code(list ? "0S%s%s" : "0S%s" , i2, list); 408 } 409 410 static char * 411 funcode(Macro *d, char *params, char *vars, char *body) 412 { 413 char *s; 414 415 s = code("[%s%s%s%s]s%c", 416 vars, params, 417 body, 418 retcode(code(" 0")), 419 d->id); 420 free(unwind); 421 unwind = NULL; 422 nested--; 423 inhome = 0; 424 425 426 return s; 427 } 428 429 static char * 430 brkcode(void) 431 { 432 Macro *d; 433 434 for (d = ¯os[nested-1]; d->op != HOME && d->op != LOOP; --d) 435 ; 436 if (d->op == HOME) 437 yyerror("break not in for or while"); 438 return code(" %dQ", nested - d->nested); 439 } 440 441 static char * 442 forcode(Macro *d, char *init, char *cmp, char *inc, char *body) 443 { 444 char *s; 445 446 s = code("[%s%ss.%s%c]s%c", 447 body, 448 inc, 449 estrdup(cmp), 450 d->id, d->id); 451 writeout(s); 452 453 s = code("%ss.%s%c", init, cmp, d->id); 454 nested--; 455 456 return s; 457 } 458 459 static char * 460 whilecode(Macro *d, char *cmp, char *body) 461 { 462 char *s; 463 464 s = code("[%ss.%s%c]s%c", 465 body, 466 estrdup(cmp), 467 d->id, d->id); 468 writeout(s); 469 470 s = code("%s%c", cmp, d->id); 471 nested--; 472 473 return s; 474 } 475 476 static char * 477 ifcode(Macro *d, char *cmp, char *body) 478 { 479 char *s; 480 481 s = code("[%s]s%c", body, d->id); 482 writeout(s); 483 484 s = code("%s%c", cmp, d->id); 485 nested--; 486 487 return s; 488 } 489 490 static char * 491 retcode(char *expr) 492 { 493 char *s; 494 495 if (nested < 2 || macros[1].op != DEF) 496 yyerror("return must be in a function"); 497 return code("%s %s %dQ", expr, estrdup(unwind), nested - 1); 498 } 499 500 static char * 501 ary(char *s) 502 { 503 return code("%c", toupper(s[0])); 504 } 505 506 static char * 507 ftn(char *s) 508 { 509 return code("%c", funid(s)); 510 } 511 512 static char * 513 var(char *s) 514 { 515 return code(s); 516 } 517 518 static void 519 quit(void) 520 { 521 exit(nerr > 0 ? 1 : 0); 522 } 523 524 static void 525 skipspaces(void) 526 { 527 int ch; 528 529 while (isspace(ch = getc(filep))) { 530 if (ch == '\n') { 531 lineno++; 532 break; 533 } 534 } 535 ungetc(ch, filep); 536 } 537 538 static int 539 iden(int ch) 540 { 541 static struct keyword { 542 char *str; 543 int token; 544 } keywords[] = { 545 {"define", DEF}, 546 {"break", BREAK}, 547 {"quit", QUIT}, 548 {"length", LENGTH}, 549 {"return", RETURN}, 550 {"for", FOR}, 551 {"if", IF}, 552 {"while", WHILE}, 553 {"sqrt", SQRT}, 554 {"scale", SCALE}, 555 {"ibase", IBASE}, 556 {"obase", OBASE}, 557 {"auto", AUTO}, 558 {"print", PRINT}, 559 {NULL} 560 }; 561 struct keyword *p; 562 char *bp; 563 564 ungetc(ch, filep); 565 for (bp = yytext; bp < &yytext[BUFSIZ]; ++bp) { 566 ch = getc(filep); 567 if (!islower(ch)) 568 break; 569 *bp = ch; 570 } 571 572 if (bp == &yytext[BUFSIZ]) 573 yyerror("too long token"); 574 *bp = '\0'; 575 ungetc(ch, filep); 576 577 if (strlen(yytext) == 1) { 578 strcpy(yylval.id, yytext); 579 return ID; 580 } 581 582 for (p = keywords; p->str && strcmp(p->str, yytext); ++p) 583 ; 584 585 if (!p->str) 586 yyerror("invalid keyword"); 587 588 return p->token; 589 } 590 591 static char * 592 digits(char *bp) 593 { 594 int ch; 595 char *digits = DIGITS, *p; 596 597 while (bp < &yytext[BUFSIZ]) { 598 ch = getc(filep); 599 p = strchr(digits, ch); 600 if (!p) 601 break; 602 *bp++ = ch; 603 } 604 605 if (bp == &yytext[BUFSIZ]) 606 return NULL; 607 ungetc(ch, filep); 608 609 return bp; 610 } 611 612 static int 613 number(int ch) 614 { 615 int d; 616 char *bp; 617 618 ungetc(ch, filep); 619 if ((bp = digits(yytext)) == NULL) 620 goto toolong; 621 622 if ((ch = getc(filep)) != '.') { 623 ungetc(ch, filep); 624 goto end; 625 } 626 *bp++ = '.'; 627 628 if ((bp = digits(bp)) == NULL) 629 goto toolong; 630 631 end: 632 if (bp == &yytext[BUFSIZ]) 633 goto toolong; 634 *bp = '\0'; 635 yylval.str = yytext; 636 637 return NUMBER; 638 639 toolong: 640 yyerror("too long number"); 641 } 642 643 static int 644 string(int ch) 645 { 646 char *bp; 647 648 for (bp = yytext; bp < &yytext[BUFSIZ]; ++bp) { 649 if ((ch = getc(filep)) == '"') 650 break; 651 *bp = ch; 652 } 653 654 if (bp == &yytext[BUFSIZ]) 655 yyerror("too long string"); 656 *bp = '\0'; 657 yylval.str = estrdup(yytext); 658 659 return STRING; 660 } 661 662 static int 663 follow(int next, int yes, int no) 664 { 665 int ch; 666 667 ch = getc(filep); 668 if (ch == next) 669 return yes; 670 ungetc(ch, filep); 671 return no; 672 } 673 674 static int 675 operand(int ch) 676 { 677 int peekc; 678 679 switch (ch) { 680 case '\n': 681 case '{': 682 case '}': 683 case '[': 684 case ']': 685 case '(': 686 case ')': 687 case ',': 688 case ';': 689 return ch; 690 case '.': 691 peekc = ungetc(getc(filep), filep); 692 if (strchr(DIGITS, peekc)) 693 return number(ch); 694 return DOT; 695 case '"': 696 return string(ch); 697 case '*': 698 yylval.str = "*"; 699 return follow('=', EQOP, '*'); 700 case '/': 701 yylval.str = "/"; 702 return follow('=', EQOP, '/'); 703 case '%': 704 yylval.str = "%"; 705 return follow('=', EQOP, '%'); 706 case '=': 707 return follow('=', EQ, '='); 708 case '+': 709 case '-': 710 yylval.str = (ch == '+') ? "+" : "-"; 711 if (follow('=', EQOP, ch) != ch) 712 return EQOP; 713 return follow(ch, INCDEC, ch); 714 case '^': 715 yylval.str = "^"; 716 return follow('=', EQOP, '^'); 717 case '<': 718 return follow('=', LE, '<'); 719 case '>': 720 return follow('=', GE, '>'); 721 case '!': 722 if (getc(filep) == '=') 723 return NE; 724 default: 725 yyerror("invalid operand"); 726 } 727 } 728 729 static void 730 comment(void) 731 { 732 int c; 733 734 for (;;) { 735 while ((c = getc(filep)) != '*') { 736 if (c == '\n') 737 lineno++; 738 } 739 if ((c = getc(filep)) == '/') 740 break; 741 ungetc(c, filep); 742 } 743 } 744 745 static int 746 yylex(void) 747 { 748 int peekc, ch; 749 750 repeat: 751 skipspaces(); 752 753 ch = getc(filep); 754 if (ch == EOF) { 755 return EOF; 756 } else if (!isascii(ch)) { 757 yyerror("invalid input character"); 758 } else if (islower(ch)) { 759 return iden(ch); 760 } else if (strchr(DIGITS, ch)) { 761 return number(ch); 762 } else { 763 if (ch == '/') { 764 peekc = getc(filep); 765 if (peekc == '*') { 766 comment(); 767 goto repeat; 768 } 769 ungetc(peekc, filep); 770 } 771 return operand(ch); 772 } 773 } 774 775 static void 776 spawn(void) 777 { 778 int fds[2]; 779 char errmsg[] = "bc:error execing dc\n"; 780 781 if (pipe(fds) < 0) 782 eprintf("creating pipe:"); 783 784 switch (fork()) { 785 case -1: 786 eprintf("forking dc:"); 787 case 0: 788 close(1); 789 dup(fds[1]); 790 close(fds[0]); 791 close(fds[1]); 792 break; 793 default: 794 close(0); 795 dup(fds[0]); 796 close(fds[0]); 797 close(fds[1]); 798 execlp("dc", "dc", (char *) NULL); 799 800 /* it shouldn't happen */ 801 write(3, errmsg, sizeof(errmsg)-1); 802 _Exit(2); 803 } 804 } 805 806 static void 807 run(void) 808 { 809 if (setjmp(recover)) { 810 if (ferror(filep)) 811 eprintf("%s:", filename); 812 if (feof(filep)) 813 return; 814 } 815 yyparse(); 816 } 817 818 static void 819 bc(char *fname) 820 { 821 Macro *d; 822 823 lineno = 1; 824 nested = 0; 825 826 macro(HOME); 827 if (!fname) { 828 filename = "<stdin>"; 829 filep = stdin; 830 } else { 831 filename = fname; 832 if ((filep = fopen(fname, "r")) == NULL) 833 eprintf("%s:", fname); 834 } 835 836 run(); 837 fclose(filep); 838 } 839 840 static void 841 loadlib(void) 842 { 843 int r; 844 size_t len; 845 char bin[FILENAME_MAX], fname[FILENAME_MAX]; 846 static char bclib[] = "bc.library"; 847 848 /* 849 * try first to load the library from the same directory than 850 * the executable, because that can makes easier the tests 851 * because it does not require to install to test the last version 852 */ 853 len = strlen(argv0); 854 if (len >= FILENAME_MAX) 855 goto share; 856 memcpy(bin, argv0, len + 1); 857 858 r = snprintf(fname, sizeof(fname), "%s/%s", dirname(bin), bclib); 859 if (r < 0 || r >= sizeof(fname)) 860 goto share; 861 862 if (access(fname, F_OK) < 0) 863 goto share; 864 865 bc(fname); 866 return; 867 868 share: 869 r = snprintf(fname, sizeof(fname), "%s/share/misc/%s", PREFIX, bclib); 870 if (r < 0 || r >= sizeof(fname)) 871 eprintf("invalid path name for bc.library\n"); 872 bc(fname); 873 } 874 875 static void 876 usage(void) 877 { 878 eprintf("usage: %s [-cdls]\n", argv0); 879 } 880 881 int 882 main(int argc, char *argv[]) 883 { 884 ARGBEGIN { 885 case 'c': 886 cflag = 1; 887 break; 888 case 'd': 889 dflag = 1; 890 yydebug = 3; 891 break; 892 case 'l': 893 lflag = 1; 894 break; 895 case 's': 896 sflag = 1; 897 break; 898 default: 899 usage(); 900 } ARGEND 901 902 yytext = malloc(BUFSIZ); 903 buff = malloc(BUFSIZ); 904 if (!yytext || !buff) 905 eprintf("out of memory\n"); 906 flowid = 128; 907 908 if (!cflag) 909 spawn(); 910 if (lflag) 911 loadlib(); 912 913 while (*argv) 914 bc(*argv++); 915 bc(NULL); 916 917 quit(); 918 }