summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--builtins.c19
-rw-r--r--develop.c3
-rw-r--r--except.c9
-rw-r--r--footobar.c1
-rw-r--r--input.c4
-rw-r--r--lex.c1
-rw-r--r--parse.y4
-rw-r--r--rc.111
-rw-r--r--rc.h6
-rw-r--r--tree.c11
-rw-r--r--trip.rc87
-rw-r--r--walk.c97
12 files changed, 207 insertions, 46 deletions
diff --git a/builtins.c b/builtins.c
index a0f0ebe..477ff71 100644
--- a/builtins.c
+++ b/builtins.c
@@ -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;
}
diff --git a/develop.c b/develop.c
index 0ebec7f..0bffde2 100644
--- a/develop.c
+++ b/develop.c
@@ -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;
diff --git a/except.c b/except.c
index 37696c4..aeb1da9 100644
--- a/except.c
+++ b/except.c
@@ -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) {
diff --git a/footobar.c b/footobar.c
index d6f6dfd..0af2891 100644
--- a/footobar.c
+++ b/footobar.c
@@ -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;
diff --git a/input.c b/input.c
index 7773ace..7e7ed33 100644
--- a/input.c
+++ b/input.c
@@ -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;
}
diff --git a/lex.c b/lex.c
index 195e8f6..58c3df5 100644
--- a/lex.c
+++ b/lex.c
@@ -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;
diff --git a/parse.y b/parse.y
index 55f7e80..b3f1255 100644
--- a/parse.y
+++ b/parse.y
@@ -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"; }
diff --git a/rc.1 b/rc.1
index b2e7c52..79e39ed 100644
--- a/rc.1
+++ b/rc.1
@@ -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.
diff --git a/rc.h b/rc.h
index 68d338d..fb54834 100644
--- a/rc.h
+++ b/rc.h
@@ -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);
diff --git a/tree.c b/tree.c
index 60b42e3..6a79bbc 100644
--- a/tree.c
+++ b/tree.c
@@ -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;
}
diff --git a/trip.rc b/trip.rc
index 5924993..666418b 100644
--- a/trip.rc
+++ b/trip.rc
@@ -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'
diff --git a/walk.c b/walk.c
index 67fbb8e..b9218ca 100644
--- a/walk.c
+++ b/walk.c
@@ -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);
+ }
+}