master xplshn/aruu / cmd / posix / bc.y
   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 *, 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 size_t buffcap;
  55static char *filename;
  56static FILE *filep;
  57static int lineno, nerr, flowid;
  58static jmp_buf recover;
  59static int nested, inhome;
  60static Macro macros[NESTED_MAX];
  61int cflag, dflag, lflag, sflag;
  62
  63static char *dcprog = "dc";
  64
  65%}
  66
  67%union {
  68	char *str;
  69	char id[16]; // matches dc's own REGSIZ: an extended register name's cap
  70	Macro *macro;
  71}
  72
  73%token <id> ID
  74%token <str> STRING NUMBER
  75%token <str> EQOP '+' '-' '*' '/' '%' '^' INCDEC
  76%token HOME LOOP
  77%token DOT
  78%token EQ
  79%token LE
  80%token GE
  81%token NE
  82%token DEF
  83%token BREAK
  84%token QUIT
  85%token LENGTH
  86%token RETURN
  87%token FOR
  88%token IF
  89%token WHILE
  90%token SQRT
  91%token SCALE
  92%token IBASE
  93%token OBASE
  94%token AUTO PARAM
  95%token PRINT
  96%token ELSE
  97%token HALT
  98%token READ
  99
 100%type <str> item statlst scolonlst
 101%type <str> function assign nexpr expr exprstat rel stat ary cond
 102%type <str> autolst arglst parlst
 103%type <str> prlst pritem
 104%type <str> params param locals local
 105%type <macro> def if for while
 106
 107%right	'=' EQOP
 108%left	'+' '-'
 109%left	'*' '/' '%'
 110%right	'^'
 111
 112%start    program
 113
 114%%
 115
 116program  :
 117         | item program
 118         ;
 119
 120item     : scolonlst '\n'       {writeout($1);}
 121         | function             {writeout($1);}
 122         ;
 123
 124function : def parlst '{' '\n' autolst statlst '}' {$$ = funcode($1, $2, $5, $6);}
 125         ;
 126
 127scolonlst:                      {$$ = code("");}
 128         | stat
 129         | scolonlst ';' stat   {$$ = code("%s%s", $1, $3);}
 130         | scolonlst ';'
 131         ;
 132
 133statlst :                       {$$ = code("");}
 134        | stat
 135        | statlst '\n' stat     {$$ = code("%s%s", $1, $3);}
 136        | statlst ';' stat      {$$ = code("%s%s", $1, $3);}
 137        | statlst '\n'
 138        | statlst ';'
 139        ;
 140
 141stat    : exprstat
 142        | PRINT prlst           {$$ = $2;}
 143        | STRING                {$$ = code("[%s]P", $1);}
 144        | BREAK                 {$$ = brkcode();}
 145        | QUIT                  {quit();}
 146        | HALT                  {$$ = code("q");}
 147        | RETURN                {$$ = retcode(code(" 0"));}
 148        | RETURN expr           {$$ = retcode($2);}
 149        | RETURN '(' ')'        {$$ = retcode(code(" 0"));}
 150        | while cond optnl stat       {$$ = whilecode($1, $2, $4);}
 151        | if cond optnl stat          {$$ = ifcode($1, $2, $4, NULL);}
 152        | if cond optnl stat ELSE optnl stat  {$$ = ifcode($1, $2, $4, $7);}
 153        | '{' statlst '}'       {$$ = $2;}
 154        | for '(' expr ';' rel ';' expr ')' optnl stat  {$$ = forcode($1, $3, $5, $7, $10);}
 155        ;
 156
 157// real scripts routinely put a while/if/for's own body on the next
 158// line instead of right after the condition; nothing else in this
 159// grammar needs a newline here, since a body wrapped in { } already
 160// goes through statlst, which handles its own internal newlines
 161optnl   :
 162        | optnl '\n'
 163        ;
 164
 165// print takes a comma-separated list of any length, mixing strings
 166// and expressions freely: real scripts (the kernel's own timeconst.bc
 167// generator among them) routinely print three or more at once. no
 168// item here ever gets an automatic trailing newline of its own,
 169// matching real bc: whatever newlines appear come from the items
 170// themselves
 171prlst   : pritem
 172        | prlst ',' pritem      {$$ = code("%s%s", $1, $3);}
 173        ;
 174
 175pritem  : expr                  {$$ = code("%sn", $1);}
 176        | STRING                {$$ = code("[%s]P", $1);}
 177        ;
 178
 179while   : WHILE                 {$$ = macro(LOOP);}
 180        ;
 181
 182if      : IF                    {$$ = macro(IF);}
 183        ;
 184
 185for     : FOR                   {$$ = macro(LOOP);}
 186        ;
 187
 188def     : DEF ID                {$$ = macro(DEF);}
 189        ;
 190
 191parlst  : '(' ')'               {$$ = code("");}
 192        | '(' params ')'        {$$ = $2;}
 193        ;
 194
 195params  : param                 {$$ = param(NULL, $1);}
 196        | params ',' param      {$$ = param($1, $3);}
 197        ;
 198
 199param   : ID                    {$$ = var($1);}
 200        | ID '[' ']'            {$$ = ary($1);}
 201        ;
 202
 203autolst :                       {$$ = code("");}
 204        | AUTO locals '\n'      {$$ = $2;}
 205        | AUTO locals ';'       {$$ = $2;}
 206        ;
 207
 208locals  : local                 {$$ = local(NULL, $1);}
 209        | locals ',' local      {$$ = local($1, $3);}
 210        ;
 211
 212local   : ID                    {$$ = var($1);}
 213        | ID '[' ']'            {$$ = ary($1);}
 214        ;
 215
 216arglst  : expr
 217        | ID '[' ']'            {$$ = code("%s", ary($1));}
 218        | expr ',' arglst       {$$ = code("%s%s", $1, $3);}
 219        | ID '[' ']' ',' arglst {$$ = code("%s%s", ary($1), $5);}
 220        ;
 221
 222cond    : '(' rel ')'           {$$ = $2;}
 223        ;
 224
 225rel     : expr                  {$$ = code("%s 0!=", $1);}
 226        | expr EQ expr          {$$ = code("%s%s=", $1, $3);}
 227        | expr LE expr          {$$ = code("%s%s!<", $1, $3);}
 228        | expr GE expr          {$$ = code("%s%s!>", $1, $3);}
 229        | expr NE expr          {$$ = code("%s%s!=", $1, $3);}
 230        | expr '<' expr         {$$ = code("%s%s>", $1, $3);}
 231        | expr '>' expr         {$$ = code("%s%s<", $1, $3);}
 232        ;
 233
 234exprstat: nexpr                 {$$ = code("%s%ss.", $1, code(sflag ? "" : "p"));}
 235        | assign                {$$ = code("%ss.", $1);}
 236        ;
 237
 238expr    : nexpr
 239        | assign
 240        ;
 241
 242nexpr   : NUMBER                {$$ = code(" %s", code($1));}
 243        | ID                    {$$ = code("l%s", var($1));}
 244        | READ '(' ')'          {$$ = code("?");}
 245        | DOT                   {$$ = code("l.");}
 246        | SCALE                 {$$ = code("K");}
 247        | IBASE                 {$$ = code("I");}
 248        | OBASE                 {$$ = code("O");}
 249        | ID ary                {$$ = code("%s;%s", $2, ary($1));}
 250        | '(' expr ')'          {$$ = $2;}
 251        | ID '(' arglst ')'     {$$ = code("%sl%sx", $3, ftn($1));}
 252        | ID '(' ')'            {$$ = code("l%sx", ftn($1));}
 253        | '-' expr              {$$ = code("0%s-", $2);}
 254        | expr '+' expr         {$$ = code("%s%s+", $1, $3);}
 255        | expr '-' expr         {$$ = code("%s%s-", $1, $3);}
 256        | expr '*' expr         {$$ = code("%s%s*", $1, $3);}
 257        | expr '/' expr         {$$ = code("%s%s/", $1, $3);}
 258        | expr '%' expr         {$$ = code("%s%s%%", $1, $3);}
 259        | expr '^' expr         {$$ = code("%s%s^", $1, $3);}
 260        | LENGTH '(' expr ')'   {$$ = code("%sZ", $3);}
 261        | SQRT '(' expr ')'     {$$ = code("%sv", $3);}
 262        | SCALE '(' expr ')'    {$$ = code("%sX", $3);}
 263        | INCDEC ID             {$$ = code("l%s1%sds%s", var($2), code($1), var($2));}
 264        | INCDEC SCALE          {$$ = code("K1%sk", code($1));}
 265        | INCDEC IBASE          {$$ = code("I1%sdi", code($1));}
 266        | INCDEC OBASE          {$$ = code("O1%sdo", code($1));}
 267        | INCDEC ID ary         {$$ = code("%sdS_;%s1%sdL_:%s", $3, ary($2), code($1), ary($2));}
 268        | ID INCDEC             {$$ = code("l%sd1%ss%s", var($1), code($2), var($1));}
 269        | SCALE INCDEC          {$$ = code("Kd1%sk", code($2));}
 270        | IBASE INCDEC          {$$ = code("Id1%si", code($2));}
 271        | OBASE INCDEC          {$$ = code("Od1%so", code($2));}
 272        | ID ary INCDEC         {$$ = code("%sds.;%sd1%sl.:%s", $2, ary($1), code($3), ary($1));}
 273        ;
 274
 275assign  : ID '=' expr           {$$ = code("%sds%s", $3, var($1));}
 276        | SCALE '=' expr        {$$ = code("%sdk", $3);}
 277        | IBASE '=' expr        {$$ = code("%sdi", $3);}
 278        | OBASE '=' expr        {$$ = code("%sdo", $3);}
 279        | ID ary '=' expr       {$$ = code("%sd%s:%s", $4, $2, ary($1));}
 280        | ID EQOP expr          {$$ = code("%sl%s%sds%s", $3, var($1), code($2), var($1));}
 281        | SCALE EQOP expr       {$$ = code("%sK%sdk", $3, code($2));}
 282        | IBASE EQOP expr       {$$ = code("%sI%sdi", $3, code($2));}
 283        | OBASE EQOP expr       {$$ = code("%sO%sdo", $3, code($2));}
 284        | ID ary EQOP expr      {$$ = code("%s%sds.;%s%sdl.:s", $4, $2, ary($1), code($3), ary($1));}
 285        ;
 286
 287ary     : '[' expr ']'          {$$ = $2;}
 288        ;
 289
 290%%
 291static int
 292yyerror(char *s)
 293{
 294	fprintf(stderr, "bc: %s:%d: %s\n", filename, lineno, s);
 295	nerr++;
 296	longjmp(recover, 1);
 297}
 298
 299static void
 300writeout(char *s)
 301{
 302	if (write(1, s, strlen(s)) < 0)
 303		goto err;
 304	if (write(1, "\n", 1) < 0)
 305		goto err;
 306	free(s);
 307	return;
 308
 309err:
 310	eprintf("writing to dc:");
 311}
 312
 313// grows the shared code buffer geometrically to fit at least 'need'
 314// bytes: a single generated dc macro (an if/else body, a print list,
 315// a deeply nested function) has no fixed upper bound, unlike the
 316// stdio-buffering constant BUFSIZ this buffer borrowed its original
 317// size from
 318static void
 319growbuff(size_t need)
 320{
 321	size_t newcap;
 322
 323	newcap = buffcap;
 324	while (newcap < need)
 325		newcap *= 2;
 326	buff    = erealloc(buff, newcap);
 327	buffcap = newcap;
 328}
 329
 330static char *
 331code(char *fmt, ...)
 332{
 333	char  *t;
 334	va_list ap;
 335	int    c, len;
 336	size_t off, room;
 337
 338	va_start(ap, fmt);
 339	for (off = 0; *fmt; off += (size_t)len) {
 340		len = 1;
 341		if ((c = *fmt++) != '%')
 342			goto append;
 343
 344		switch (*fmt++) {
 345		case 'd':
 346			c = va_arg(ap, int);
 347			for (;;) {
 348				room = buffcap - off;
 349				len  = snprintf(buff + off, room, "%d", c);
 350				if (len < 0)
 351					eprintf("unable to code requested operation\n");
 352				if ((size_t)len < room)
 353					break;
 354				growbuff(off + (size_t)len + 1);
 355			}
 356			break;
 357		case 'c':
 358			c = va_arg(ap, int);
 359			goto append;
 360		case 's':
 361			t   = va_arg(ap, void *);
 362			len = strlen(t);
 363			if ((size_t)len >= buffcap - off)
 364				growbuff(off + (size_t)len + 1);
 365			memcpy(buff + off, t, len);
 366			free(t);
 367			break;
 368		case '%':
 369		append:
 370			if (buffcap - off <= 1)
 371				growbuff(off + 2);
 372			buff[off] = c;
 373			break;
 374		default:
 375			abort();
 376		}
 377	}
 378	va_end(ap);
 379
 380	buff[off] = '\0';
 381	return estrdup(buff);
 382}
 383
 384static Macro *
 385macro(int op)
 386{
 387	int preop;
 388	Macro *d, *p;
 389
 390	if (nested == NESTED_MAX)
 391		yyerror("too much nesting");
 392
 393	d = &macros[nested];
 394	d->op = op;
 395	d->nested = nested++;
 396	d->name = NULL;
 397
 398	switch (op) {
 399	case HOME:
 400		d->id = 0;
 401		d->flowid = flowid;
 402		inhome = 1;
 403		break;
 404	case DEF:
 405		unwind = estrdup("");
 406		inhome = 0;
 407		d->id = funid(yytext);
 408		d->name = estrdup(yytext);
 409		d->flowid = macros[0].flowid;
 410		break;
 411	default:
 412		assert(nested > 1);
 413		preop = d[-1].op;
 414		d->flowid = d[-1].flowid;
 415		if (preop != HOME && preop != DEF) {
 416			if (d->flowid == 255)
 417				eprintf("too many control flow structures");
 418			d->flowid++;
 419		}
 420		d->id = d->flowid;
 421		if (!inhome) {
 422			/* populate reserved id */
 423			flowid = d->flowid;
 424			for (p = d; p != macros; --p)
 425				p[-1].flowid++;
 426		}
 427		break;
 428	}
 429
 430	return d;
 431}
 432
 433static char *
 434decl(int type, char *list, char *id)
 435{
 436	char *i1, *i2;
 437
 438	i1 = estrdup(id);
 439	i2 = estrdup(id);
 440	free(id);
 441
 442	if (!list)
 443		list = estrdup("");
 444
 445	unwind = code("%sL%ss.", unwind, i1);
 446
 447	return code((type == AUTO) ? "0S%s%s" : "S%s%s", i2, list);
 448}
 449
 450static char *
 451param(char *list, char *id)
 452{
 453	return decl(PARAM, list, id);
 454}
 455
 456static char *
 457local(char *list, char *id)
 458{
 459	return decl(AUTO, list, id);
 460}
 461
 462static char *
 463funcode(Macro *d, char *params, char *vars, char *body)
 464{
 465	char *s;
 466
 467	if (strlen(d->name) > 1) {
 468		s = code("[%s%s%s%s]s\"()%s\"",
 469			 vars, params,
 470			 body,
 471			 retcode(code(" 0")),
 472			 d->name);
 473	} else {
 474		s = code(sflag ? "[%s%s%s%s]s<%d>" : "[%s%s%s%s]s%c",
 475			 vars, params,
 476			 body,
 477			 retcode(code(" 0")),
 478			 d->id);
 479		free(d->name);
 480	}
 481
 482	free(unwind);
 483	unwind = NULL;
 484	nested--;
 485	inhome = 0;
 486
 487	return s;
 488}
 489
 490static char *
 491brkcode(void)
 492{
 493	Macro *d;
 494
 495	for (d = &macros[nested-1]; d->op != HOME && d->op != LOOP; --d)
 496		;
 497	if (d->op == HOME)
 498		yyerror("break not in for or while");
 499	return code(" %dQ", nested  - d->nested);
 500}
 501
 502static char *
 503forcode(Macro *d, char *init, char *cmp, char *inc, char *body)
 504{
 505	char *s;
 506
 507	s = code(sflag ? "[%s%ss.%s<%d>]s<%d>" : "[%s%ss.%s%c]s%c",
 508	         body,
 509	         inc,
 510	         estrdup(cmp),
 511	         d->id, d->id);
 512	writeout(s);
 513
 514	s = code(sflag ? "%ss.%s<%d> " : "%ss.%s%c ",
 515	         init,
 516	         cmp,
 517	         d->id);
 518	nested--;
 519
 520	return s;
 521}
 522
 523static char *
 524whilecode(Macro *d, char *cmp, char *body)
 525{
 526	char *s;
 527
 528	s = code(sflag ? "[%s%s<%d>]s<%d>" : "[%s%s%c]s%c",
 529	         body,
 530	         estrdup(cmp),
 531	         d->id, d->id);
 532	writeout(s);
 533
 534	s = code(sflag ? "%s<%d> " : "%s%c ",
 535	         cmp, d->id);
 536	nested--;
 537
 538	return s;
 539}
 540
 541// the trailing relational operator on a cond's own generated code
 542// (see the rel grammar rule) is always one of these six forms; an
 543// else branch runs on whichever of them means "the opposite happened"
 544static char *
 545invertrel(char *cmp)
 546{
 547	size_t n = strlen(cmp);
 548	const char *suffix;
 549	size_t keep;
 550	char *s;
 551
 552	if (n >= 2 && strcmp(cmp + n - 2, "!=") == 0) {
 553		keep = n - 2;
 554		suffix = "=";
 555	} else if (n >= 2 && strcmp(cmp + n - 2, "!<") == 0) {
 556		keep = n - 2;
 557		suffix = "<";
 558	} else if (n >= 2 && strcmp(cmp + n - 2, "!>") == 0) {
 559		keep = n - 2;
 560		suffix = ">";
 561	} else if (n >= 1 && cmp[n - 1] == '=') {
 562		keep = n - 1;
 563		suffix = "!=";
 564	} else if (n >= 1 && cmp[n - 1] == '<') {
 565		keep = n - 1;
 566		suffix = "!<";
 567	} else if (n >= 1 && cmp[n - 1] == '>') {
 568		keep = n - 1;
 569		suffix = "!>";
 570	} else {
 571		yyerror("cannot invert comparison for else");
 572		return NULL;
 573	}
 574
 575	s = emalloc(keep + strlen(suffix) + 1);
 576	memcpy(s, cmp, keep);
 577	strcpy(s + keep, suffix);
 578
 579	return s;
 580}
 581
 582// an else branch's own stored body needs a register distinct from the
 583// then branch's d->id: nested caps how high d->id itself ever goes,
 584// so offsetting by it lands on a register no other nesting level, and
 585// no ordinary (single-letter or, under -i, named) variable, ever uses
 586static char *
 587ifcode(Macro *d, char *cmp, char *body, char *ebody)
 588{
 589	char *s, *inv;
 590
 591	s = code(sflag ? "[%s]s<%d>" : "[%s]s%c",
 592	         body, d->id);
 593	writeout(s);
 594
 595	if (ebody) {
 596		s = code(sflag ? "[%s]s<%d>" : "[%s]s%c",
 597		         ebody, d->id + NESTED_MAX);
 598		writeout(s);
 599
 600		inv = invertrel(cmp);
 601		s = code(sflag ? "%s<%d> %s<%d> " : "%s%c %s%c ",
 602		         cmp, d->id, inv, d->id + NESTED_MAX);
 603	} else {
 604		s = code(sflag ? "%s<%d> " : "%s%c ",
 605		         cmp, d->id);
 606	}
 607	nested--;
 608
 609	return s;
 610}
 611
 612static char *
 613retcode(char *expr)
 614{
 615	if (nested < 2 || macros[1].op != DEF)
 616		yyerror("return must be in a function");
 617	return code("%s %s %dQ", expr, estrdup(unwind), nested - 1);
 618}
 619
 620static char *
 621ary(char *s)
 622{
 623	if (strlen(s) == 1)
 624		return code("%c", toupper(s[0]));
 625	return code("\"[]%s\"", estrdup(s));
 626}
 627
 628static char *
 629ftn(char *s)
 630{
 631	if (strlen(s) == 1)
 632		return code(sflag ? "<%d>" : "%c", funid(s));
 633	return code("\"()%s\"", estrdup(s));
 634}
 635
 636static char *
 637var(char *s)
 638{
 639	if (strlen(s) == 1)
 640		return code(s);
 641	return code("\"%s\"", estrdup(s));
 642}
 643
 644static void
 645quit(void)
 646{
 647	exit(nerr > 0 ? 1 : 0);
 648}
 649
 650static void
 651skipspaces(void)
 652{
 653	int ch;
 654
 655	while (isascii(ch = getc(filep)) && isspace(ch)) {
 656		if (ch == '\n') {
 657			lineno++;
 658			break;
 659		}
 660	}
 661	ungetc(ch, filep);
 662}
 663
 664static int
 665iden(int ch)
 666{
 667	static struct keyword {
 668		char *str;
 669		int token;
 670	} keywords[] = {
 671		{"define", DEF},
 672		{"break", BREAK},
 673		{"quit", QUIT},
 674		{"length", LENGTH},
 675		{"return", RETURN},
 676		{"for", FOR},
 677		{"if", IF},
 678		{"while", WHILE},
 679		{"sqrt", SQRT},
 680		{"scale", SCALE},
 681		{"ibase", IBASE},
 682		{"obase", OBASE},
 683		{"auto", AUTO},
 684		{"print", PRINT},
 685		{"else", ELSE},
 686		{"halt", HALT},
 687		{"read", READ},
 688		{NULL}
 689	};
 690	struct keyword *p;
 691	char *bp;
 692
 693	ungetc(ch, filep);
 694	for (bp = yytext; bp < &yytext[BUFSIZ]; ++bp) {
 695		ch = getc(filep);
 696		if (!isascii(ch) || !islower(ch))
 697			break;
 698		*bp = ch;
 699	}
 700
 701	if (bp == &yytext[BUFSIZ])
 702		yyerror("too long token");
 703	*bp = '\0';
 704	ungetc(ch, filep);
 705
 706	if (strlen(yytext) == 1) {
 707		strcpy(yylval.id, yytext);
 708		return ID;
 709	}
 710
 711	for (p = keywords; p->str && strcmp(p->str, yytext); ++p)
 712		;
 713	if (p->str)
 714		return p->token;
 715
 716	// strict POSIX bc only ever names a variable/function with a
 717	// single lowercase letter; every real bc script in the wild
 718	// (the kernel's own timeconst.bc among them) freely uses
 719	// multi-character names anyway, same as GNU bc's own default
 720#if !FEATURE_BC_MULTICHAR
 721	if (!sflag)
 722		yyerror("invalid keyword");
 723#endif
 724	if (strlen(yytext) >= sizeof(yylval.id))
 725		yyerror("identifier too long");
 726	strcpy(yylval.id, yytext);
 727	return ID;
 728}
 729
 730static char *
 731digits(char *bp)
 732{
 733	int ch;
 734	char *digits = DIGITS, *p;
 735
 736	while (bp < &yytext[BUFSIZ]) {
 737		ch = getc(filep);
 738		p = strchr(digits, ch);
 739		if (!p)
 740			break;
 741		*bp++ = ch;
 742	}
 743
 744	if (bp == &yytext[BUFSIZ])
 745		return NULL;
 746	ungetc(ch, filep);
 747
 748	return bp;
 749}
 750
 751static int
 752number(int ch)
 753{
 754	char *bp;
 755
 756	ungetc(ch, filep);
 757	if ((bp = digits(yytext)) == NULL)
 758		goto toolong;
 759
 760	if ((ch = getc(filep)) != '.') {
 761		ungetc(ch, filep);
 762		goto end;
 763	}
 764	*bp++ = '.';
 765
 766	if ((bp = digits(bp)) == NULL)
 767		goto toolong;
 768
 769end:
 770	if (bp ==  &yytext[BUFSIZ])
 771		goto toolong;
 772	*bp = '\0';
 773	yylval.str = yytext;
 774
 775	return NUMBER;
 776
 777toolong:
 778	yyerror("too long number");
 779	return 0;
 780}
 781
 782static int
 783string(int ch)
 784{
 785	char *bp;
 786
 787	for (bp = yytext; bp < &yytext[BUFSIZ]; ++bp) {
 788		if ((ch = getc(filep)) == '"')
 789			break;
 790#if FEATURE_BC_ESCAPES
 791		// strict POSIX bc has no escape sequences in a string at
 792		// all; every real script (again, the kernel's own scripts
 793		// among them) assumes GNU bc's \n/\t/etc, since dc's own
 794		// string reader passes a literal byte straight through
 795		// with nothing left to turn it into a real newline later
 796		if (ch == '\\') {
 797			switch (ch = getc(filep)) {
 798			case 'a':
 799				ch = '\a';
 800				break;
 801			case 'b':
 802				ch = '\b';
 803				break;
 804			case 'f':
 805				ch = '\f';
 806				break;
 807			case 'n':
 808				ch = '\n';
 809				break;
 810			case 'r':
 811				ch = '\r';
 812				break;
 813			case 't':
 814				ch = '\t';
 815				break;
 816			case 'v':
 817				ch = '\v';
 818				break;
 819			case 'q':
 820				// GNU bc's own escape for a literal double
 821				// quote, needed since a bc string's own
 822				// delimiter is " with no other way in
 823				ch = '"';
 824				break;
 825			case '\\':
 826			case '"':
 827				break;
 828			default:
 829				*bp++ = '\\';
 830				break;
 831			}
 832		}
 833#endif
 834		*bp = ch;
 835	}
 836
 837	if (bp == &yytext[BUFSIZ])
 838		yyerror("too long string");
 839	*bp = '\0';
 840	yylval.str = estrdup(yytext);
 841
 842	return STRING;
 843}
 844
 845static int
 846follow(int next, int yes, int no)
 847{
 848	int ch;
 849
 850	ch = getc(filep);
 851	if (ch == next)
 852		return yes;
 853	ungetc(ch, filep);
 854	return no;
 855}
 856
 857static int
 858operand(int ch)
 859{
 860	int peekc;
 861
 862	switch (ch) {
 863	case '\n':
 864	case '{':
 865	case '}':
 866	case '[':
 867	case ']':
 868	case '(':
 869	case ')':
 870	case ',':
 871	case ';':
 872		return ch;
 873	case '.':
 874		peekc = ungetc(getc(filep), filep);
 875		if (strchr(DIGITS, peekc))
 876			return number(ch);
 877		return DOT;
 878	case '"':
 879		return string(ch);
 880	case '*':
 881		yylval.str = "*";
 882		return follow('=', EQOP, '*');
 883	case '/':
 884		yylval.str = "/";
 885		return follow('=', EQOP, '/');
 886	case '%':
 887		yylval.str = "%";
 888		return follow('=', EQOP, '%');
 889	case '=':
 890		return follow('=', EQ, '=');
 891	case '+':
 892	case '-':
 893		yylval.str = (ch == '+') ? "+" : "-";
 894		if (follow('=', EQOP, ch) != ch)
 895			return EQOP;
 896		return follow(ch, INCDEC, ch);
 897	case '^':
 898		yylval.str = "^";
 899		return follow('=', EQOP, '^');
 900	case '<':
 901		return follow('=', LE, '<');
 902	case '>':
 903		return follow('=', GE, '>');
 904	case '!':
 905		if (getc(filep) == '=')
 906			return NE;
 907		/* fallthrough */
 908	default:
 909		yyerror("invalid operand");
 910		return 0;
 911	}
 912}
 913
 914static void
 915comment(void)
 916{
 917	int c;
 918
 919	for (;;) {
 920		while ((c = getc(filep)) != '*') {
 921			if (c == '\n')
 922				lineno++;
 923		}
 924		if ((c = getc(filep)) == '/')
 925			break;
 926		ungetc(c, filep);
 927	}
 928}
 929
 930static int
 931yylex(void)
 932{
 933	int peekc, ch;
 934
 935repeat:
 936	skipspaces();
 937
 938	ch = getc(filep);
 939	if (ch == EOF) {
 940		return EOF;
 941	} else if (!isascii(ch)) {
 942		yyerror("invalid input character");
 943	} else if (islower(ch)) {
 944		return iden(ch);
 945	} else if (strchr(DIGITS, ch)) {
 946		return number(ch);
 947	} else {
 948		if (ch == '/') {
 949			peekc = getc(filep);
 950			if (peekc == '*') {
 951				comment();
 952				goto repeat;
 953			}
 954			ungetc(peekc, filep);
 955		}
 956		return operand(ch);
 957	}
 958
 959	return 0;
 960}
 961
 962static void
 963spawn(void)
 964{
 965	int fds[2];
 966	// dc only accepts the extended, multi-character register
 967	// syntax bc.y's own ftn()/var()/ary() emit for a non-single-
 968	// letter name (see iden() above) when told -i: whenever this
 969	// build can produce that syntax at all, dc always needs it
 970#if FEATURE_BC_MULTICHAR
 971	char *par = "-i";
 972#else
 973	char *par = sflag ? "-i" : NULL;
 974#endif
 975	char errmsg[] = "bc:error execing dc\n";
 976
 977	// fd 0 is about to become the read end of the code pipe below,
 978	// but dc's own "?" (bc's read()) needs the real, original stdin:
 979	// stash it on a fixed fd dc knows to check, or read() would read
 980	// straight out of this project's own generated dc code instead
 981	// of whatever the user actually piped in. this has to happen
 982	// before pipe() claims its own two fds, or dup2 here could
 983	// clobber one of them (fd 3 is often the next one pipe() itself
 984	// would have picked)
 985	dup2(0, 3);
 986
 987	if (pipe(fds) < 0)
 988		eprintf("creating pipe:");
 989
 990	switch (fork()) {
 991	case -1:
 992		eprintf("forking dc:");
 993		/* fallthrough */
 994	case 0:
 995		close(1);
 996		dup(fds[1]);
 997		close(fds[0]);
 998		close(fds[1]);
 999		break;
1000	default:
1001		close(0);
1002		dup(fds[0]);
1003		close(fds[0]);
1004		close(fds[1]);
1005		execlp(dcprog, "dc", par, (char *) NULL);
1006
1007		/* it shouldn't happen */
1008		write(3, errmsg, sizeof(errmsg)-1);
1009		_Exit(2);
1010	}
1011}
1012
1013static void
1014run(void)
1015{
1016	if (setjmp(recover)) {
1017		if (ferror(filep))
1018			eprintf("%s:", filename);
1019		if (feof(filep))
1020			return;
1021	}
1022	yyparse();
1023}
1024
1025static void
1026bc(char *fname)
1027{
1028	lineno = 1;
1029	nested = 0;
1030
1031	macro(HOME);
1032	if (!fname) {
1033		filename = "<stdin>";
1034		filep = stdin;
1035	} else {
1036		filename = fname;
1037		if ((filep = fopen(fname, "r")) == NULL)
1038			eprintf("%s:", fname);
1039	}
1040
1041	run();
1042	fclose(filep);
1043}
1044
1045static void
1046usage(void)
1047{
1048#if FEATURE_BC_QUIET
1049	eprintf("usage: %s [-p dc][-cdlqs]\n", argv0);
1050#else
1051	eprintf("usage: %s [-p dc][-cdls]\n", argv0);
1052#endif
1053}
1054
1055int
1056main(int argc, char *argv[])
1057{
1058	ARGBEGIN {
1059	case 'p':
1060		dcprog = EARGF(usage());
1061		break;
1062	case 'c':
1063		cflag = 1;
1064		break;
1065	case 'd':
1066		dflag = 1;
1067		yydebug = 3;
1068		break;
1069	case 'l':
1070		lflag = 1;
1071		break;
1072#if FEATURE_BC_QUIET
1073	// for compat with sucky impls
1074	case 'q':
1075		break;
1076#endif
1077	case 's':
1078		sflag = 1;
1079		break;
1080	default:
1081		usage();
1082	} ARGEND
1083
1084	yytext  = malloc(BUFSIZ);
1085	buffcap = BUFSIZ;
1086	buff    = malloc(buffcap);
1087	if (!yytext || !buff)
1088		eprintf("out of memory\n");
1089	flowid = 128;
1090
1091	if (!cflag)
1092		spawn();
1093	if (lflag)
1094		bc(PREFIX "/share/misc/bc.library");
1095
1096	// stdin is only the script source when no file was given at all:
1097	// a real file argument alongside piped stdin data (the kernel's
1098	// own "echo $HZ | bc -q file.bc" being the standing example) means
1099	// that data is meant for read(), not for bc itself to parse as
1100	// more of the program
1101	if (!*argv) {
1102		bc(NULL);
1103	} else {
1104		while (*argv)
1105			bc(*argv++);
1106	}
1107
1108	quit();
1109}