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