/* * As its sole author, I explicitly place this program in the public domain. * It may be used by anyone in any way for any purpose, though I would * appreciate credit where it's due. * Mouse, mouse@rodents-montreal.org, 2021-07-29 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #define DEFAULT_MAX_OUTPUT 65536 static char *scriptfile = 0; static char *ttydev = 0; static char *command = 0; static int tracing = 0; static int watch = 0; typedef unsigned long long int TIME; typedef enum { LINE_SEND = 1, LINE_PRINT, LINE_EXPECT, LINE_NEXT, LINE_DRAIN, LINE_AWAIT, LINE_DELAY, LINE_LOOP, LINE_BREAK, LINE_CONTINUE, LINE_SET, LINE_PARAM, LINE_IF, LINE_ELIF, LINE_ELSE, LINE_EXIT, } LINETYPE; typedef enum { X_IDENT = 1, X_NUM, X_STR, X_PAREN, X_TAG, X_NILARY, X_UNARY, X_BINARY, X_TERNARY, X_FXN, } EXPRTYPE; typedef enum { N_ARRAY = 1, N_FALSE, N_TRUE, } N_OP; typedef enum { U_MINUS = 1, U_LOGNOT, U_FOUND, } U_OP; typedef enum { B_ADD = 1, B_SUB, B_MUL, B_DIV, B_AND, B_OR, B_XOR, B_AREF, B_LT, B_GT, B_LE, B_GE, B_EQ, B_NE, B_ANDAND, B_OROR, B_XORXOR, B_REGEX, } B_OP; typedef enum { NUM_INT = 1, NUM_FLOAT, } NUMTYPE; typedef enum { VAL_NIL = 1, VAL_NUM, VAL_STR, VAL_ARR, VAL_TAG, VAL_LOG, } VALTYPE; typedef enum { PAR__ERR = 1, PAR_INTERCHAR, PAR_MAX_OUTPUT, } PARID; typedef struct linelist LINELIST; typedef struct exprlist EXPRLIST; typedef struct lines LINES; typedef struct line LINE; typedef struct expr EXPR; typedef struct ident IDENT; typedef struct number NUMBER; typedef struct string STRING; typedef struct parsectx PARSECTX; typedef struct val VAL; typedef struct array ARRAY; typedef struct ael AEL; typedef struct expectation EXPECTATION; typedef struct loopstack LOOPSTACK; struct linelist { LINELIST *link; LINE *line; } ; struct exprlist { EXPRLIST *link; EXPR *expr; } ; struct lines { int nlines; LINE **lines; } ; struct line { LINETYPE t; int inx; int lno; int indent; const unsigned char *text; int textlen; union { struct { // LINE_SEND EXPR *text; } l_send; struct { // LINE_PRINT EXPR *text; } l_print; struct { // LINE_EXPECT EXPR *tag; EXPR *want; } l_expect; struct { // LINE_NEXT EXPR *tag; EXPR *want; } l_next; struct { // LINE_DRAIN EXPR *timeout; } l_drain; struct { // LINE_AWAIT EXPR *timeout; EXPR *string; // may be nil } l_await; struct { // LINE_DELAY EXPR *timeout; } l_delay; struct { // LINE_LOOP EXPR *tag; // may be nil } l_loop; struct { // LINE_BREAK EXPR *tag; // may be nil } l_break; struct { // LINE_CONTINUE EXPR *tag; // may be nil } l_continue; struct { // LINE_SET EXPR *lhs; EXPR *rhs; } l_set; struct { // LINE_PARAM PARID parno; EXPR *rhs; } l_param; struct { // LINE_IF EXPR *cond; } l_if; struct { // LINE_ELIF EXPR *cond; } l_elif; // nothing for LINE_ELSE struct { // LINE_EXIT EXPR *ecode; // may be nil } l_exit; } u; } ; struct number { NUMTYPE t; union { long long int i; // NUM_INT double f; // NUM_FLOAT } u; } ; #define MAKENUM_INT(v) ((NUMBER){.t=NUM_INT,.u={.i=(v)}}) #define MAKENUM_FLOAT(v) ((NUMBER){.t=NUM_FLOAT,.u={.f=(v)}}) struct string { int refcnt; unsigned char *body; int len; } ; struct expr { EXPRTYPE t; union { IDENT *x_ident; // X_IDENT NUMBER x_num; // X_NUM STRING *x_str; // X_STR EXPR *x_paren; // X_PAREN EXPR *x_tag; // X_TAG struct { // X_NILARY N_OP op; } x_nilary; struct { // X_UNARY U_OP op; EXPR *arg; } x_unary; struct { // X_BINARY EXPR *lhs; B_OP op; EXPR *rhs; } x_binary; struct { // X_TERNARY // no op; we support only one ternary op EXPR *lhs; EXPR *mhs; EXPR *rhs; } x_ternary; struct { // X_FXN IDENT *fxn; int nargs; EXPR **args; } x_fxn; } u; } ; struct parsectx { const unsigned char *buf; int buflen; int lx; int lno; LINELIST *lines; LINELIST **tail; int errs; int err; int indent; const unsigned char *lp; int ll; int px; } ; struct val { VALTYPE t; union { // nothing for VAL_NIL #define MAKEVAL_NIL() ((VAL){.t=VAL_NIL}) NUMBER n; // VAL_NUM #define MAKEVAL_NUM(v) ((VAL){.t=VAL_NUM,.u={.n=(v)}}) STRING *s; // VAL_STR #define MAKEVAL_STR(v) ((VAL){.t=VAL_STR,.u={.s=(v)}}) ARRAY *a; // VAL_ARR #define MAKEVAL_ARR(v) ((VAL){.t=VAL_ARR,.u={.a=(v)}}) IDENT *t; // VAL_TAG #define MAKEVAL_TAG(v) ((VAL){.t=VAL_TAG,.u={.t=(v)}}) int l; // VAL_LOG #define MAKEVAL_LOG(v) ((VAL){.t=VAL_LOG,.u={.l=(v)?1:0}}) } u; } ; struct ident { IDENT *link; const char *name; int namelen; VAL value; } ; struct ael { AEL *link; VAL key; VAL val; } ; struct array { int refcnt; AEL *els; } ; struct expectation { EXPECTATION *link; VAL tag; STRING *str; unsigned int flags; #define EXPF_NEXT 0x00000001 int at; } ; struct loopstack { LOOPSTACK *link; VAL tag; int end; int through; unsigned int flags; #define LOOP_BREAK 0x00000001 } ; static IDENT *idents; static LINE **script; static int scriptlen; static int iofd; static unsigned char iorb[256]; static int iorf; static int iorx; static AIO_OQ iooq; static TIME ionexto; static EXPECTATION *expected; static VAL found; static LOOPSTACK *loops; static TIME interchar; static int max_output; static int devnull; static int watch_last; static const char *iotext; // Forwards static EXPR *parse_expr_top(PARSECTX *); static void val_drop(VAL); static VAL eval_tag(LINE *, EXPR *); static int run_beginning(int, int); static void nbio(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { if (! scriptfile) { scriptfile = *av; } else if (!ttydev && !command) { ttydev = *av; } else { fprintf(stderr,"%s: stray argument `%s'\n",__progname,*av); errs = 1; } continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs = 1; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-trace")) { tracing = 1; continue; } if (!strcmp(*av,"-watch")) { watch = 1; continue; } if (!strcmp(*av,"-tty")) { WANTARG(); ttydev = av[skip]; command = 0; continue; } if (!strcmp(*av,"-cmd")) { WANTARG(); command = av[skip]; ttydev = 0; continue; } // if (!strcmp(*av,"-timenet")) // { WANTARG(); // time_net = atoi(av[skip]); // continue; // } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs = 1; } if (! scriptfile) { fprintf(stderr,"%s: no script specified\n",__progname); errs = 1; } else if (! (ttydev || command)) { fprintf(stderr,"%s: no tty device nor command specified\n",__progname); errs = 1; } if (errs) { fprintf(stderr,"Usage: %s [-trace] [-watch] [-tty ttydev] [-cmd command] scriptfile [ttydev]\n",__progname); exit(1); } } static void parse_err(PARSECTX *, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void parse_err(PARSECTX *c, const char *fmt, ...) { va_list ap; char *s; int i; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"%s: %s: line %d: %s\n",__progname,scriptfile,c->lno,s); free(s); if (c->px >= 0) { fprintf(stderr,"\t%.*s\n",c->ll,c->lp); putc('\t',stderr); for (i=0;ipx;i++) putc((c->lp[i]=='\t')?'\t':' ',stderr); putc('^',stderr); putc('\n',stderr); } c->err = 1; c->errs = 1; } static void ref_string(STRING *s) { s->refcnt ++; } static void deref_string(STRING *s) { if (s->refcnt < 1) abort(); s->refcnt --; if (s->refcnt > 0) return; free(s->body); free(s); } static void ref_array(ARRAY *a) { a->refcnt ++; } static void deref_array(ARRAY *a) { AEL *e; if (a->refcnt < 1) abort(); a->refcnt --; if (a->refcnt > 0) return; while ((e = a->els)) { a->els = e->link; val_drop(e->key); val_drop(e->val); free(e); } free(a); } static void expr_free(EXPR *e) { int i; switch (e->t) { case X_IDENT: break; case X_NUM: break; case X_STR: deref_string(e->u.x_str); break; case X_PAREN: expr_free(e->u.x_paren); break; case X_TAG: expr_free(e->u.x_tag); break; case X_NILARY: break; case X_UNARY: expr_free(e->u.x_unary.arg); break; case X_BINARY: expr_free(e->u.x_binary.lhs); expr_free(e->u.x_binary.rhs); break; case X_TERNARY: expr_free(e->u.x_ternary.lhs); expr_free(e->u.x_ternary.mhs); expr_free(e->u.x_ternary.rhs); break; case X_FXN: for (i=e->u.x_fxn.nargs-1;i>=0;i--) expr_free(e->u.x_fxn.args[i]); free(e->u.x_fxn.args); break; } free(e); } static void free_exprlist(EXPRLIST *list) { EXPRLIST *t; while (list) { t = list; list = t->link; expr_free(t->expr); free(t); } } static int ident_char(int c, int isfirst) { switch (c) { case '$': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': return(1); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return(!isfirst); break; default: return(0); break; } } static int digit_char(int c) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return(1); break; default: return(0); break; } } static int curchar(PARSECTX *c) { return((c->pxll)?c->lp[c->px]:-1); } static int offchar(PARSECTX *c, int o) { o += c->px; return((oll)?c->lp[o]:-1); } static void skip_ws(PARSECTX *c) { while (1) { switch (curchar(c)) { case ' ': case '\t': break; default: return; } c->px ++; } } static void justcomment(PARSECTX *c) { skip_ws(c); if (c->px >= c->ll) return; switch (curchar(c)) { case ';': case '#': return; break; } parse_err(c,"trailing junk on line"); } static void consume(PARSECTX *c, int n) { c->px += n; } static LINE *append_line(PARSECTX *c, LINETYPE t) { LINE *l; LINELIST *ll; l = malloc(sizeof(LINE)); l->t = t; l->lno = c->lno; l->indent = c->indent; l->text = c->lp; l->textlen = c->ll; ll = malloc(sizeof(LINELIST)); ll->line = l; *c->tail = ll; c->tail = &ll->link; return(l); } static IDENT *find_ident(const char *name, int len) { IDENT *i; char *n; for (i=idents;i;i=i->link) { if ((len == i->namelen) && !bcmp(name,i->name,len)) return(i); } n = malloc(len); bcopy(name,n,len); i = malloc(sizeof(IDENT)); i->name = n; i->namelen = len; i->link = idents; i->value = MAKEVAL_NIL(); idents = i; return(i); } static EXPR *build_binary(EXPR *lhs, B_OP op, EXPR *rhs) { EXPR *e; e = malloc(sizeof(EXPR)); e->t = X_BINARY; e->u.x_binary.lhs = lhs; e->u.x_binary.op = op; e->u.x_binary.rhs = rhs; return(e); } static EXPR *parse_string(PARSECTX *c) { unsigned char *b; int a; int l; int ch; int bq; EXPR *e; STRING *s; unsigned int ev; unsigned int dv; void accum(unsigned char ch) { if (l >= a) b = realloc(b,a=l+8); b[l++] = ch; } b = 0; a = 0; l = 0; bq = '\0'; while <"string"> (1) { ch = curchar(c); if (ch < 0) { parse_err(c,"unclosed string"); free(b); return(0); } switch (bq) { case '\0': switch (ch) { case '"': s = malloc(sizeof(STRING)); s->body = malloc(l+1); bcopy(b,s->body,l); s->body[l] = '\0'; s->len = l; s->refcnt = 1; e = malloc(sizeof(EXPR)); e->t = X_STR; e->u.x_str = s; free(b); consume(c,1); return(e); break; case '\\': bq = '\\'; break; default: accum(ch); break; } break; case '\\': switch (ch) { case '"': case '\\': bq = '\0'; accum(ch); break; case '.': // No accum() - that's the point of \. bq = '\0'; break; case 'a': accum(7); bq = '\0'; break; case 'b': accum(8); bq = '\0'; break; case 'e': accum(27); bq = '\0'; break; case 'f': accum(12); bq = '\0'; break; case 'n': accum(10); bq = '\0'; break; case 'r': accum(13); bq = '\0'; break; case 't': accum(9); bq = '\0'; break; case '0': ev = 0; if (0) { case '1': ev = 1; } if (0) { case '2': ev = 2; } if (0) { case '3': ev = 3; } if (0) { case '4': ev = 4; } if (0) { case '5': ev = 5; } if (0) { case '6': ev = 6; } if (0) { case '7': ev = 7; } bq = '1'; break; case 'x': ev = 0; bq = 'x'; break; default: bq = '\0'; parse_err(c,"unknown string escape"); break; } break; case '1': case '2': switch (ch) { case '0': dv = 0; if (0) { case '1': dv = 1; } if (0) { case '2': dv = 2; } if (0) { case '3': dv = 3; } if (0) { case '4': dv = 4; } if (0) { case '5': dv = 5; } if (0) { case '6': dv = 6; } if (0) { case '7': dv = 7; } ev = (ev << 3) + dv; switch (bq) { case '1': bq = '2'; break; case '2': accum(ev); bq = '\0'; break; } break; default: accum(ev); bq = '\0'; continue <"string">; // bypass consume() break; } break; case 'x': switch (ch) { case '0': dv = 0; if (0) { case '1': dv = 1; } if (0) { case '2': dv = 2; } if (0) { case '3': dv = 3; } if (0) { case '4': dv = 4; } if (0) { case '5': dv = 5; } if (0) { case '6': dv = 6; } if (0) { case '7': dv = 7; } if (0) { case '8': dv = 8; } if (0) { case '9': dv = 9; } if (0) { case 'a': case 'A': dv = 10; } if (0) { case 'b': case 'B': dv = 11; } if (0) { case 'c': case 'C': dv = 12; } if (0) { case 'd': case 'D': dv = 13; } if (0) { case 'e': case 'E': dv = 14; } if (0) { case 'f': case 'F': dv = 15; } ev = (ev << 4) + dv; break; default: accum(ev); bq = '\0'; continue <"string">; // bypass consume() break; } break; default: abort(); break; } consume(c,1); } } static EXPR *parse_number(PARSECTX *c) { int postdot; long long int iv; double dv; double p10; int ch; int ndig; int v; EXPR *e; ndig = 0; postdot = 0; iv = 0; while <"loop"> (1) { ch = curchar(c); switch (ch) { case '.': if (postdot) break <"loop">; postdot = 1; dv = iv; p10 = .1; break; case '0': v = 0; if (0) { case '1': v = 1; } if (0) { case '2': v = 2; } if (0) { case '3': v = 3; } if (0) { case '4': v = 4; } if (0) { case '5': v = 5; } if (0) { case '6': v = 6; } if (0) { case '7': v = 7; } if (0) { case '8': v = 8; } if (0) { case '9': v = 9; } if (postdot) dv += p10 * v; else iv = (iv * 10) + v; ndig ++; break; default: break <"loop">; } consume(c,1); } if (ndig < 1) { if (postdot) { parse_err(c,"invalid number"); return(0); } abort(); } e = malloc(sizeof(EXPR)); e->t = X_NUM; if (postdot) { e->u.x_num.t = NUM_FLOAT; e->u.x_num.u.f = dv; } else { e->u.x_num.t = NUM_INT; e->u.x_num.u.i = iv; } return(e); } static EXPR *build_nilary(N_OP op) { EXPR *rv; rv = malloc(sizeof(EXPR)); rv->t = X_NILARY; rv->u.x_nilary.op = op; return(rv); } static EXPR *build_unary(U_OP op, EXPR *arg) { EXPR *rv; rv = malloc(sizeof(EXPR)); rv->t = X_UNARY; rv->u.x_unary.op = op; rv->u.x_unary.arg = arg; return(rv); } static EXPR *parse_expr_primary(PARSECTX *c) { EXPR *e; EXPR *rv; int cc; int ident0; int identl; EXPRLIST *el; EXPRLIST **elt; EXPRLIST *et; int nargs; int i; skip_ws(c); cc = curchar(c); switch (cc) { case '(': consume(c,1); e = parse_expr_top(c); if (! e) { if (! c->err) parse_err(c,"syntax error"); return(0); } skip_ws(c); if (curchar(c) == ')') { consume(c,1); rv = malloc(sizeof(EXPR)); rv->t = X_PAREN; rv->u.x_paren = e; return(rv); } parse_err(c,"improperly closed ("); expr_free(e); return(0); break; case '[': if (offchar(c,1) == ']') { consume(c,2); return(build_nilary(N_ARRAY)); } return(0); break; case '"': consume(c,1); return(parse_string(c)); break; case '@': if ( (offchar(c,1) == 't') && (offchar(c,2) == 'r') && (offchar(c,3) == 'u') && (offchar(c,4) == 'e') ) { consume(c,5); return(build_nilary(N_TRUE)); } else if ( (offchar(c,1) == 'f') && (offchar(c,2) == 'a') && (offchar(c,3) == 'l') && (offchar(c,4) == 's') && (offchar(c,5) == 'e') ) { consume(c,6); return(build_nilary(N_FALSE)); } break; } if ((cc == '.') || digit_char(cc)) { return(parse_number(c)); } if (! ident_char(cc,1)) return(0); ident0 = c->px; do consume(c,1); while (ident_char(curchar(c),0)); identl = c->px - ident0; skip_ws(c); if (curchar(c) != '(') { rv = malloc(sizeof(EXPR)); rv->t = X_IDENT; rv->u.x_ident = find_ident(c->lp+ident0,identl); return(rv); } consume(c,1); nargs = 0; elt = ⪙ while (1) { skip_ws(c); if (curchar(c) == ')') { consume(c,1); break; } if (elt != &el) { if (curchar(c) != ',') { parse_err(c,"invalid arglist delimiter"); *elt = 0; free_exprlist(el); return(0); } consume(c,1); } e = parse_expr_top(c); if (! e) { if (! c->err) parse_err(c,"missing argument"); *elt = 0; free_exprlist(el); return(0); } et = malloc(sizeof(EXPRLIST)); et->expr = e; *elt = et; elt = &et->link; nargs ++; } rv = malloc(sizeof(EXPR)); rv->t = X_FXN; rv->u.x_fxn.fxn = find_ident(c->lp+ident0,identl); rv->u.x_fxn.nargs = nargs; rv->u.x_fxn.args = malloc(nargs*sizeof(EXPR *)); *elt = 0; for (i=0;ilink; rv->u.x_fxn.args[i] = et->expr; free(et); } if (el) abort(); return(rv); } static EXPR *parse_expr_unary(PARSECTX *c) { EXPR *arg; U_OP op; EXPR *rv; int tagged; skip_ws(c); tagged = 0; switch (curchar(c)) { case '-': op = U_MINUS; consume(c,1); break; case '!': if (offchar(c,1) != '=') { op = U_LOGNOT; consume(c,1); break; } // fall through default: return(parse_expr_primary(c)); break; case '@': if ( (offchar(c,1) == 'f') && (offchar(c,2) == 'o') && (offchar(c,3) == 'u') && (offchar(c,4) == 'n') && (offchar(c,5) == 'd') ) { consume(c,6); op = U_FOUND; tagged = 1; } break; } arg = parse_expr_unary(c); if (! arg) { if (! c->err) parse_err(c,"syntax error"); return(0); } if (tagged) { rv = malloc(sizeof(EXPR)); rv->t = X_TAG; rv->u.x_tag = arg; arg = rv; } return(build_unary(op,arg)); } static EXPR *parse_expr_subscript(PARSECTX *c) { EXPR *lhs; EXPR *rhs; lhs = parse_expr_unary(c); if (! lhs) return(0); while (1) { skip_ws(c); if (curchar(c) != '[') return(lhs); consume(c,1); rhs = parse_expr_subscript(c); if (! rhs) { if (! c->err) parse_err(c,"syntax error"); expr_free(lhs); return(0); } if (curchar(c) != ']') { parse_err(c,"improperly closed ["); expr_free(lhs); expr_free(rhs); return(0); } lhs = build_binary(lhs,B_AREF,rhs); } } static EXPR *parse_expr_muldiv(PARSECTX *c) { EXPR *lhs; EXPR *rhs; B_OP op; lhs = parse_expr_subscript(c); if (! lhs) return(0); while (1) { skip_ws(c); switch (curchar(c)) { case '*': op = B_MUL; consume(c,1); break; case '/': op = B_DIV; consume(c,1); break; default: return(lhs); break; } rhs = parse_expr_subscript(c); if (! rhs) { if (! c->err) parse_err(c,"syntax error"); expr_free(lhs); return(0); } lhs = build_binary(lhs,op,rhs); } } static EXPR *parse_expr_addsub(PARSECTX *c) { EXPR *lhs; EXPR *rhs; B_OP op; lhs = parse_expr_muldiv(c); if (! lhs) return(0); while (1) { skip_ws(c); switch (curchar(c)) { case '+': op = B_ADD; consume(c,1); break; case '-': op = B_SUB; consume(c,1); break; default: return(lhs); break; } rhs = parse_expr_muldiv(c); if (! rhs) { if (! c->err) parse_err(c,"syntax error"); expr_free(lhs); return(0); } lhs = build_binary(lhs,op,rhs); } } static EXPR *parse_expr_bit(PARSECTX *c) { EXPR *lhs; EXPR *rhs; B_OP curop; char curname[2]; B_OP op; char opname[2]; int ncons; // B_ADD here can be anything we don't handle, but code below knows it. curop = B_ADD; lhs = parse_expr_addsub(c); if (! lhs) return(0); while (1) { skip_ws(c); // Here too, B_SUB can be anything we don't handle, but code below knows it. op = B_SUB; if (curchar(c) != offchar(c,1)) { switch (curchar(c)) { case '&': op = B_AND; break; case '|': op = B_OR; break; case '^': op = B_XOR; break; } } if (op == B_SUB) return(lhs); opname[0] = curchar(c); opname[1] = '\0'; if ((curop != B_ADD) && (curop != op)) { parse_err(c,"mixing %s and %s requires parens",&curname[0],&opname[0]); expr_free(lhs); return(0); } consume(c,ncons); rhs = parse_expr_addsub(c); if (! rhs) { if (! c->err) parse_err(c,"syntax error"); expr_free(lhs); return(0); } lhs = build_binary(lhs,op,rhs); curop = op; bcopy(&opname[0],&curname[0],2); } } static EXPR *parse_expr_regex(PARSECTX *c) { EXPR *lhs; EXPR *rhs; int neg; lhs = parse_expr_bit(c); if (! lhs) return(0); skip_ws(c); do <"haveop"> { switch (curchar(c)) { case '~': neg = 0; consume(c,1); break <"haveop">; case '!': if (offchar(c,1) == '~') { neg = 1; consume(c,2); break <"haveop">; } break; } return(lhs); } while (0); rhs = parse_expr_bit(c); if (! rhs) { if (! c->err) parse_err(c,"syntax error"); expr_free(lhs); return(0); } lhs = build_binary(lhs,B_REGEX,rhs); if (neg) lhs = build_unary(U_LOGNOT,lhs); return(lhs); } static EXPR *parse_expr_cmp(PARSECTX *c) { EXPR *lhs; EXPR *rhs; B_OP op; lhs = parse_expr_regex(c); if (! lhs) return(0); skip_ws(c); do <"haveop"> { switch (curchar(c)) { case '<': if (offchar(c,1) == '=') { op = B_LE; consume(c,2); } else { op = B_LT; consume(c,1); } break <"haveop">; case '>': if (offchar(c,1) == '=') { op = B_GE; consume(c,2); } else { op = B_GT; consume(c,1); } break <"haveop">; case '=': if (offchar(c,1) == '=') { op = B_EQ; consume(c,2); break <"haveop">; } break; case '!': if (offchar(c,1) == '=') { op = B_NE; consume(c,2); break <"haveop">; } break; } return(lhs); } while (0); rhs = parse_expr_regex(c); if (! rhs) { if (! c->err) parse_err(c,"syntax error"); expr_free(lhs); return(0); } return(build_binary(lhs,op,rhs)); } static EXPR *parse_expr_log(PARSECTX *c) { EXPR *lhs; EXPR *rhs; B_OP curop; char curname[3]; B_OP op; char opname[3]; // B_ADD here can be anything we don't handle, but code below knows it. curop = B_ADD; lhs = parse_expr_cmp(c); if (! lhs) return(0); while (1) { skip_ws(c); // Here too, B_SUB can be anything we don't handle, but code below knows it. op = B_SUB; if (curchar(c) == offchar(c,1)) { switch (curchar(c)) { case '&': op = B_ANDAND; break; case '|': op = B_OROR; break; case '^': op = B_XORXOR; break; } } if (op == B_SUB) return(lhs); opname[0] = curchar(c); opname[1] = curchar(c); opname[2] = '\0'; if ((curop != B_ADD) && (curop != op)) { parse_err(c,"mixing %s and %s requires parens",&curname[0],&opname[0]); expr_free(lhs); return(0); } consume(c,2); rhs = parse_expr_cmp(c); if (! rhs) { if (! c->err) parse_err(c,"syntax error"); expr_free(lhs); return(0); } lhs = build_binary(lhs,op,rhs); curop = op; bcopy(&opname[0],&curname[0],3); } } static EXPR *parse_expr_cond(PARSECTX *c) { EXPR *lhs; EXPR *mhs; EXPR *rhs; EXPR *rv; lhs = parse_expr_log(c); if (! lhs) return(0); skip_ws(c); if (curchar(c) != '?') return(lhs); c->px ++; mhs = parse_expr_cond(c); if (! mhs) { if (! c->err) parse_err(c,"syntax error"); expr_free(lhs); return(0); } skip_ws(c); if (curchar(c) != ':') return(lhs); c->px ++; rhs = parse_expr_cond(c); if (! rhs) { if (! c->err) parse_err(c,"syntax error"); expr_free(lhs); expr_free(mhs); return(0); } rv = malloc(sizeof(EXPR)); rv->t = X_TERNARY; rv->u.x_ternary.lhs = lhs; rv->u.x_ternary.mhs = mhs; rv->u.x_ternary.rhs = rhs; return(rv); } static EXPR *parse_expr_top(PARSECTX *c) { return(parse_expr_cond(c)); } static EXPR *parse_expr(PARSECTX *c, const char *what) { EXPR *e; e = parse_expr_top(c); if (!e && !c->err && what) parse_err(c,"missing %s",what); return(e); } static PARID lookup_param(const unsigned char *name, int namelen) { switch (namelen) { case 9: if (! bcmp(name,"interchar",9)) return(PAR_INTERCHAR); break; case 10: if (! bcmp(name,"max_output",10)) return(PAR_MAX_OUTPUT); break; } return(PAR__ERR); } static int need(PARSECTX *c, unsigned char ch) { skip_ws(c); if (curchar(c) != ch) { parse_err(c,"missing %c",ch); return(1); } consume(c,1); return(0); } static void parse_line_if(PARSECTX *c) { EXPR *cond; cond = parse_expr(c,"condition"); if (cond) justcomment(c); append_line(c,LINE_IF)->u.l_if.cond = cond; } static void parse_line_set(PARSECTX *c) { EXPR *lhs; EXPR *rhs; LINE *l; lhs = parse_expr(c,"variable expression"); rhs = 0; if (lhs) { if ( (lhs->t == X_IDENT) || ((lhs->t == X_BINARY) && (lhs->u.x_binary.op == B_AREF)) ) { if (need(c,'=')) return; rhs = parse_expr(c,"value expression"); if (rhs) justcomment(c); } else { parse_err(c,"set LHS must be variable or subscript expression"); expr_free(lhs); return; } } l = append_line(c,LINE_SET); l->u.l_set.lhs = lhs; l->u.l_set.rhs = rhs; } static void parse_line_param(PARSECTX *c) { EXPR *lhs; EXPR *rhs; LINE *l; PARID pno; lhs = parse_expr(c,"parameter name"); if (lhs) { if (lhs->t == X_IDENT) { pno = lookup_param(lhs->u.x_ident->name,lhs->u.x_ident->namelen); if (pno == PAR__ERR) { parse_err(c,"unrecognized parameter name"); expr_free(lhs); return; } expr_free(lhs); if (need(c,'=')) return; rhs = parse_expr(c,"value expression"); if (rhs) justcomment(c); } else { parse_err(c,"set LHS must be a variable"); expr_free(lhs); return; } } l = append_line(c,LINE_PARAM); l->u.l_param.parno = pno; l->u.l_param.rhs = rhs; } static void parse_line_elif(PARSECTX *c) { EXPR *cond; cond = parse_expr(c,"condition"); if (cond) justcomment(c); append_line(c,LINE_ELIF)->u.l_if.cond = cond; } static void parse_line_else(PARSECTX *c) { justcomment(c); append_line(c,LINE_ELSE); } static void parse_line_exit(PARSECTX *c) { EXPR *ecode; ecode = parse_expr(c,0); if (ecode || !c->err) justcomment(c); append_line(c,LINE_EXIT)->u.l_exit.ecode = ecode; } static void parse_line_loop(PARSECTX *c) { EXPR *tag; tag = parse_expr(c,0); if (tag || !c->err) justcomment(c); append_line(c,LINE_LOOP)->u.l_loop.tag = tag; } static void parse_line_send(PARSECTX *c) { EXPR *text; text = parse_expr(c,"text"); if (text) justcomment(c); append_line(c,LINE_SEND)->u.l_send.text = text; } static void parse_line_print(PARSECTX *c) { EXPR *text; text = parse_expr(c,"text"); if (text) justcomment(c); append_line(c,LINE_PRINT)->u.l_print.text = text; } static void parse_line_await(PARSECTX *c) { EXPR *timeout; EXPR *string; LINE *l; timeout = parse_expr(c,"timeout"); string = 0; if (timeout) { skip_ws(c); if (curchar(c) == ',') { consume(c,1); string = parse_expr(c,"expectation"); if (string || !c->err) justcomment(c); } else { justcomment(c); } } l = append_line(c,LINE_AWAIT); l->u.l_await.timeout = timeout; l->u.l_await.string = string; } static void parse_line_break(PARSECTX *c) { EXPR *tag; tag = parse_expr(c,0); if (tag || !c->err) justcomment(c); append_line(c,LINE_BREAK)->u.l_break.tag = tag; } static void parse_line_delay(PARSECTX *c) { EXPR *timeout; timeout = parse_expr(c,"timeout"); if (timeout) justcomment(c); append_line(c,LINE_DELAY)->u.l_delay.timeout = timeout; } static void parse_line_drain(PARSECTX *c) { EXPR *timeout; timeout = parse_expr(c,"timeout"); if (timeout) justcomment(c); append_line(c,LINE_DRAIN)->u.l_drain.timeout = timeout; } static void parse_line_expect(PARSECTX *c) { EXPR *tag; EXPR *want; LINE *l; tag = parse_expr(c,"tag"); want = 0; if (tag) { if (need(c,',')) return; want = parse_expr(c,"string"); if (want) justcomment(c); } l = append_line(c,LINE_EXPECT); l->u.l_expect.tag = tag; l->u.l_expect.want = want; } static void parse_line_next(PARSECTX *c) { EXPR *tag; EXPR *want; LINE *l; tag = parse_expr(c,"tag"); want = 0; if (tag) { if (need(c,',')) return; want = parse_expr(c,"string"); if (want) justcomment(c); } l = append_line(c,LINE_NEXT); l->u.l_next.tag = tag; l->u.l_next.want = want; } static void parse_line_continue(PARSECTX *c) { EXPR *tag; tag = parse_expr(c,0); if (tag || !c->err) justcomment(c); append_line(c,LINE_CONTINUE)->u.l_break.tag = tag; } static void parse_line(PARSECTX *c) { int i; int i0; c->err = 0; c->indent = 0; for <"indent"> (i=0;ill;i++) { switch (c->lp[i]) { case ' ': c->indent ++; break; case '\t': c->indent = (c->indent + 8) & ~7; break; default: break <"indent">; } } if (i >= c->ll) return; switch (c->lp[i]) { case ';': case '#': return; break; } i0 = i; for (;ill;i++) { if (! ident_char(c->lp[i],i==i0)) break; } c->px = i; skip_ws(c); switch (i - i0) { case 0: c->px = i0; parse_err(c,"missing line keyword"); return; break; case 2: if (! bcmp(c->lp+i0,"if",2)) { parse_line_if(c); return; } break; case 3: if (! bcmp(c->lp+i0,"set",3)) { parse_line_set(c); return; } break; case 4: switch (c->lp[i0]) { case 'e': if (! bcmp(c->lp+i0,"elif",4)) { parse_line_elif(c); return; } if (! bcmp(c->lp+i0,"else",4)) { parse_line_else(c); return; } if (! bcmp(c->lp+i0,"exit",4)) { parse_line_exit(c); return; } break; case 'l': if (! bcmp(c->lp+i0,"loop",4)) { parse_line_loop(c); return; } break; case 'n': if (! bcmp(c->lp+i0,"next",4)) { parse_line_next(c); return; } break; case 's': if (! bcmp(c->lp+i0,"send",4)) { parse_line_send(c); return; } break; } break; case 5: switch (c->lp[i0]) { case 'a': if (! bcmp(c->lp+i0,"await",5)) { parse_line_await(c); return; } break; case 'b': if (! bcmp(c->lp+i0,"break",5)) { parse_line_break(c); return; } break; case 'd': if (! bcmp(c->lp+i0,"delay",5)) { parse_line_delay(c); return; } if (! bcmp(c->lp+i0,"drain",5)) { parse_line_drain(c); return; } break; case 'p': if (! bcmp(c->lp+i0,"param",5)) { parse_line_param(c); return; } if (! bcmp(c->lp+i0,"print",5)) { parse_line_print(c); return; } break; } break; case 6: if (! bcmp(c->lp+i0,"expect",6)) { parse_line_expect(c); return; } break; case 8: if (! bcmp(c->lp+i0,"continue",8)) { parse_line_continue(c); return; } break; } c->px = i0; parse_err(c,"unrecognized line type `%.*s'",i-i0,c->lp+i0); } static void parse_top(PARSECTX *c) { const unsigned char *nl; while (c->lx < c->buflen) { c->lno ++; c->lp = c->buf + c->lx; nl = memchr(c->buf+c->lx,'\n',c->buflen-c->lx); if (! nl) { c->px = -1; parse_err(c,"last line is missing its newline"); c->ll = c->buflen - c->lx; parse_line(c); break; } c->ll = nl - c->lp; parse_line(c); c->lx += c->ll + 1; } } static void loadscript(void) { int fd; struct stat stb; void *mmrv; PARSECTX pc; int i; LINELIST *ll; fd = open(scriptfile,O_RDONLY,0); if (fd < 0) { fprintf(stderr,"%s: can't open %s: %s\n",__progname,scriptfile,strerror(errno)); exit(1); } if (fstat(fd,&stb) < 0) { fprintf(stderr,"%s: can't fstat %s: %s\n",__progname,scriptfile,strerror(errno)); exit(1); } mmrv = mmap(0,stb.st_size,PROT_READ,MAP_SHARED,fd,0); if (mmrv == MAP_FAILED) { fprintf(stderr,"%s: can't mmap %s: %s\n",__progname,scriptfile,strerror(errno)); exit(1); } close(fd); pc.buf = mmrv; pc.buflen = stb.st_size; pc.lx = 0; pc.lno = 0; pc.lines = 0; pc.tail = &pc.lines; pc.errs = 0; parse_top(&pc); if (pc.errs) { fprintf(stderr,"%s: exiting on parse failure\n",__progname); exit(1); } *pc.tail = 0; scriptlen = 0; for (ll=pc.lines;ll;ll=ll->link) scriptlen ++; script = malloc(scriptlen*sizeof(LINE *)); i = 0; for (ll=pc.lines;ll;ll=ll->link) { if (i >= scriptlen) abort(); script[i] = ll->line; ll->line->inx = i; i ++; } if (i != scriptlen) abort(); while (pc.lines) { ll = pc.lines; pc.lines = ll->link; free(ll); } } static void open_tty(void) { struct termios tio; iofd = open(ttydev,O_RDWR|O_NONBLOCK,0); if (iofd < 0) { fprintf(stderr,"%s: can't open %s: %s\n",__progname,ttydev,strerror(errno)); exit(1); } tcgetattr(iofd,&tio); cfmakeraw(&tio); tcsetattr(iofd,TCSANOW,&tio); tcsetattr(iofd,TCSAFLUSH,&tio); iotext = "tty"; } static void open_cmd(void) { int pp[2]; int xp[2]; pid_t kid; int e; int r; if ( (socketpair(AF_LOCAL,SOCK_STREAM,0,&pp[0]) < 0) || (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[0]) < 0) ) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } fcntl(xp[1],F_SETFD,1); fflush(0); kid = fork(); if (kid == 0) { dup2(devnull,0); dup2(pp[1],0); dup2(0,1); dup2(0,2); close(pp[1]); close(pp[0]); close(xp[0]); execlp("sh","sh","-c",command,(char *)0); e = errno; write(xp[1],&e,sizeof(e)); _exit(1); } close(xp[1]); close(pp[1]); r = recv(xp[0],&e,sizeof(e),MSG_WAITALL); if (r < 0) { fprintf(stderr,"%s: recv for exec %s: %s\n",__progname,command,strerror(errno)); exit(1); } if (r == sizeof(int)) { fprintf(stderr,"%s: exec %s: %s\n",__progname,command,strerror(e)); exit(1); } if (r != 0) { fprintf(stderr,"%s: recv for exec %s got %d, expected 0 or %d\n",__progname,command,r,(int)sizeof(e)); exit(1); } close(xp[0]); iofd = pp[0]; nbio(iofd); iotext = "command"; } static void openio(void) { if (ttydev) { open_tty(); } else if (command) { open_cmd(); } else { abort(); } } static void run_err(LINE *, const char *, ...) __attribute__((__format__(__printf__,2,3),__noreturn__)); static void run_err(LINE *l, const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"%s: script line %d: %s\n",__progname,l->lno,s); free(s); exit(1); } static VAL val_copy(VAL v) { switch (v.t) { case VAL_NUM: return(v); break; case VAL_STR: ref_string(v.u.s); return(v); break; case VAL_ARR: ref_array(v.u.a); return(v); break; case VAL_TAG: return(v); break; case VAL_LOG: return(v); break; default: abort(); break; } } static VAL val_mutate(VAL v) { // no types currently need this return(v); } static void val_drop(VAL v) { switch (v.t) { case VAL_STR: deref_string(v.u.s); break; case VAL_ARR: deref_array(v.u.a); break; default: break; } } static VAL empty_array(void) { ARRAY *a; a = malloc(sizeof(ARRAY)); a->refcnt = 1; a->els = 0; return(MAKEVAL_ARR(a)); } static VAL new_string_val(unsigned char *body, int len) { STRING *s; s = malloc(sizeof(STRING)); s->refcnt = 1; s->body = body; s->len = len; return(MAKEVAL_STR(s)); } static void deref_sent_string(void *sv) { deref_string(sv); } static void send_string(STRING *s) { ref_string(s); aio_oq_queue_cb(&iooq,s->body,s->len,&deref_sent_string,s); } static void print_string(STRING *s) { write(1,s->body,s->len); } static void add_expect(VAL tag, STRING *want, int next) { EXPECTATION *e; e = malloc(sizeof(EXPECTATION)); e->tag = tag; e->str = want; e->flags = next ? EXPF_NEXT : 0; e->link = expected; expected = e; } static TIME get_now(void) { struct timeval tv; gettimeofday(&tv,0); return((tv.tv_sec * (TIME)1000000) + tv.tv_usec); } static void process_tty_char(unsigned char c) { EXPECTATION *e; STRING *s; int i; for <"exp"> (e=expected;e;e=e->link) { s = e->str; if (c == s->body[e->at]) { e->at ++; if (e->at >= s->len) { val_drop(found); found = val_copy(e->tag); return; } } else if (e->at) { for (i=1;iat;i++) { if ((s->body[e->at-i] == c) && !bcmp(s->body+i,s->body,e->at-i)) { e->at -= i; continue <"exp">; } } e->at = (s->body[0] == c); } } } static void watch_output(unsigned char c, unsigned char next) { if ((c < 32) || ((c >= 127) && (c < 160))) { putchar(c); } else if ((next >= '0') && (next <= '9')) { printf("\\%03o",c); } else { printf("\\%o",c); } } static void watch_char(unsigned char c, const char *tag) { if (! watch) return; if (watch_last < 0) { printf("%s",tag); } else { watch_output(watch_last,c); } watch_last = c; } static void watch_close_line(void) { if (watch_last < 0) return; watch_output(watch_last,'\0'); watch_last = -1; } static void do_await_prep(NUMBER timeout) { EXPECTATION *e; int id; TIME end; int blockfn(void *arg __attribute__((__unused__))) { TIME now; int ms; int tq; now = get_now(); if (iorx < iorf) { while ((found.t == VAL_NIL) && (iorx < iorf)) { watch_char(iorb[iorx],"<<< "); process_tty_char(iorb[iorx]); iorx ++; } watch_close_line(); if (iorx >= iorf) { iorx = 0; iorf = 0; } return(AIO_BLOCK_LOOP); } if (aio_oq_nonempty(&iooq)) { if (interchar) { if (now > ionexto) return(0); ms = (999 + ionexto - now) / 1000; return((ms<1)?1:ms); } else { return(AIO_BLOCK_NIL); } } if (ttydev) { if ((ioctl(iofd,TIOCOUTQ,&tq) >= 0) && (tq > 0)) { write(-tq,"",1); return(10); } } #ifdef FIONWRITE if (command) { if (ioctl(iofd,FIONWRITE,&tq) >= 0) && (tq > 0)) { write(-tq,"",1); return(10); } } #endif if (end == 0) { switch (timeout.t) { case NUM_INT: end = now + (timeout.u.i * (TIME)1000000); break; case NUM_FLOAT: end = now + (TIME)(timeout.u.f * 1e6); break; default: abort(); break; } } if (now >= end) { val_drop(found); found = MAKEVAL_TAG(find_ident("timeout",7)); return(AIO_BLOCK_LOOP); } if (end-now > 1000000) { return(1000); } else { ms = (999 + end - now) / 1000; if (ms < 1) ms = 1; return(ms); } } for (e=expected;e;e=e->link) e->at = 0; end = 0; id = aio_add_block(&blockfn,0); val_drop(found); found = MAKEVAL_NIL(); while (found.t == VAL_NIL) { if (aio_pre_poll() < 0) { fprintf(stderr,"%s: aio_pre_poll failed\n",__progname); exit(1); } if (aio_do_poll() < 0) { fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } aio_post_poll(); } aio_remove_block(id); while ((e = expected)) { expected = e->link; val_drop(e->tag); deref_string(e->str); free(e); } } static void do_await_str(NUMBER timeout, STRING *want) { EXPECTATION *esave; esave = expected; expected = 0; add_expect(MAKEVAL_NUM(MAKENUM_INT(0)),want,0); do_await_prep(timeout); if (expected) abort(); expected = esave; } static void delay_time(double timeout) { TIME end; TIME now; int ms; end = get_now() + (TIME)(timeout * 1e6); while (1) { now = get_now(); if (now >= end) return; ms = (999 + end - now) / 1000; if (ms < 1) ms = 1; else if (ms > 1000) ms = 1000; poll(0,0,ms); } } static int compare(VAL lhs, VAL rhs, int anytype) { if (lhs.t != rhs.t) { if (anytype) return(0); abort(); } switch (lhs.t) { case VAL_NUM: switch (lhs.u.n.t) { case NUM_INT: switch (rhs.u.n.t) { case NUM_INT: if (lhs.u.n.u.i < rhs.u.n.u.i) return(-1); if (lhs.u.n.u.i > rhs.u.n.u.i) return(1); return(0); break; case NUM_FLOAT: if (lhs.u.n.u.i < rhs.u.n.u.f) return(-1); if (lhs.u.n.u.i > rhs.u.n.u.f) return(1); return(0); break; default: abort(); break; } break; case NUM_FLOAT: switch (rhs.u.n.t) { case NUM_INT: if (lhs.u.n.u.f < rhs.u.n.u.i) return(-1); if (lhs.u.n.u.f > rhs.u.n.u.i) return(1); return(0); break; case NUM_FLOAT: if (lhs.u.n.u.f < rhs.u.n.u.f) return(-1); if (lhs.u.n.u.f > rhs.u.n.u.f) return(1); return(0); break; default: abort(); break; } break; default: abort(); break; } break; case VAL_STR: { int len; int eq; int cmp; if (lhs.u.s->len < rhs.u.s->len) { len = lhs.u.s->len; eq = -1; } else if (lhs.u.s->len > rhs.u.s->len) { len = rhs.u.s->len; eq = 1; } else { len = lhs.u.s->len; eq = 0; } cmp = strncmp(lhs.u.s->body,rhs.u.s->body,len); return(cmp?cmp:eq); } break; default: if (anytype) return(0); break; } abort(); } static AEL *array_find_el(LINE *l, VAL arr, VAL sub, int create) { AEL *e; if (arr.t != VAL_ARR) run_err(l,"subscripting non-array"); for (e=arr.u.a->els;e;e=e->link) { if (e->key.t == sub.t) { switch (sub.t) { case VAL_NUM: case VAL_STR: if (compare(e->key,sub,0) == 0) return(e); break; case VAL_ARR: if (e->key.u.a == sub.u.a) return(e); break; case VAL_TAG: if (e->key.u.t == sub.u.t) return(e); break; case VAL_LOG: if (e->key.u.l == sub.u.l) return(e); break; default: abort(); break; } } } if (! create) return(0); e = malloc(sizeof(AEL)); e->key = val_copy(sub); e->val = MAKEVAL_NIL(); e->link = arr.u.a->els; arr.u.a->els = e; return(e); } static int val_equal(VAL a, VAL b) { if (a.t != b.t) return(0); switch (a.t) { case VAL_NUM: // heh, glad I don't use a third NUMBER here! switch (a.u.n.t) { case NUM_INT: switch (b.u.n.t) { case NUM_INT: return(a.u.n.u.i==b.u.n.u.i); break; case NUM_FLOAT: return(a.u.n.u.i==b.u.n.u.f); break; default: abort(); break; } break; case NUM_FLOAT: switch (b.u.n.t) { case NUM_INT: return(a.u.n.u.f==b.u.n.u.i); break; case NUM_FLOAT: return(a.u.n.u.f==b.u.n.u.f); break; default: abort(); break; } break; default: abort(); break; } break; case VAL_STR: if (a.u.s == b.u.s) return(1); if (a.u.s->len != b.u.s->len) return(0); return(!bcmp(a.u.s->body,b.u.s->body,a.u.s->len)); break; case VAL_ARR: return(a.u.a==b.u.a); break; case VAL_TAG: return(a.u.t==b.u.t); break; case VAL_LOG: return(a.u.l==b.u.l); break; default: abort(); break; } } static VAL perform_nilary(LINE *l, N_OP op) { switch (op) { case N_ARRAY: return(empty_array()); break; case N_FALSE: return(MAKEVAL_LOG(0)); break; case N_TRUE: return(MAKEVAL_LOG(1)); break; } (void)l; abort(); } static VAL command_output(LINE *l, VAL arg) { int xp[2]; int op[2]; pid_t kid; char *t; int e; int r; int f; if (arg.t != VAL_STR) run_err(l,"@run applied to non-string"); if (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[0]) < 0) run_err(l,"socketpair failed: %s",strerror(errno)); if (pipe(&op[0]) < 0) run_err(l,"pipe failed: %s",strerror(errno)); fcntl(xp[1],F_SETFD,1); fflush(0); kid = fork(); if (kid == 0) { close(iofd); dup2(devnull,0); dup2(op[1],1); close(devnull); close(op[0]); close(op[1]); close(xp[0]); execlp("sh","sh","-c",arg.u.s->body,(char *)0); e = errno; write(xp[1],&e,sizeof(e)); _exit(1); } close(xp[1]); close(op[1]); r = recv(xp[0],&e,sizeof(e),MSG_WAITALL); if (r < 0) run_err(l,"exec recv: %s",strerror(errno)); if (r == sizeof(int)) run_err(l,"exec sh: %s",strerror(e)); if (r != 0) run_err(l,"exec protocol error: wanted %d or 0, got %d",(int)sizeof(e),r); close(xp[0]); t = malloc(max_output+1); f = 0; while (1) { r = read(op[0],t+f,max_output+1-f); if (r < 0) run_err(l,"command output read error: %s",strerror(errno)); if (r == 0) break; f += r; if (f > max_output) run_err(l,"command produced too much output"); } t[f] = '\0'; close(op[0]); return(new_string_val(t,f)); } static void re_fail(LINE *l, int err, regex_t *re, const char *msg) { int el; char *ebuf; el = regerror(err,re,0,0); if (el < 1) el = 1; ebuf = malloc(el); regerror(err,re,ebuf,el); ebuf[el-1] = '\0'; run_err(l,"%s (%s)\n",msg,ebuf); } static int regex_match(LINE *l, STRING *s_s, STRING *s_r) { regex_t re; regmatch_t match; int e; int rv; re.re_endp = s_r->body + s_r->len; e = regcomp(&re,s_r->body,REG_EXTENDED|REG_NOSUB|REG_PEND); if (e) re_fail(l,e,&re,"Invalid regular expression"); match.rm_so = 0; match.rm_eo = s_s->len; e = regexec(&re,s_s->body,0,&match,REG_STARTEND); switch (e) { case 0: rv = 1; break; case REG_NOMATCH: rv = 0; break; default: re_fail(l,e,&re,"Error matching regular expression"); break; } regfree(&re); return(rv); } static VAL perform_unary(LINE *l, U_OP op, VAL arg) { switch (op) { case U_MINUS: if (arg.t != VAL_NUM) run_err(l,"- applied to non-number"); arg = val_mutate(arg); switch (arg.u.n.t) { case NUM_INT: arg.u.n.u.i = - arg.u.n.u.i; break; case NUM_FLOAT: arg.u.n.u.f = - arg.u.n.u.f; break; } return(arg); break; case U_LOGNOT: if (arg.t != VAL_LOG) run_err(l,"! applied to non-boolean"); arg = val_mutate(arg); arg.u.l = ! arg.u.l; return(arg); break; case U_FOUND: return(MAKEVAL_LOG(val_equal(found,arg))); break; } abort(); } static int cmpfn_lt(int c) { return(c<0); } static int cmpfn_gt(int c) { return(c>0); } static int cmpfn_le(int c) { return(c<=0); } static int cmpfn_ge(int c) { return(c>=0); } static int cmpfn_eq(int c) { return(c==0); } static int cmpfn_ne(int c) { return(c!=0); } static long long int intop_and(long long int a, long long int b) { return(a&b); } static long long int intop_or(long long int a, long long int b) { return(a|b); } static long long int intop_xor(long long int a, long long int b) { return(a^b); } static VAL perform_binary(LINE *l, VAL lhs, B_OP op, VAL rhs) { VAL rv; switch (op) { case B_ADD: if ((lhs.t == VAL_NUM) && (rhs.t == VAL_NUM)) { switch (lhs.u.n.t) { case NUM_INT: switch (rhs.u.n.t) { case NUM_INT: rv = MAKEVAL_NUM(MAKENUM_INT(lhs.u.n.u.i+rhs.u.n.u.i)); break; case NUM_FLOAT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.i+rhs.u.n.u.f)); break; default: abort(); break; } break; case NUM_FLOAT: switch (rhs.u.n.t) { case NUM_INT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.f+rhs.u.n.u.i)); break; case NUM_FLOAT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.f+rhs.u.n.u.f)); break; default: abort(); break; } break; default: abort(); break; } val_drop(lhs); val_drop(rhs); return(rv); } else if ((lhs.t == VAL_STR) && (rhs.t == VAL_STR)) { unsigned char *t; int tl; tl = lhs.u.s->len + rhs.u.s->len; t = malloc(tl+1); bcopy(lhs.u.s->body,t,lhs.u.s->len); bcopy(rhs.u.s->body,t+lhs.u.s->len,rhs.u.s->len); t[tl] = '\0'; val_drop(lhs); val_drop(rhs); return(new_string_val(t,tl)); } run_err(l,"+ requires two numbers or two strings"); break; case B_SUB: if ((lhs.t == VAL_NUM) && (rhs.t == VAL_NUM)) { switch (lhs.u.n.t) { case NUM_INT: switch (rhs.u.n.t) { case NUM_INT: rv = MAKEVAL_NUM(MAKENUM_INT(lhs.u.n.u.i-rhs.u.n.u.i)); break; case NUM_FLOAT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.i-rhs.u.n.u.f)); break; default: abort(); break; } break; case NUM_FLOAT: switch (rhs.u.n.t) { case NUM_INT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.f-rhs.u.n.u.i)); break; case NUM_FLOAT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.f-rhs.u.n.u.f)); break; default: abort(); break; } break; default: abort(); break; } val_drop(lhs); val_drop(rhs); return(rv); } run_err(l,"- requires two numbers"); break; case B_MUL: if ((lhs.t == VAL_NUM) && (rhs.t == VAL_NUM)) { switch (lhs.u.n.t) { case NUM_INT: switch (rhs.u.n.t) { case NUM_INT: rv = MAKEVAL_NUM(MAKENUM_INT(lhs.u.n.u.i*rhs.u.n.u.i)); break; case NUM_FLOAT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.i*rhs.u.n.u.f)); break; default: abort(); break; } break; case NUM_FLOAT: switch (rhs.u.n.t) { case NUM_INT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.f*rhs.u.n.u.i)); break; case NUM_FLOAT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.f*rhs.u.n.u.f)); break; default: abort(); break; } break; default: abort(); break; } } else if ( ((lhs.t == VAL_NUM) && (rhs.t == VAL_STR)) || ((lhs.t == VAL_STR) && (rhs.t == VAL_NUM)) ) { int n; VAL nval; VAL sval; unsigned char *t; int tl; int i; if (lhs.t == VAL_NUM) { nval = lhs; sval = rhs; } else { nval = rhs; sval = lhs; } switch (nval.u.n.t) { case NUM_INT: n = nval.u.n.u.i; break; case NUM_FLOAT: n = nval.u.n.u.f; break; default: abort(); break; } if (n < 0) run_err(l,"* with number and string: number must not be negative"); tl = sval.u.s->len * n; t = malloc(tl+1); for (i=n-1;i>=0;i--) bcopy(sval.u.s->body,t+(i*sval.u.s->len),sval.u.s->len); t[tl] = '\0'; rv = new_string_val(t,tl); } else { run_err(l,"* requires two numbers or a number and a string"); } val_drop(lhs); val_drop(rhs); return(rv); break; case B_DIV: if ((lhs.t == VAL_NUM) && (rhs.t == VAL_NUM)) { switch (rhs.u.n.t) { case NUM_INT: if (rhs.u.n.u.i == 0) run_err(l,"division by zero"); switch (lhs.u.n.t) { case NUM_INT: rv = MAKEVAL_NUM(MAKENUM_INT(lhs.u.n.u.i/rhs.u.n.u.i)); break; case NUM_FLOAT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.f/rhs.u.n.u.i)); break; default: abort(); break; } break; case NUM_FLOAT: if (rhs.u.n.u.f == 0) run_err(l,"division by zero"); switch (lhs.u.n.t) { case NUM_INT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.i/rhs.u.n.u.f)); break; case NUM_FLOAT: rv = MAKEVAL_NUM(MAKENUM_FLOAT(lhs.u.n.u.f/rhs.u.n.u.f)); break; default: abort(); break; } break; default: abort(); break; } val_drop(lhs); val_drop(rhs); return(rv); } run_err(l,"/ requires two numbers"); break; { long long int (*intop)(long long int, long long int); const char *sform; case B_AND: intop = &intop_and; sform = "&"; if (0) { case B_OR: intop = &intop_or; sform = "|"; } if (0) { case B_XOR: intop = &intop_xor; sform = "^"; } if (!( (lhs.t == VAL_NUM) && (lhs.u.n.t == NUM_INT) && (rhs.t == VAL_NUM) && (rhs.u.n.t == NUM_INT) )) run_err(l,"%s requires two integers",sform); rv = MAKEVAL_NUM(MAKENUM_INT((*intop)(lhs.u.n.u.i,rhs.u.n.u.i))); val_drop(lhs); val_drop(rhs); return(rv); } break; case B_AREF: if (lhs.t == VAL_ARR) { AEL *el; el = array_find_el(l,lhs,rhs,0); if (! el) run_err(l,"no such element in array"); return(val_copy(el->val)); } break; { int (*cmpfn)(int); const char *opstr; int cmp; case B_LT: cmpfn = &cmpfn_lt; opstr = "<"; if (0) { case B_GT: cmpfn = &cmpfn_gt; opstr = ">"; } if (0) { case B_LE: cmpfn = &cmpfn_le; opstr = "<="; } if (0) { case B_GE: cmpfn = &cmpfn_ge; opstr = ">="; } if (0) { case B_EQ: cmpfn = &cmpfn_eq; opstr = "=="; } if (0) { case B_NE: cmpfn = &cmpfn_ne; opstr = "!="; } cmp = compare(lhs,rhs,1); val_drop(lhs); val_drop(rhs); return(MAKEVAL_LOG((*cmpfn)(cmp))); } break; case B_ANDAND: case B_OROR: // handled by our caller, to short-circuit abort(); break; case B_XORXOR: { int rv; if ((lhs.t != VAL_LOG) || (rhs.t != VAL_LOG)) run_err(l,"^^ requires two booleans"); rv = lhs.u.l ? !rhs.u.l : rhs.u.l; val_drop(lhs); val_drop(rhs); return(MAKEVAL_LOG(rv)); } break; case B_REGEX: if ((lhs.t != VAL_STR) || (rhs.t != VAL_STR)) run_err(l,"~ requires two strings"); return(MAKEVAL_LOG(regex_match(l,lhs.u.s,rhs.u.s))); break; } abort(); } static long long int coerce_to_int(LINE *l, VAL v, const char *errmsg) { switch (v.t) { case VAL_NUM: switch (v.u.n.t) { case NUM_INT: return(v.u.n.u.i); break; case NUM_FLOAT: return(v.u.n.u.f); break; default: abort(); break; } break; case VAL_STR: return(strtoll(v.u.s->body,0,0)); break; case VAL_LOG: return(v.u.l?1:0); break; default: run_err(l,"%s",errmsg); break; } } static VAL builtin_int(LINE *l, int nargs, VAL *args) { if (nargs != 1) abort(); return(MAKEVAL_NUM(MAKENUM_INT(coerce_to_int(l,args[0],"invalid argument to $int")))); } static VAL builtin_len(LINE *l, int nargs, VAL *args) { if (nargs != 1) abort(); switch (args[0].t) { case VAL_STR: return(MAKEVAL_NUM(MAKENUM_INT(args[0].u.s->len))); break; case VAL_ARR: { int i; AEL *e; i = 0; for (e=args[0].u.a->els;e;e=e->link) i ++; return(MAKEVAL_NUM(MAKENUM_INT(i))); } break; default: run_err(l,"invalid argument type to $len"); break; } } static VAL builtin_run(LINE *l, int nargs, VAL *args) { if (nargs != 1) abort(); return(command_output(l,args[0])); } static VAL builtin_float(LINE *l, int nargs, VAL *args) { if (nargs != 1) abort(); switch (args[0].t) { case VAL_NUM: switch (args[0].u.n.t) { case NUM_INT: return(MAKEVAL_NUM(MAKENUM_FLOAT(args[0].u.n.u.i))); break; case NUM_FLOAT: return(val_copy(args[0])); break; default: abort(); break; } break; case VAL_STR: return(MAKEVAL_NUM(MAKENUM_FLOAT(strtod(args[0].u.s->body,0)))); break; case VAL_LOG: return(MAKEVAL_NUM(MAKENUM_FLOAT(args[0].u.l?1:0))); break; default: run_err(l,"invalid argument type to $float"); break; } } static VAL builtin_substr(LINE *l, int nargs, VAL *args) { int n1; int n2; char *t; int tl; if ((nargs < 2) || (nargs > 3)) abort(); if (args[0].t != VAL_STR) run_err(l,"invalid first arg to $substr"); n1 = coerce_to_int(l,args[1],"invalid second arg to $substr"); if (nargs > 2) { n2 = coerce_to_int(l,args[2],"invalid third arg to $substr"); } else { n2 = args[0].u.s->len; } if (n1 < 0) { n1 += args[0].u.s->len; if (n1 < 0) { n2 += n1; n1 = 0; if (n2 < 0) n2 = 0; } } if (n1 > args[0].u.s->len) { n1 = 0; n2 = 0; } if (n2 < 0) { n2 += args[0].u.s->len - n1; if (n2 < 0) n2 = 0; } if (n1+n2 > args[0].u.s->len) n2 = args[0].u.s->len - n1; tl = n2; t = malloc(tl+1); if (tl > 0) bcopy(args[0].u.s->body+n1,t,tl); t[tl] = '\0'; return(new_string_val(t,tl)); } static VAL call_function(LINE *l, IDENT *f, int nargs, VAL *args) { int i; VAL v; int minargs; int maxargs; VAL (*impl)(LINE *, int, VAL *); impl = 0; switch (f->namelen) { case 4: switch (f->name[1]) { case 'i': if (! bcmp(f->name,"$int",4)) { minargs = 1; maxargs = 1; impl = &builtin_int; } break; case 'l': if (! bcmp(f->name,"$len",4)) { minargs = 1; maxargs = 1; impl = &builtin_len; } break; case 'r': if (! bcmp(f->name,"$run",4)) { minargs = 1; maxargs = 1; impl = &builtin_run; } break; } break; case 6: if (! bcmp(f->name,"$float",6)) { minargs = 1; maxargs = 1; impl = &builtin_float; } break; case 7: if (! bcmp(f->name,"$substr",7)) { minargs = 2; maxargs = 3; impl = &builtin_substr; } break; } if (! impl) run_err(l,"unrecognized function name `%.*s'",f->namelen,f->name); if (nargs < minargs) run_err(l,"too few args to %.*s",f->namelen,f->name); if (nargs > maxargs) run_err(l,"too many args to %.*s",f->namelen,f->name); v = (*impl)(l,nargs,args); for (i=nargs-1;i>=0;i--) val_drop(args[i]); return(v); } static VAL eval_expr(LINE *l, EXPR *e) { switch (e->t) { case X_IDENT: if (e->u.x_ident->value.t == VAL_NIL) { run_err(l,"`%.*s' has no value",e->u.x_ident->namelen,e->u.x_ident->name); } return(val_copy(e->u.x_ident->value)); break; case X_NUM: return(MAKEVAL_NUM(e->u.x_num)); break; case X_STR: e->u.x_str->refcnt ++; return(MAKEVAL_STR(e->u.x_str)); break; case X_PAREN: return(eval_expr(l,e->u.x_paren)); break; case X_TAG: return(eval_tag(l,e->u.x_tag)); break; case X_NILARY: return(perform_nilary(l,e->u.x_nilary.op)); break; case X_UNARY: { VAL arg; arg = eval_expr(l,e->u.x_unary.arg); return(perform_unary(l,e->u.x_unary.op,arg)); } break; case X_BINARY: { VAL lhs; VAL rhs; switch (e->u.x_binary.op) { case B_ANDAND: lhs = eval_expr(l,e->u.x_binary.lhs); if (lhs.t != VAL_LOG) run_err(l,"&& requires two booleans"); if (! lhs.u.l) return(lhs); val_drop(lhs); rhs = eval_expr(l,e->u.x_binary.rhs); if (rhs.t != VAL_LOG) run_err(l,"&& requires two booleans"); return(rhs); break; case B_OROR: lhs = eval_expr(l,e->u.x_binary.lhs); if (lhs.t != VAL_LOG) run_err(l,"|| requires two booleans"); if (lhs.u.l) return(lhs); val_drop(lhs); rhs = eval_expr(l,e->u.x_binary.rhs); if (rhs.t != VAL_LOG) run_err(l,"|| requires two booleans"); return(rhs); break; default: lhs = eval_expr(l,e->u.x_binary.lhs); rhs = eval_expr(l,e->u.x_binary.rhs); return(perform_binary(l,lhs,e->u.x_binary.op,rhs)); break; } } break; case X_TERNARY: { VAL v; v = eval_expr(l,e->u.x_ternary.lhs); if (v.t != VAL_LOG) run_err(l,"?: condition must be boolean"); if (v.u.l) { val_drop(v); return(eval_expr(l,e->u.x_ternary.mhs)); } else { val_drop(v); return(eval_expr(l,e->u.x_ternary.rhs)); } } break; case X_FXN: { VAL *vv; int i; VAL v; vv = malloc(e->u.x_fxn.nargs*sizeof(VAL)); for (i=e->u.x_fxn.nargs-1;i>=0;i--) vv[i] = eval_expr(l,e->u.x_fxn.args[i]); v = call_function(l,e->u.x_fxn.fxn,e->u.x_fxn.nargs,vv); return(v); } break; } abort(); } static VAL eval_tag(LINE *l, EXPR *e) { if (e->t == X_IDENT) return(MAKEVAL_TAG(e->u.x_ident)); return(eval_expr(l,e)); } static int skip_to(int x, int indent) { while (1) { if (x >= scriptlen) return(x); if (script[x]->indent <= indent) return(x); x ++; } } static int do_send(LINE *l) { VAL v; if (l->t != LINE_SEND) abort(); v = eval_expr(l,l->u.l_send.text); if (v.t != VAL_STR) run_err(l,"`send' argument must be a string"); send_string(v.u.s); val_drop(v); return(l->inx+1); } static int do_print(LINE *l) { VAL v; if (l->t != LINE_PRINT) abort(); v = eval_expr(l,l->u.l_print.text); if (v.t != VAL_STR) run_err(l,"`print' argument must be a string"); print_string(v.u.s); val_drop(v); return(l->inx+1); } static int do_expect(LINE *l) { VAL tv; VAL wv; if (l->t != LINE_EXPECT) abort(); tv = eval_tag(l,l->u.l_expect.tag); wv = eval_expr(l,l->u.l_expect.want); if (wv.t != VAL_STR) run_err(l,"`expect' expectation must be a string"); add_expect(tv,wv.u.s,0); val_drop(found); found = MAKEVAL_NIL(); return(l->inx+1); } static int do_next(LINE *l) { VAL tv; VAL wv; if (l->t != LINE_NEXT) abort(); tv = eval_tag(l,l->u.l_next.tag); wv = eval_expr(l,l->u.l_next.want); if (wv.t != VAL_STR) run_err(l,"`next' expectation must be a string"); add_expect(tv,wv.u.s,1); val_drop(found); found = MAKEVAL_NIL(); return(l->inx+1); } static int do_await(LINE *l) { VAL tv; VAL wv; if (l->t != LINE_AWAIT) abort(); tv = eval_expr(l,l->u.l_await.timeout); if (tv.t != VAL_NUM) run_err(l,"`await' timeout must be a number"); if (l->u.l_await.string) { wv = eval_expr(l,l->u.l_await.string); if (wv.t != VAL_STR) run_err(l,"`await' expectation must be a string"); do_await_str(tv.u.n,wv.u.s); } else { do_await_prep(tv.u.n); } return(l->inx+1); } static int do_drain(LINE *l) { VAL tv; EXPECTATION *esave; VAL fsave; if (l->t != LINE_DRAIN) abort(); tv = eval_expr(l,l->u.l_await.timeout); if (tv.t != VAL_NUM) run_err(l,"`drain' timeout must be a number"); esave = expected; expected = 0; fsave = found; found = MAKEVAL_NIL(); do_await_prep(tv.u.n); if (expected) abort(); expected = esave; val_drop(found); found = fsave; return(l->inx+1); } static int do_delay(LINE *l) { VAL tv; if (l->t != LINE_DELAY) abort(); tv = eval_expr(l,l->u.l_await.timeout); if (tv.t != VAL_NUM) run_err(l,"`delay' timeout must be a number"); switch (tv.u.n.t) { case NUM_INT: delay_time(tv.u.n.u.i); break; case NUM_FLOAT: delay_time(tv.u.n.u.f); break; default: abort(); break; } return(l->inx+1); } static int do_loop(LINE *l) { LOOPSTACK ls; if (l->t != LINE_LOOP) abort(); ls.tag = l->u.l_loop.tag ? eval_tag(l,l->u.l_loop.tag) : MAKEVAL_NIL(); ls.flags = 0; ls.link = loops; ls.end = skip_to(l->inx+1,l->indent); ls.through = -1; loops = &ls; while (1) { run_beginning(l->inx+1,l->indent+1); if (ls.flags & LOOP_BREAK) { if (loops != &ls) abort(); loops = ls.link; return(ls.end); } if (ls.through >= 0) { if (loops != &ls) abort(); loops = ls.link; return(ls.through); } } } static int do_break(LINE *l) { VAL tag; LOOPSTACK *ls; if (l->t != LINE_BREAK) abort(); tag = l->u.l_break.tag ? eval_tag(l,l->u.l_break.tag) : MAKEVAL_NIL(); do <"found"> { for (ls=loops;ls;ls=ls->link) { if ((tag.t == VAL_NIL) || val_equal(tag,ls->tag)) break <"found">; } run_err(l,"no matching loop found"); } while (0); if (ls == loops) { ls->flags |= LOOP_BREAK; } else { loops->through = l->inx; } return(loops->end); } static int do_continue(LINE *l) { VAL tag; LOOPSTACK *ls; if (l->t != LINE_CONTINUE) abort(); tag = l->u.l_continue.tag ? eval_tag(l,l->u.l_continue.tag) : MAKEVAL_NIL(); do <"found"> { for (ls=loops;ls;ls=ls->link) { if ((tag.t == VAL_NIL) || val_equal(tag,ls->tag)) break <"found">; } run_err(l,"no matching loop found"); } while (0); if (ls != loops) loops->through = l->inx; return(loops->end); } static int do_set(LINE *l) { VAL v; if (l->t != LINE_SET) abort(); v = eval_expr(l,l->u.l_set.rhs); if (l->u.l_set.lhs->t == X_IDENT) { IDENT *i; i = l->u.l_set.lhs->u.x_ident; val_drop(i->value); i->value = v; } else if ((l->u.l_set.lhs->t == X_BINARY) && (l->u.l_set.lhs->u.x_binary.op == B_AREF)) { AEL *el; el = array_find_el(l, eval_expr(l,l->u.l_set.lhs->u.x_binary.lhs), eval_expr(l,l->u.l_set.lhs->u.x_binary.rhs), 1); val_drop(el->val); el->val = v; } else { abort(); } return(l->inx+1); } static int do_param(LINE *l) { VAL v; if (l->t != LINE_PARAM) abort(); v = eval_expr(l,l->u.l_param.rhs); switch (l->u.l_param.parno) { case PAR_INTERCHAR: if (v.t != VAL_NUM) run_err(l,"`interchar' value must be a number"); switch (v.u.n.t) { case NUM_INT: if (v.u.n.u.i < 0) run_err(l,"`interchar' value must be non-negative"); interchar = v.u.n.u.i * (TIME)1000000; break; case NUM_FLOAT: if (v.u.n.u.f < 0) run_err(l,"`interchar' value must be non-negative"); interchar = v.u.n.u.f * 1e6; break; default: abort(); break; } break; case PAR_MAX_OUTPUT: if ((v.t != VAL_NUM) || (v.u.n.t != NUM_INT)) run_err(l,"`max_output' value must be an integer"); if (v.u.n.u.i <= 0) run_err(l,"`max_output' value must be strictly positive"); max_output = v.u.n.u.i; break; default: abort(); break; } return(l->inx+1); } static int do_if_elif(LINE *l) { const char *kindstr; VAL v; int want; int x; switch (l->t) { case LINE_IF: kindstr = "if"; break; case LINE_ELIF: kindstr = "elif"; break; default: abort(); break; } v = eval_expr(l,l->u.l_if.cond); if (v.t != VAL_LOG) run_err(l,"`%s' condition must be boolean",kindstr); want = v.u.l ? 1 : 0; val_drop(v); if (want) { x = run_beginning(l->inx+1,l->indent+1); while ( (x >= 0) && (x < scriptlen) && (script[x]->indent == l->indent) && ((script[x]->t == LINE_ELIF) || (script[x]->t == LINE_ELSE)) ) { x = skip_to(x+1,l->indent); } return(x); } else { x = skip_to(l->inx+1,l->indent); if ( (x >= 0) && (x < scriptlen) && (script[x]->indent == l->indent) && (script[x]->t == LINE_ELSE) ) { return(x+1); } return(x); } } static int do_else(LINE *l) { // any correct structure handles the LINE_ELSE in do_if_elif run_err(l,"misnested if-else"); } static int do_exit(LINE *l) { VAL v; if (l->t != LINE_EXIT) abort(); v = l->u.l_exit.ecode ? eval_expr(l,l->u.l_exit.ecode) : MAKEVAL_NUM(MAKENUM_INT(0)); if ((v.t != VAL_NUM) || (v.u.n.t != NUM_INT)) run_err(l,"`exit' argument must be an integer"); exit(v.u.n.u.i); } static int run_line(int x) { LINE *l; l = script[x]; if (tracing) printf("running line %d: %.*s\n",l->lno,l->textlen,l->text); switch (l->t) { case LINE_SEND: return(do_send(l)); break; case LINE_PRINT: return(do_print(l)); break; case LINE_EXPECT: return(do_expect(l)); break; case LINE_NEXT: return(do_next(l)); break; case LINE_AWAIT: return(do_await(l)); break; case LINE_DELAY: return(do_delay(l)); break; case LINE_DRAIN: return(do_drain(l)); break; case LINE_LOOP: return(do_loop(l)); break; case LINE_BREAK: return(do_break(l)); break; case LINE_CONTINUE: return(do_continue(l)); break; case LINE_SET: return(do_set(l)); break; case LINE_PARAM: return(do_param(l)); break; case LINE_IF: return(do_if_elif(l)); break; case LINE_ELIF: return(do_if_elif(l)); break; case LINE_ELSE: return(do_else(l)); break; case LINE_EXIT: return(do_exit(l)); break; default: abort(); break; } } static int rtest_io(void *arg __attribute__((__unused__))) { return(iorf=ionexto))); } static void wr_io(void *arg __attribute__((__unused__))) { int nw; if (interchar) { nw = aio_oq_writev(&iooq,iofd,1); if (nw > 0) ionexto = get_now() + interchar; } else { nw = aio_oq_writev(&iooq,iofd,-1); } if (nw < 0) { fprintf(stderr,"%s: %s write: %s\n",__progname,iotext,strerror(errno)); exit(1); } aio_oq_dropdata(&iooq,nw); } static void open_null(void) { devnull = open("/dev/null",O_RDWR,0); if (devnull < 0) fprintf(stderr,"%s: can't open /dev/null: %s\n",__progname,strerror(errno)); while (devnull < 3) { devnull = dup(devnull); if (devnull < 0) fprintf(stderr,"%s: dup /dev/null failed: %s\n",__progname,strerror(errno)); } } static void init_run(void) { expected = 0; found = MAKEVAL_NIL(); loops = 0; aio_oq_init(&iooq); ionexto = 0; interchar = 0; max_output = DEFAULT_MAX_OUTPUT; aio_add_poll(iofd,&rtest_io,&wtest_io,&rd_io,&wr_io,0); watch_last = -1; } static int run_beginning(int x, int minindent) { while (1) { if ((x < 0) || (x > scriptlen)) abort(); if ((x == scriptlen) || (script[x]->indent < minindent)) return(x); x = run_line(x); } } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); loadscript(); open_null(); openio(); init_run(); run_beginning(0,0); exit(0); }