%{
#include "hoc.h"
#define code2(c1,c2)    code(c1); code(c2)
#define code3(c1,c2,c3) code(c1); code(c2); code(c3)
%}
%union {
       Symbol  *sym;   /* symbol table pointer */
       Inst    *inst;  /* machine instruction */
       int     narg;   /* number of arguments */
       Formal  *formals;       /* list of formal parameters */
}
%token  <sym>   NUMBER STRING PRINT VAR BLTIN UNDEF WHILE FOR IF ELSE
%token  <sym>   FUNCTION PROCEDURE RETURN FUNC PROC READ
%type   <formals>       formals
%type   <inst>  expr stmt asgn prlist stmtlist
%type   <inst>  cond while for if begin end
%type   <sym>   procname
%type   <narg>  arglist
%right  '=' ADDEQ SUBEQ MULEQ DIVEQ MODEQ
%left   OR
%left   AND
%left   GT GE LT LE EQ NE
%left   '+' '-'
%left   '*' '/' '%'
%left   UNARYMINUS NOT INC DEC
%right  '^'
%%
list:     /* nothing */
       | list '\n'
       | list defn '\n'
       | list asgn '\n'  { code2(xpop, STOP); return 1; }
       | list stmt '\n'  { code(STOP); return 1; }
       | list expr '\n'  { code2(printtop, STOP); return 1; }
       | list error '\n' { yyerrok; }
       ;
asgn:     VAR '=' expr { code3(varpush,(Inst)$1,assign); $$=$3; }
       | VAR ADDEQ expr        { code3(varpush,(Inst)$1,addeq); $$=$3; }
       | VAR SUBEQ expr        { code3(varpush,(Inst)$1,subeq); $$=$3; }
       | VAR MULEQ expr        { code3(varpush,(Inst)$1,muleq); $$=$3; }
       | VAR DIVEQ expr        { code3(varpush,(Inst)$1,diveq); $$=$3; }
       | VAR MODEQ expr        { code3(varpush,(Inst)$1,modeq); $$=$3; }
       ;
stmt:     expr  { code(xpop); }
       | RETURN { defnonly("return"); code(procret); }
       | RETURN expr
               { defnonly("return"); $$=$2; code(funcret); }
       | PROCEDURE begin '(' arglist ')'
               { $$ = $2; code3(call, (Inst)$1, (Inst)$4); }
       | PRINT prlist  { $$ = $2; }
       | while '(' cond ')' stmt end {
               ($1)[1] = (Inst)$5;     /* body of loop */
               ($1)[2] = (Inst)$6; }   /* end, if cond fails */
       | for '(' cond ';' cond ';' cond ')' stmt end {
               ($1)[1] = (Inst)$5;     /* condition */
               ($1)[2] = (Inst)$7;     /* post loop */
               ($1)[3] = (Inst)$9;     /* body of loop */
               ($1)[4] = (Inst)$10; }  /* end, if cond fails */
       | if '(' cond ')' stmt end {    /* else-less if */
               ($1)[1] = (Inst)$5;     /* thenpart */
               ($1)[3] = (Inst)$6; }   /* end, if cond fails */
       | if '(' cond ')' stmt end ELSE stmt end {      /* if with else */
               ($1)[1] = (Inst)$5;     /* thenpart */
               ($1)[2] = (Inst)$8;     /* elsepart */
               ($1)[3] = (Inst)$9; }   /* end, if cond fails */
       | '{' stmtlist '}'      { $$ = $2; }
       ;
cond:      expr         { code(STOP); }
       ;
while:    WHILE { $$ = code3(whilecode,STOP,STOP); }
       ;
for:      FOR   { $$ = code(forcode); code3(STOP,STOP,STOP); code(STOP); }
       ;
if:       IF    { $$ = code(ifcode); code3(STOP,STOP,STOP); }
       ;
begin:    /* nothing */         { $$ = progp; }
       ;
end:      /* nothing */         { code(STOP); $$ = progp; }
       ;
stmtlist: /* nothing */         { $$ = progp; }
       | stmtlist '\n'
       | stmtlist stmt
       ;
