sbase

suckless unix tools
git clone git://git.suckless.org/sbase
Log | Files | Refs | README | LICENSE

bc.y (14560B)


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