bc.y - sbase - suckless unix tools
(HTM) git clone git://git.suckless.org/sbase
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
bc.y (16970B)
---
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 PARAM
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=", $1, $3);}
198 | expr LE expr {$$ = code("%s%s!<", $1, $3);}
199 | expr GE expr {$$ = code("%s%s!>", $1, $3);}
200 | expr NE expr {$$ = code("%s%s!=", $1, $3);}
201 | expr '<' expr {$$ = code("%s%s>", $1, $3);}
202 | expr '>' expr {$$ = code("%s%s<", $1, $3);}
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 return NULL;
335 }
336
337 static Macro *
338 macro(int op)
339 {
340 int preop;
341 Macro *d, *p;
342
343 if (nested == NESTED_MAX)
344 yyerror("too much nesting");
345
346 d = ¯os[nested];
347 d->op = op;
348 d->nested = nested++;
349
350 switch (op) {
351 case HOME:
352 d->id = 0;
353 d->flowid = flowid;
354 inhome = 1;
355 break;
356 case DEF:
357 inhome = 0;
358 d->id = funid(yytext);
359 d->flowid = macros[0].flowid;
360 break;
361 default:
362 assert(nested > 1);
363 preop = d[-1].op;
364 d->flowid = d[-1].flowid;
365 if (preop != HOME && preop != DEF) {
366 if (d->flowid == 255)
367 eprintf("too many control flow structures");
368 d->flowid++;
369 }
370 d->id = d->flowid;
371 if (!inhome) {
372 /* populate reserved id */
373 flowid = d->flowid;
374 for (p = d; p != macros; --p)
375 p[-1].flowid++;
376 }
377 break;
378 }
379
380 return d;
381 }
382
383 static char *
384 decl(int type, char *list, char *id)
385 {
386 char *i1, *i2;
387
388 i1 = estrdup(id);
389 i2 = estrdup(id);
390 free(id);
391
392 if (!unwind)
393 unwind = estrdup("");
394 if (!list)
395 list = estrdup("");
396
397 unwind = code("%sL%ss.", unwind, i1);
398
399 return code((type == AUTO) ? "0S%s%s" : "S%s%s", i2, list);
400 }
401
402 static char *
403 param(char *list, char *id)
404 {
405 return decl(PARAM, list, id);
406 }
407
408 static char *
409 local(char *list, char *id)
410 {
411 return decl(AUTO, list, id);
412 }
413
414 static char *
415 funcode(Macro *d, char *params, char *vars, char *body)
416 {
417 char *s;
418
419 s = code("[%s%s%s%s]s%c",
420 vars, params,
421 body,
422 retcode(code(" 0")),
423 d->id);
424 free(unwind);
425 unwind = NULL;
426 nested--;
427 inhome = 0;
428
429
430 return s;
431 }
432
433 static char *
434 brkcode(void)
435 {
436 Macro *d;
437
438 for (d = ¯os[nested-1]; d->op != HOME && d->op != LOOP; --d)
439 ;
440 if (d->op == HOME)
441 yyerror("break not in for or while");
442 return code(" %dQ", nested - d->nested);
443 }
444
445 static char *
446 forcode(Macro *d, char *init, char *cmp, char *inc, char *body)
447 {
448 char *s;
449
450 s = code("[%s%ss.%s%c]s%c",
451 body,
452 inc,
453 estrdup(cmp),
454 d->id, d->id);
455 writeout(s);
456
457 s = code("%ss.%s%c ", init, cmp, d->id);
458 nested--;
459
460 return s;
461 }
462
463 static char *
464 whilecode(Macro *d, char *cmp, char *body)
465 {
466 char *s;
467
468 s = code("[%s%s%c]s%c",
469 body,
470 estrdup(cmp),
471 d->id, d->id);
472 writeout(s);
473
474 s = code("%s%c ", cmp, d->id);
475 nested--;
476
477 return s;
478 }
479
480 static char *
481 ifcode(Macro *d, char *cmp, char *body)
482 {
483 char *s;
484
485 s = code("[%s]s%c", body, d->id);
486 writeout(s);
487
488 s = code("%s%c ", cmp, d->id);
489 nested--;
490
491 return s;
492 }
493
494 static char *
495 retcode(char *expr)
496 {
497 char *s;
498
499 if (nested < 2 || macros[1].op != DEF)
500 yyerror("return must be in a function");
501 return code("%s %s %dQ", expr, estrdup(unwind), nested - 1);
502 }
503
504 static char *
505 ary(char *s)
506 {
507 return code("%c", toupper(s[0]));
508 }
509
510 static char *
511 ftn(char *s)
512 {
513 return code("%c", funid(s));
514 }
515
516 static char *
517 var(char *s)
518 {
519 return code(s);
520 }
521
522 static void
523 quit(void)
524 {
525 exit(nerr > 0 ? 1 : 0);
526 }
527
528 static void
529 skipspaces(void)
530 {
531 int ch;
532
533 while (isspace(ch = getc(filep))) {
534 if (ch == '\n') {
535 lineno++;
536 break;
537 }
538 }
539 ungetc(ch, filep);
540 }
541
542 static int
543 iden(int ch)
544 {
545 static struct keyword {
546 char *str;
547 int token;
548 } keywords[] = {
549 {"define", DEF},
550 {"break", BREAK},
551 {"quit", QUIT},
552 {"length", LENGTH},
553 {"return", RETURN},
554 {"for", FOR},
555 {"if", IF},
556 {"while", WHILE},
557 {"sqrt", SQRT},
558 {"scale", SCALE},
559 {"ibase", IBASE},
560 {"obase", OBASE},
561 {"auto", AUTO},
562 {"print", PRINT},
563 {NULL}
564 };
565 struct keyword *p;
566 char *bp;
567
568 ungetc(ch, filep);
569 for (bp = yytext; bp < &yytext[BUFSIZ]; ++bp) {
570 ch = getc(filep);
571 if (!islower(ch))
572 break;
573 *bp = ch;
574 }
575
576 if (bp == &yytext[BUFSIZ])
577 yyerror("too long token");
578 *bp = '\0';
579 ungetc(ch, filep);
580
581 if (strlen(yytext) == 1) {
582 strcpy(yylval.id, yytext);
583 return ID;
584 }
585
586 for (p = keywords; p->str && strcmp(p->str, yytext); ++p)
587 ;
588
589 if (!p->str)
590 yyerror("invalid keyword");
591
592 return p->token;
593 }
594
595 static char *
596 digits(char *bp)
597 {
598 int ch;
599 char *digits = DIGITS, *p;
600
601 while (bp < &yytext[BUFSIZ]) {
602 ch = getc(filep);
603 p = strchr(digits, ch);
604 if (!p)
605 break;
606 *bp++ = ch;
607 }
608
609 if (bp == &yytext[BUFSIZ])
610 return NULL;
611 ungetc(ch, filep);
612
613 return bp;
614 }
615
616 static int
617 number(int ch)
618 {
619 int d;
620 char *bp;
621
622 ungetc(ch, filep);
623 if ((bp = digits(yytext)) == NULL)
624 goto toolong;
625
626 if ((ch = getc(filep)) != '.') {
627 ungetc(ch, filep);
628 goto end;
629 }
630 *bp++ = '.';
631
632 if ((bp = digits(bp)) == NULL)
633 goto toolong;
634
635 end:
636 if (bp == &yytext[BUFSIZ])
637 goto toolong;
638 *bp = '\0';
639 yylval.str = yytext;
640
641 return NUMBER;
642
643 toolong:
644 yyerror("too long number");
645 return 0;
646 }
647
648 static int
649 string(int ch)
650 {
651 char *bp;
652
653 for (bp = yytext; bp < &yytext[BUFSIZ]; ++bp) {
654 if ((ch = getc(filep)) == '"')
655 break;
656 *bp = ch;
657 }
658
659 if (bp == &yytext[BUFSIZ])
660 yyerror("too long string");
661 *bp = '\0';
662 yylval.str = estrdup(yytext);
663
664 return STRING;
665 }
666
667 static int
668 follow(int next, int yes, int no)
669 {
670 int ch;
671
672 ch = getc(filep);
673 if (ch == next)
674 return yes;
675 ungetc(ch, filep);
676 return no;
677 }
678
679 static int
680 operand(int ch)
681 {
682 int peekc;
683
684 switch (ch) {
685 case '\n':
686 case '{':
687 case '}':
688 case '[':
689 case ']':
690 case '(':
691 case ')':
692 case ',':
693 case ';':
694 return ch;
695 case '.':
696 peekc = ungetc(getc(filep), filep);
697 if (strchr(DIGITS, peekc))
698 return number(ch);
699 return DOT;
700 case '"':
701 return string(ch);
702 case '*':
703 yylval.str = "*";
704 return follow('=', EQOP, '*');
705 case '/':
706 yylval.str = "/";
707 return follow('=', EQOP, '/');
708 case '%':
709 yylval.str = "%";
710 return follow('=', EQOP, '%');
711 case '=':
712 return follow('=', EQ, '=');
713 case '+':
714 case '-':
715 yylval.str = (ch == '+') ? "+" : "-";
716 if (follow('=', EQOP, ch) != ch)
717 return EQOP;
718 return follow(ch, INCDEC, ch);
719 case '^':
720 yylval.str = "^";
721 return follow('=', EQOP, '^');
722 case '<':
723 return follow('=', LE, '<');
724 case '>':
725 return follow('=', GE, '>');
726 case '!':
727 if (getc(filep) == '=')
728 return NE;
729 default:
730 yyerror("invalid operand");
731 return 0;
732 }
733 }
734
735 static void
736 comment(void)
737 {
738 int c;
739
740 for (;;) {
741 while ((c = getc(filep)) != '*') {
742 if (c == '\n')
743 lineno++;
744 }
745 if ((c = getc(filep)) == '/')
746 break;
747 ungetc(c, filep);
748 }
749 }
750
751 static int
752 yylex(void)
753 {
754 int peekc, ch;
755
756 repeat:
757 skipspaces();
758
759 ch = getc(filep);
760 if (ch == EOF) {
761 return EOF;
762 } else if (!isascii(ch)) {
763 yyerror("invalid input character");
764 } else if (islower(ch)) {
765 return iden(ch);
766 } else if (strchr(DIGITS, ch)) {
767 return number(ch);
768 } else {
769 if (ch == '/') {
770 peekc = getc(filep);
771 if (peekc == '*') {
772 comment();
773 goto repeat;
774 }
775 ungetc(peekc, filep);
776 }
777 return operand(ch);
778 }
779
780 return 0;
781 }
782
783 static void
784 spawn(void)
785 {
786 int fds[2];
787 char errmsg[] = "bc:error execing dc\n";
788
789 if (pipe(fds) < 0)
790 eprintf("creating pipe:");
791
792 switch (fork()) {
793 case -1:
794 eprintf("forking dc:");
795 case 0:
796 close(1);
797 dup(fds[1]);
798 close(fds[0]);
799 close(fds[1]);
800 break;
801 default:
802 close(0);
803 dup(fds[0]);
804 close(fds[0]);
805 close(fds[1]);
806 execlp("dc", "dc", (char *) NULL);
807
808 /* it shouldn't happen */
809 write(3, errmsg, sizeof(errmsg)-1);
810 _Exit(2);
811 }
812 }
813
814 static void
815 run(void)
816 {
817 if (setjmp(recover)) {
818 if (ferror(filep))
819 eprintf("%s:", filename);
820 if (feof(filep))
821 return;
822 }
823 yyparse();
824 }
825
826 static void
827 bc(char *fname)
828 {
829 Macro *d;
830
831 lineno = 1;
832 nested = 0;
833
834 macro(HOME);
835 if (!fname) {
836 filename = "<stdin>";
837 filep = stdin;
838 } else {
839 filename = fname;
840 if ((filep = fopen(fname, "r")) == NULL)
841 eprintf("%s:", fname);
842 }
843
844 run();
845 fclose(filep);
846 }
847
848 static void
849 usage(void)
850 {
851 eprintf("usage: %s [-cdls]\n", argv0);
852 }
853
854 int
855 main(int argc, char *argv[])
856 {
857 ARGBEGIN {
858 case 'c':
859 cflag = 1;
860 break;
861 case 'd':
862 dflag = 1;
863 yydebug = 3;
864 break;
865 case 'l':
866 lflag = 1;
867 break;
868 case 's':
869 sflag = 1;
870 break;
871 default:
872 usage();
873 } ARGEND
874
875 yytext = malloc(BUFSIZ);
876 buff = malloc(BUFSIZ);
877 if (!yytext || !buff)
878 eprintf("out of memory\n");
879 flowid = 128;
880
881 if (!cflag)
882 spawn();
883 if (lflag)
884 bc(PREFIX "/bc.library");
885
886 while (*argv)
887 bc(*argv++);
888 bc(NULL);
889
890 quit();
891 }