expr:     NUMBER { $$ = code2(constpush, (Inst)$1); }
       | VAR    { $$ = code3(varpush, (Inst)$1, eval); }
       | asgn
       | FUNCTION begin '(' arglist ')'
               { $$ = $2; code3(call,(Inst)$1,(Inst)$4); }
       | READ '(' VAR ')' { $$ = code2(varread, (Inst)$3); }
       | BLTIN '(' expr ')' { $$=$3; code2(bltin, (Inst)$1->u.ptr); }
       | '(' expr ')'  { $$ = $2; }
       | expr '+' expr { code(add); }
       | expr '-' expr { code(sub); }
       | expr '*' expr { code(mul); }
       | expr '/' expr { code(div); }
       | expr '%' expr { code(mod); }
       | expr '^' expr { code (power); }
       | '-' expr   %prec UNARYMINUS   { $$=$2; code(negate); }
       | expr GT expr  { code(gt); }
       | expr GE expr  { code(ge); }
       | expr LT expr  { code(lt); }
       | expr LE expr  { code(le); }
       | expr EQ expr  { code(eq); }
       | expr NE expr  { code(ne); }
       | expr AND expr { code(and); }
       | expr OR expr  { code(or); }
       | NOT expr      { $$ = $2; code(not); }
       | INC VAR       { $$ = code2(preinc,(Inst)$2); }
       | DEC VAR       { $$ = code2(predec,(Inst)$2); }
       | VAR INC       { $$ = code2(postinc,(Inst)$1); }
       | VAR DEC       { $$ = code2(postdec,(Inst)$1); }
       ;
prlist:   expr                  { code(prexpr); }
       | STRING                { $$ = code2(prstr, (Inst)$1); }
       | prlist ',' expr       { code(prexpr); }
       | prlist ',' STRING     { code2(prstr, (Inst)$3); }
       ;
defn:     FUNC procname { $2->type=FUNCTION; indef=1; }
           '(' formals ')' stmt { code(procret); define($2, $5); indef=0; }
       | PROC procname { $2->type=PROCEDURE; indef=1; }
           '(' formals ')' stmt { code(procret); define($2, $5); indef=0; }
       ;
formals:        { $$ = 0; }
       | VAR                   { $$ = formallist($1, 0); }
       | VAR ',' formals       { $$ = formallist($1, $3); }
       ;
procname: VAR
       | FUNCTION
       | PROCEDURE
       ;
arglist:  /* nothing */         { $$ = 0; }
       | expr                  { $$ = 1; }
       | arglist ',' expr      { $$ = $1 + 1; }
       ;
%%
       /* end of grammar */
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ctype.h>
char    *progname;
int     lineno = 1;
jmp_buf begin;
int     indef;
char    *infile;        /* input file name */
Biobuf  *bin;           /* input file descriptor */
Biobuf  binbuf;
char    **gargv;        /* global argument list */
int     gargc;

int c = '\n';   /* global for use by warning() */

int     backslash(int), follow(int, int, int);
void    defnonly(char*), run(void);
void    warning(char*, char*);

yylex(void)             /* hoc6 */
{
       while ((c=Bgetc(bin)) == ' ' || c == '\t')
               ;
       if (c < 0)
               return 0;
       if (c == '\\') {
               c = Bgetc(bin);
               if (c == '\n') {
                       lineno++;
                       return yylex();
               }
       }
       if (c == '#') {         /* comment */
               while ((c=Bgetc(bin)) != '\n' && c >= 0)
                       ;
               if (c == '\n')
                       lineno++;
               return c;
       }
       if (c == '.' || isdigit(c)) {   /* number */
               double d;
               Bungetc(bin);
               Bgetd(bin, &d);
               yylval.sym = install("", NUMBER, d);
               return NUMBER;
       }
       if (isalpha(c) || c == '_' || c >= 0x80) {
               Symbol *s;
               char sbuf[100], *p = sbuf;
               do {
                       if (p >= sbuf + sizeof(sbuf) - 1) {
                               *p = '\0';
                               execerror("name too long", sbuf);
                       }
                       *p++ = c;
               } while ((c=Bgetc(bin)) >= 0 && (isalnum(c) || c == '_' || c >= 0x80));
               Bungetc(bin);
               *p = '\0';
               if ((s=lookup(sbuf)) == 0)
                       s = install(sbuf, UNDEF, 0.0);
               yylval.sym = s;
               return s->type == UNDEF ? VAR : s->type;
       }
       if (c == '"') { /* quoted string */
               char sbuf[100], *p;
               for (p = sbuf; (c=Bgetc(bin)) != '"'; p++) {
                       if (c == '\n' || c == Beof)
                               execerror("missing quote", "");
                       if (p >= sbuf + sizeof(sbuf) - 1) {
                               *p = '\0';
                               execerror("string too long", sbuf);
                       }
                       *p = backslash(c);
               }
               *p = 0;
               yylval.sym = (Symbol *)emalloc(strlen(sbuf)+1);
               strcpy((char*)yylval.sym, sbuf);
               return STRING;
       }
       switch (c) {
       case '+':       return follow('+', INC, '+') == INC ? INC : follow('=', ADDEQ, '+');
       case '-':       return follow('-', DEC, '-') == DEC ? DEC : follow('=', SUBEQ, '-');
       case '*':       return follow('=', MULEQ, '*');
       case '/':       return follow('=', DIVEQ, '/');
       case '%':       return follow('=', MODEQ, '%');
       case '>':       return follow('=', GE, GT);
       case '<':       return follow('=', LE, LT);
       case '=':       return follow('=', EQ, '=');
       case '!':       return follow('=', NE, NOT);
       case '|':       return follow('|', OR, '|');
       case '&':       return follow('&', AND, '&');
       case '\n':      lineno++; return '\n';
       default:        return c;
       }
}

