diff options
| -rw-r--r-- | builtins.c | 19 | ||||
| -rw-r--r-- | develop.c | 3 | ||||
| -rw-r--r-- | except.c | 9 | ||||
| -rw-r--r-- | footobar.c | 1 | ||||
| -rw-r--r-- | input.c | 4 | ||||
| -rw-r--r-- | lex.c | 1 | ||||
| -rw-r--r-- | parse.y | 4 | ||||
| -rw-r--r-- | rc.1 | 11 | ||||
| -rw-r--r-- | rc.h | 6 | ||||
| -rw-r--r-- | tree.c | 11 | ||||
| -rw-r--r-- | trip.rc | 87 | ||||
| -rw-r--r-- | walk.c | 97 |
12 files changed, 207 insertions, 46 deletions
@@ -20,7 +20,7 @@ #include "rlimit.h" #include "sigmsgs.h" -static void b_break(char **), b_cd(char **), b_eval(char **), b_exit(char **), +static void b_break(char **), b_cd(char **), b_continue(char **), b_eval(char **), b_exit(char **), b_newpgrp(char **), b_return(char **), b_shift(char **), b_umask(char **), b_wait(char **), b_whatis(char **); @@ -39,6 +39,7 @@ static struct { { b_break, "break" }, { b_builtin, "builtin" }, { b_cd, "cd" }, + { b_continue, "continue" }, #if RC_ECHO { b_echo, "echo" }, #endif @@ -83,8 +84,8 @@ extern void funcall(char **av) { except(eVarstack, star, &e2); walk(treecpy(fnlookup(*av), nalloc), TRUE); varrm("*", TRUE); - unexcept(); /* eVarstack */ - unexcept(); /* eReturn */ + unexcept(eVarstack); + unexcept(eReturn); } static void arg_count(char *name) { @@ -216,6 +217,16 @@ static void b_break(char **av) { rc_raise(eBreak); } +/* raise a "continue" exception to finish early an iteration of 'for' and 'while' loops */ + +static void b_continue(char **av) { + if (av[1] != NULL) { + arg_count("continue"); + return; + } + rc_raise(eContinue); +} + /* shift $* n places (default 1) */ static void b_shift(char **av) { @@ -401,7 +412,7 @@ extern void b_dot(char **av) { except(eVarstack, star, &e); doit(TRUE); varrm("*", TRUE); - unexcept(); /* eVarstack */ + unexcept(eVarstack); interactive = old_i; } @@ -118,6 +118,9 @@ void dump(Node *n, int indent) { case nIf: dump_2("nIf", n, indent); break; + case nIfnot: + dump_1("nIfnot", n, indent); + break; case nLappend: dump_2("nLappend", n, indent); break; @@ -22,13 +22,14 @@ extern void except(ecodes e, Edata data, Estack *ex) { estack = ex; estack->e = e; estack->data = data; - if (e == eError || e == eBreak || e == eReturn) + if (e == eError || e == eBreak || e == eReturn || e == eContinue) estack->interactive = interactive; } /* remove an exception, restore last interactive value */ -extern void unexcept() { +extern void unexcept(ecodes e) { + assert(e == estack->e); switch (estack->e) { default: break; @@ -66,8 +67,10 @@ extern void rc_raise(ecodes e) { exit(1); /* child processes exit on an error/signal */ for (; estack != NULL; estack = estack->prev) if (estack->e != e) { - if (e == eBreak && estack->e != eArena && estack->e != eVarstack) + if (e == eBreak && (estack->e != eArena && estack->e != eVarstack && estack->e != eContinue)) rc_error("break outside of loop"); + else if (e == eContinue && (estack->e != eVarstack)) + rc_error("continue outside of loop"); else if (e == eReturn && estack->e == eError) /* can return from loops inside functions */ rc_error("return outside of function"); switch (estack->e) { @@ -67,6 +67,7 @@ static bool Tconv(Format *f, int ignore) { case nElse: fmtprint(f, "{%T}else %T", n->u[0].p, n->u[1].p); break; case nNewfn: fmtprint(f, "fn %T {%T}", n->u[0].p, n->u[1].p); break; case nIf: fmtprint(f, "if(%T)%T", n->u[0].p, n->u[1].p); break; + case nIfnot: fmtprint(f, "if not %T", n->u[0].p); break; case nOrelse: fmtprint(f, "%T||%T", n->u[0].p, n->u[1].p); break; case nArgs: fmtprint(f, "%T %T", n->u[0].p, n->u[1].p); break; case nSwitch: fmtprint(f, "switch(%T){%T}", n->u[0].p, n->u[1].p); break; @@ -314,10 +314,10 @@ extern Node *doit(bool clobberexecit) { else if (dashex && dashen) fprint(2, "%T\n", parsetree); } - unexcept(); /* eArena */ + unexcept(eArena); } popinput(); - unexcept(); /* eError */ + unexcept(eError); return parsetree; } @@ -159,6 +159,7 @@ top: while ((c = gchar()) == ' ' || c == '\t') if (*buf == 'f' && buf[1] == 'n') return FN; if (*buf == 'i' && buf[1] == 'n') return IN; } + if (streq(buf, "not")) return NOT; if (streq(buf, "for")) return FOR; if (streq(buf, "else")) return ELSE; if (streq(buf, "switch")) return SWITCH; @@ -13,7 +13,7 @@ static Node *star, *nolist; Node *parsetree; /* not using yylval because bison declares it as an auto */ %} -%token ANDAND BACKBACK BANG CASE COUNT DUP ELSE END FLAT FN FOR IF IN +%token ANDAND BACKBACK BANG CASE COUNT DUP ELSE END FLAT FN FOR IF IN NOT %token OROR PIPE REDIR SREDIR SUB SUBSHELL SWITCH TWIDDLE WHILE WORD HUH %left '^' '=' @@ -106,6 +106,7 @@ cmd : /* empty */ %prec WHILE { $$ = NULL; } | simple | brace epilog { $$ = mk(nBrace,$1,$2); } | IF paren optnl iftail { $$ = mk(nIf,$2,$4); } + | IF NOT optnl cmd { $$ = mk(nIfnot,$4); } | FOR '(' word IN words ')' optnl cmd { $$ = mk(nForin,$3,$5,$8); } | FOR '(' word ')' optnl cmd { $$ = mk(nForin,$3,star,$6); } | WHILE paren optnl cmd { $$ = mk(nWhile,$2,$4); } @@ -158,6 +159,7 @@ keyword : FOR { $$ = "for"; } | IN { $$ = "in"; } | WHILE { $$ = "while"; } | IF { $$ = "if"; } + | NOT { $$ = "not"; } | SWITCH { $$ = "switch"; } | FN { $$ = "fn"; } | ELSE { $$ = "else"; } @@ -1662,6 +1662,17 @@ With no argument, changes the current directory to .Cr "$home" . .TP +.B continue +Continues the innermost +.Cr for +or +.Cr while +loop, +as in C. +It is an error to invoke +.B continue +outside of a loop. +.TP \fBecho \fR[\fB\-n\fR] [\fB\-\|\-\fR] [\fIarg ...\fR] Prints its arguments to standard output, terminated by a newline. Arguments are separated by spaces. @@ -36,12 +36,12 @@ typedef union Edata Edata; typedef enum nodetype { nAndalso, nAssign, nBackq, nBang, nBody, nCbody, nNowait, nBrace, nConcat, nCount, nElse, nFlat, nDup, nEpilog, nNewfn, nForin, nIf, - nOrelse, nPipe, nPre, nRedir, nRmfn, nArgs, nSubshell, nCase, + nIfnot, nOrelse, nPipe, nPre, nRedir, nRmfn, nArgs, nSubshell, nCase, nSwitch, nMatch, nVar, nVarsub, nWhile, nWord, nLappend, nNmpipe } nodetype; typedef enum ecodes { - eError, eBreak, eReturn, eVarstack, eArena, eFifo, eFd + eError, eBreak, eReturn, eVarstack, eArena, eFifo, eFd, eContinue } ecodes; typedef enum bool { @@ -185,7 +185,7 @@ extern bool outstanding_cmdarg(void); extern void pop_cmdarg(bool); extern void rc_raise(ecodes); extern void except(ecodes, Edata, Estack *); -extern void unexcept(void); +extern void unexcept(ecodes); extern void rc_error(char *); extern void sigint(int); @@ -26,11 +26,11 @@ extern Node *mk(int /*nodetype*/ t,...) { break; case nBang: case nNowait: case nCount: case nFlat: case nRmfn: case nSubshell: - case nVar: case nCase: + case nVar: case nCase: case nIfnot: n = nalloc(offsetof(Node, u[1])); n->u[0].p = va_arg(ap, Node *); break; - case nAndalso: case nAssign: case nBackq: case nBody: case nBrace: case nConcat: + case nAndalso: case nAssign: case nBackq: case nBody: case nBrace: case nConcat: case nElse: case nEpilog: case nIf: case nNewfn: case nCbody: case nOrelse: case nPre: case nArgs: case nSwitch: case nMatch: case nVarsub: case nWhile: case nLappend: @@ -60,6 +60,13 @@ extern Node *mk(int /*nodetype*/ t,...) { break; } n->type = t; + if (t == nBody && n->u[1].p && n->u[1].p->type == nIfnot) { + if (n->u[0].p->type == nIf) { + fprint(2, "here i am!\n"); + } else + rc_error("`if not' must follow `if'"); + + } va_end(ap); return n; } @@ -17,8 +17,11 @@ fn expect { fn submatch { if (!~ $#* 3) fail incorrect invocation of submatch - prompt=$nl if (!~ `` $nl {$rc -ic $1>[2=1]} $2) + got = `` $nl { prompt=$nl $rc -ic $1>[2=1] } + if (!~ $got $2) { + echo got $got expected $2 fail $3 + } } fn sigexit sigint sigquit sigsegv fn sigexit { @@ -598,8 +601,84 @@ x=$tmpdir/qux*/foo rm -rf $tmpdir +############################################################################# +## Check builtin continue in while loop. +############################################################################# +q='''' +C=$q^while-continue$q +L=() + +save_star = * +ten = a^(1 2 3 4 5 6 7 8 9 10) +* = $ten +while (! ~ $#* 0) { + n = $1; shift + if (~ $n a2 a3) { + continue + } + L=($L $n) +} +* = $save_star; save_star = () + +if (!~ $#L 8) { + fail Wrong length of list from $C: $#L +} +if (!~ $L(1) a1) { + fail First element of $C list is not a1: $L(1) +} +if (!~ $L(2) a4) { + fail Second element of $C list is not a4: $L(2) +} +C=() L=() + +############################################################################# +## Check builtin continue in for loop. +############################################################################# +C=$q^for-continue$q +L=() + +for (x in a b c d e f g) { + if (~ $x c f) { + continue + } + L=($L $x) +} +if (!~ $#L 5) { + fail Wrong length of list from $C +} +if (!~ $L(1) a) { + fail First element of $C list is not a: $L(1) +} +if (!~ $L(3) d) { + fail Third element of $C list is not d: $L(3) +} +if (!~ $L(5) g) { + fail Fifth element of $C list is not g: $L(5) +} +C=() L=() + +submatch continue 'rc: continue outside of loop' 'continue outside of loop' + +############################################################################# +## check builtin continue in for loop (2) +############################################################################# +L=() +for (x in a b c d e f g) { + if (~ $x b d) { + continue + } + L=($L $x) + if (~ $x f) { + break; + } +} + +if (!~ $^L 'a c e f') { + fail List should be '(a c e f)', but is $L +} + # test support for unquoted = -submatch 'echo foo = bar' 'foo = bar' 'unquoted equals 2' +submatch 'echo foo = bar' 'foo = bar' 'unquoted equals 1' submatch 'echo foo=bar' 'foo=bar' 'unquoted equals 2' submatch 'echo foo=' 'foo=' 'unquoted equals 3' submatch 'echo =bar; whatis -v echo' 'echo=bar' 'unquoted equals 4' @@ -624,3 +703,7 @@ $rc -ec 'X=`{false}' if (~ $status 0) { fail '"rc -e" exits with zero status when backquote command returns non-zero' } + +# exercise "if not" +submatch 'if (false) echo foo; if not echo bar' 'bar' 'if not 1' +submatch 'if (false) echo foo; echo qux; if not echo bar' 'rc: `if not'' must follow `if''' 'if not 2' @@ -17,6 +17,8 @@ static bool haspreredir(Node *); static bool isallpre(Node *); static bool dofork(bool); static void dopipe(Node *); +static void loop_body(Node* n); +static int if_state = 2; /* last if, for "if not" interactive top-level */ /* Tail-recursive version of walk() */ @@ -32,6 +34,7 @@ top: sigchk(); set(TRUE); return TRUE; } + if (parent && n->type != nIfnot) if_state = 2; switch (n->type) { case nArgs: case nBackq: case nConcat: case nCount: case nFlat: case nLappend: case nRedir: case nVar: @@ -95,58 +98,71 @@ top: sigchk(); true_cmd = true_cmd->u[0].p; } cond = TRUE; - if (!walk(n->u[0].p, TRUE)) - true_cmd = false_cmd; /* run the else clause */ + if_state = walk(n->u[0].p, TRUE); cond = oldcond; - WALK(true_cmd, parent); + WALK(if_state ? true_cmd : false_cmd, parent); + } + case nIfnot: { + if (if_state == 2) + rc_error("`if not' must follow `if'"); + if (if_state == 0) + walk(n->u[0].p, TRUE); + if_state = 2; + break; } case nWhile: { - Jbwrap j; - Edata jbreak; - Estack e1, e2; - bool testtrue, oldcond = cond; + Jbwrap break_jb; + Edata break_data; + Estack break_stack; + bool testtrue; + const bool oldcond = cond; cond = TRUE; if (!walk(n->u[0].p, TRUE)) { /* prevent spurious breaks inside test */ cond = oldcond; break; } cond = oldcond; - if (sigsetjmp(j.j, 1)) + if (sigsetjmp(break_jb.j, 1)) break; - jbreak.jb = &j; - except(eBreak, jbreak, &e1); + break_data.jb = &break_jb; + except(eBreak, break_data, &break_stack); + + cond = oldcond; do { - Edata block; - block.b = newblock(); - except(eArena, block, &e2); - walk(n->u[1].p, TRUE); + Edata iter_data; + Estack iter_stack; + iter_data.b = newblock(); + except(eArena, iter_data, &iter_stack); + loop_body(n->u[1].p); cond = TRUE; testtrue = walk(n->u[0].p, TRUE); cond = oldcond; - unexcept(); /* eArena */ + unexcept(eArena); } while (testtrue); cond = oldcond; - unexcept(); /* eBreak */ + unexcept(eBreak); break; } case nForin: { List *l, *var = glom(n->u[0].p); - Jbwrap j; - Estack e1, e2; - Edata jbreak; - if (sigsetjmp(j.j, 1)) + Jbwrap break_jb; + Edata break_data; + Estack break_stack; + if (sigsetjmp(break_jb.j, 1)) break; - jbreak.jb = &j; - except(eBreak, jbreak, &e1); + break_data.jb = &break_jb; + except(eBreak, break_data, &break_stack); + for (l = listcpy(glob(glom(n->u[1].p)), nalloc); l != NULL; l = l->n) { - Edata block; + Edata iter_data; + Estack iter_stack; assign(var, word(l->w, NULL), FALSE); - block.b = newblock(); - except(eArena, block, &e2); - walk(n->u[2].p, TRUE); - unexcept(); /* eArena */ + iter_data.b = newblock(); + except(eArena, iter_data, &iter_stack); + loop_body(n->u[2].p); + unexcept(eArena); } - unexcept(); /* eBreak */ + unexcept(eBreak); break; } case nSubshell: @@ -240,7 +256,7 @@ top: sigchk(); except(eVarstack, var, &e); walk(n->u[1].p, parent); varrm(v->w, TRUE); - unexcept(); /* eVarstack */ + unexcept(eVarstack); } } else panic("unexpected node in preredir section of walk"); @@ -364,3 +380,26 @@ static void dopipe(Node *n) { setpipestatus(stats, i); sigchk(); } + +/* From http://en.cppreference.com/w/c/program/setjmp + * According to the C standard setjmp() must appear only in the following 4 constructs: + * 1. switch (setjmp(args)) {statements} + * 2. if (setjmp(args) == Const) {statements} with any of + * operators: ==, !=, <, >, <=, >= + * 3. while (! setjmp(args)) {statements} + * 4. setjmp(args); +*/ +static void loop_body(Node* nd) +{ + Node *volatile n = nd; + Jbwrap cont_jb; + Edata cont_data; + Estack cont_stack; + + if (sigsetjmp(cont_jb.j, 1) == 0) { + cont_data.jb = &cont_jb; + except(eContinue, cont_data, &cont_stack); + walk(n, TRUE); + unexcept(eContinue); + } +} |