backslash(int c)        /* get next char with \'s interpreted */
{
       static char transtab[] = "b\bf\fn\nr\rt\t";
       if (c != '\\')
               return c;
       c = Bgetc(bin);
       if (islower(c) && strchr(transtab, c))
               return strchr(transtab, c)[1];
       return c;
}

follow(int expect, int ifyes, int ifno) /* look ahead for >=, etc. */
{
       int c = Bgetc(bin);

       if (c == expect)
               return ifyes;
       Bungetc(bin);
       return ifno;
}

void
yyerror(char* s)        /* report compile-time error */
{
/*rob
       warning(s, (char *)0);
       longjmp(begin, 0);
rob*/
       execerror(s, (char *)0);
}

void
execerror(char* s, char* t)     /* recover from run-time error */
{
       warning(s, t);
       Bseek(bin, 0L, 2);              /* flush rest of file */
       restoreall();
       longjmp(begin, 0);
}

void
fpecatch(void)  /* catch floating point exceptions */
{
       execerror("floating point exception", (char *) 0);
}

void
intcatch(void)  /* catch interrupts */
{
       execerror("interrupt", 0);
}

void
run(void)       /* execute until EOF */
{
       setjmp(begin);
       for (initcode(); yyparse(); initcode())
               execute(progbase);
}

void
main(int argc, char* argv[])    /* hoc6 */
{
       static int first = 1;
#ifdef YYDEBUG
       extern int yydebug;
       yydebug=3;
#endif
       progname = argv[0];
       init();
       if (argc == 1) {        /* fake an argument list */
               static char *stdinonly[] = { "-" };

               gargv = stdinonly;
               gargc = 1;
       } else if (first) {     /* for interrupts */
               first = 0;
               gargv = argv+1;
               gargc = argc-1;
       }
       Binit(&binbuf, 0, OREAD);
       bin = &binbuf;
       while (moreinput())
               run();
       exits(0);
}

moreinput(void)
{
       char *expr;
       static char buf[64];
       int fd;
       static Biobuf b;

       if (gargc-- <= 0)
               return 0;
       if (bin && bin != &binbuf)
               Bterm(bin);
       infile = *gargv++;
       lineno = 1;
       if (strcmp(infile, "-") == 0) {
               bin = &binbuf;
               infile = 0;
               return 1;
       }
       if(strncmp(infile, "-e", 2) == 0) {
               if(infile[2]==0){
                       if(gargc == 0){
                               fprint(2, "%s: no argument for -e\n", progname);
                               return 0;
                       }
                       gargc--;
                       expr = *gargv++;
               }else
                       expr = infile+2;
               sprint(buf, "/tmp/hocXXXXXXX");
               infile = mktemp(buf);
               fd = create(infile, ORDWR|ORCLOSE, 0600);
               if(fd < 0){
                       fprint(2, "%s: can't create temp. file: %r\n", progname);
                       return 0;
               }
               fprint(fd, "%s\n", expr);
               /* leave fd around; file will be removed on exit */
               /* the following looks weird but is required for unix version */
               bin = &b;
               seek(fd, 0, 0);
               Binit(bin, fd, OREAD);
       } else {
               bin=Bopen(infile, OREAD);
               if (bin == 0) {
                       fprint(2, "%s: can't open %s\n", progname, infile);
                       return moreinput();
               }
       }
       return 1;
}

void
warning(char* s, char* t)       /* print warning message */
{
       fprint(2, "%s: %s", progname, s);
       if (t)
               fprint(2, " %s", t);
       if (infile)
               fprint(2, " in %s", infile);
       fprint(2, " near line %d\n", lineno);
       while (c != '\n' && c != Beof)
               if((c = Bgetc(bin)) == '\n')    /* flush rest of input line */
                       lineno++;
}

void
defnonly(char *s)       /* warn if illegal definition */
{
       if (!indef)
               execerror(s, "used outside definition");
}