diff --git a/doc/es.1 b/doc/es.1 index 3fb8e790..b2695a92 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -988,6 +988,24 @@ If any element of a list is not equal to (or the empty string), that list is considered false. .PP The return value of an assignment operation is the assigned value. +.PP +When +.I es +is invoked with the +.Cr \-e +option, false return values generate the +.Cr false +exception. +(See the section entitled +.B Exceptions +for more information about the +.Cr false +exception.) +The +.Cr false +exception is caught and treated as a return value by the +.Cr "<=" +construct. .SS "Logical Operators" There are a number of operators in .I es @@ -1543,6 +1561,20 @@ and the signal is listed in the variable .Cr signals . .I Signame is the name of the signal that was raised. +.TP +.Cr "exit \fIcode\fP" +Raised by the +.Cr exit +command. +Causes the shell to exit with the given +.Cr code . +.TP +.Cr "false \fIvalue\fP" +Raised by the +.Cr $&exitonfalse +primitive when a command exits with a falsey status. +Causes the shell to exit if not caught. +By default, this exception is caught in an interactive shell. .PP See the builtin commands .Cr catch @@ -2071,6 +2103,13 @@ If none of the are true, the .I else command is run. +The +.Cr if +function catches any +.Cr false +exceptions generated from +.IR test s +and treats them as a false return value. .TP .Cr "limit \fR[\fP-h\fR]\fP \fI\fR[\fPresource \fR[\fPvalue\fR]\fP\fR]\fP\fP" Similar to the @@ -2367,14 +2406,18 @@ may provide a builtin version of this function to handle shell scripts if the kernel does not. .TP .Cr "%exit-on-false \fIcmd\fP" -Runs the command, and exits if any command -(except those executing as the tests of conditional statements) +Runs the command, and throws the +.Cr false +exception if any command (except assignments) returns a non-zero status. (This function is used in the definition of .Cr %dispatch when the shell is invoked with the .Cr \-e option.) +The uncaught +.Cr false +exception causes the shell to exit. .TP .Cr "%flatten \fIseparator list\fP" Concatenate the elements of @@ -2871,8 +2914,10 @@ starts with a dash .Rc ( - ). .TP .Cr \-e -Exit if any command (except those executing as the tests of -conditional statements) returns a non-zero status. +Throw a +.Cr false +exception if any command (other than assignments) returns a non-zero +status. .TP .Cr \-v Echo all input to standard error. diff --git a/es.h b/es.h index cf298c09..592788b4 100644 --- a/es.h +++ b/es.h @@ -167,8 +167,8 @@ extern unsigned long evaldepth, maxevaldepth; /* glom.c */ -extern List *glom(Tree *tree, Binding *binding, Boolean globit); -extern List *glom2(Tree *tree, Binding *binding, StrList **quotep); +extern List *glom(Tree *tree, Binding *binding, Boolean globit, int evalflags); +extern List *glom2(Tree *tree, Binding *binding, StrList **quotep, int evalflags); /* glob.c */ diff --git a/eval.c b/eval.c index 4c1977a1..fb2a3b10 100644 --- a/eval.c +++ b/eval.c @@ -3,6 +3,7 @@ #include "es.h" unsigned long evaldepth = 0, maxevaldepth = MAXmaxevaldepth; +static Boolean did_assign; static Noreturn failexec(char *file, List *args) { List *fn; @@ -46,17 +47,17 @@ extern List *forkexec(char *file, List *list, Boolean inchild) { } /* assign -- bind a list of values to a list of variables */ -static List *assign(Tree *varform, Tree *valueform0, Binding *binding0) { +static List *assign(Tree *varform, Tree *valueform0, Binding *binding0, int evalflags) { Ref(List *, result, NULL); Ref(Tree *, valueform, valueform0); Ref(Binding *, binding, binding0); - Ref(List *, vars, glom(varform, binding, FALSE)); + Ref(List *, vars, glom(varform, binding, FALSE, evalflags)); if (vars == NULL) fail("es:assign", "null variable name"); - Ref(List *, values, glom(valueform, binding, TRUE)); + Ref(List *, values, glom(valueform, binding, TRUE, evalflags)); result = values; for (; vars != NULL; vars = vars->next) { @@ -76,12 +77,13 @@ static List *assign(Tree *varform, Tree *valueform0, Binding *binding0) { } RefEnd4(values, vars, binding, valueform); + did_assign = TRUE; RefReturn(result); } /* letbindings -- create a new Binding containing let-bound variables */ static Binding *letbindings(Tree *defn0, Binding *outer0, - Binding *context0, int UNUSED evalflags) { + Binding *context0, int evalflags) { Ref(Binding *, binding, outer0); Ref(Binding *, context, context0); Ref(Tree *, defn, defn0); @@ -93,8 +95,8 @@ static Binding *letbindings(Tree *defn0, Binding *outer0, Ref(Tree *, assign, defn->u[0].p); assert(assign->kind == nAssign); - Ref(List *, vars, glom(assign->u[0].p, context, FALSE)); - Ref(List *, values, glom(assign->u[1].p, context, TRUE)); + Ref(List *, vars, glom(assign->u[0].p, context, FALSE, evalflags)); + Ref(List *, values, glom(assign->u[1].p, context, TRUE, evalflags)); if (vars == NULL) fail("es:let", "null variable name"); @@ -175,8 +177,8 @@ static List *forloop(Tree *defn0, Tree *body0, continue; Ref(Tree *, assign, defn->u[0].p); assert(assign->kind == nAssign); - Ref(List *, vars, glom(assign->u[0].p, outer, FALSE)); - Ref(List *, list, glom(assign->u[1].p, outer, TRUE)); + Ref(List *, vars, glom(assign->u[0].p, outer, FALSE, evalflags)); + Ref(List *, list, glom(assign->u[1].p, outer, TRUE, evalflags)); if (vars == NULL) fail("es:for", "null variable name"); for (; vars != NULL; vars = vars->next) { @@ -235,14 +237,14 @@ static List *forloop(Tree *defn0, Tree *body0, /* matchpattern -- does the text match a pattern? */ static List *matchpattern(Tree *subjectform0, Tree *patternform0, - Binding *binding) { + Binding *binding, int evalflags) { Boolean result; List *pattern; Ref(Binding *, bp, binding); Ref(Tree *, patternform, patternform0); - Ref(List *, subject, glom(subjectform0, bp, TRUE)); + Ref(List *, subject, glom(subjectform0, bp, TRUE, evalflags)); Ref(StrList *, quote, NULL); - pattern = glom2(patternform, bp, "e); + pattern = glom2(patternform, bp, "e, evalflags); result = listmatch(subject, pattern, quote); RefEnd4(quote, subject, patternform, bp); return result ? ltrue : lfalse; @@ -250,14 +252,14 @@ static List *matchpattern(Tree *subjectform0, Tree *patternform0, /* extractpattern -- Like matchpattern, but returns matches */ static List *extractpattern(Tree *subjectform0, Tree *patternform0, - Binding *binding) { + Binding *binding, int evalflags) { List *pattern; Ref(List *, result, NULL); Ref(Binding *, bp, binding); Ref(Tree *, patternform, patternform0); - Ref(List *, subject, glom(subjectform0, bp, TRUE)); + Ref(List *, subject, glom(subjectform0, bp, TRUE, evalflags)); Ref(StrList *, quote, NULL); - pattern = glom2(patternform, bp, "e); + pattern = glom2(patternform, bp, "e, evalflags); result = (List *) extractmatches(subject, pattern, quote); RefEnd4(quote, subject, patternform, bp); RefReturn(result); @@ -280,14 +282,14 @@ extern List *walk(Tree *tree0, Binding *binding0, int flags) { case nWord: case nThunk: case nLambda: case nCall: case nPrim: { List *list; Ref(Binding *, bp, binding); - list = glom(tree, binding, TRUE); + list = glom(tree, binding, TRUE, flags); binding = bp; RefEnd(bp); return eval(list, binding, flags); } case nAssign: - return assign(tree->u[0].p, tree->u[1].p, binding); + return assign(tree->u[0].p, tree->u[1].p, binding, flags); case nLet: case nClosure: Ref(Tree *, body, tree->u[1].p); @@ -301,12 +303,12 @@ extern List *walk(Tree *tree0, Binding *binding0, int flags) { case nFor: return forloop(tree->u[0].p, tree->u[1].p, binding, flags); - + case nMatch: - return matchpattern(tree->u[0].p, tree->u[1].p, binding); + return matchpattern(tree->u[0].p, tree->u[1].p, binding, flags); case nExtract: - return extractpattern(tree->u[0].p, tree->u[1].p, binding); + return extractpattern(tree->u[0].p, tree->u[1].p, binding, flags); default: panic("walk: bad node kind %d", tree->kind); @@ -378,6 +380,7 @@ extern List *eval(List *list0, Binding *binding0, int flags) { return ltrue; } assert(list->term != NULL); + did_assign = FALSE; if ((cp = getclosure(list->term)) != NULL) { switch (cp->tree->kind) { @@ -418,7 +421,7 @@ extern List *eval(List *list0, Binding *binding0, int flags) { EndExceptionHandler break; case nList: { - Ref(List *, lp, glom(cp->tree, cp->binding, TRUE)); + Ref(List *, lp, glom(cp->tree, cp->binding, TRUE, flags)); list = append(lp, list->next); RefEnd(lp); goto restart; @@ -478,8 +481,10 @@ extern List *eval(List *list0, Binding *binding0, int flags) { done: --evaldepth; - if ((flags & eval_exitonfalse) && !istrue(list)) - esexit(exitstatus(list)); + if ((flags & eval_exitonfalse) && !istrue(list) && !did_assign) { + Term *t = mkstr("false"); + throw(mklist(t, list)); + } RefEnd2(funcname, binding); RefReturn(list); } diff --git a/glom.c b/glom.c index 28bfb48e..f1beb0df 100644 --- a/glom.c +++ b/glom.c @@ -149,7 +149,7 @@ static List *subscript(List *list, List *subs) { } /* glom1 -- glom when we don't need to produce a quote list */ -static List *glom1(Tree *tree, Binding *binding) { +static List *glom1(Tree *tree, Binding *binding, int evalflags) { Ref(List *, result, NULL); Ref(List *, tail, NULL); Ref(Tree *, tp, tree); @@ -179,7 +179,7 @@ static List *glom1(Tree *tree, Binding *binding) { tp = NULL; break; case nVar: - Ref(List *, var, glom1(tp->u[0].p, bp)); + Ref(List *, var, glom1(tp->u[0].p, bp, evalflags)); tp = NULL; for (; var != NULL; var = var->next) { list = listcopy(varlookup(getstr(var->term), bp)); @@ -196,29 +196,36 @@ static List *glom1(Tree *tree, Binding *binding) { RefEnd(var); break; case nVarsub: - list = glom1(tp->u[0].p, bp); + list = glom1(tp->u[0].p, bp, evalflags); if (list == NULL) fail("es:glom", "null variable name in subscript"); if (list->next != NULL) fail("es:glom", "multi-word variable name in subscript"); Ref(char *, name, getstr(list->term)); list = varlookup(name, bp); - Ref(List *, sub, glom1(tp->u[1].p, bp)); + Ref(List *, sub, glom1(tp->u[1].p, bp, evalflags)); tp = NULL; list = subscript(list, sub); RefEnd2(sub, name); break; case nCall: - list = listcopy(walk(tp->u[0].p, bp, 0)); + ExceptionHandler + list = listcopy(walk(tp->u[0].p, bp, evalflags & eval_exitonfalse)); + CatchException (e) + if (!termeq(e->term, "false")) + throw(e); + else + list = e->next; + EndExceptionHandler tp = NULL; break; case nList: - list = glom1(tp->u[0].p, bp); + list = glom1(tp->u[0].p, bp, evalflags); tp = tp->u[1].p; break; case nConcat: - Ref(List *, l, glom1(tp->u[0].p, bp)); - Ref(List *, r, glom1(tp->u[1].p, bp)); + Ref(List *, l, glom1(tp->u[0].p, bp, evalflags)); + Ref(List *, r, glom1(tp->u[1].p, bp, evalflags)); tp = NULL; list = concat(l, r); RefEnd2(r, l); @@ -243,7 +250,7 @@ static List *glom1(Tree *tree, Binding *binding) { } /* glom2 -- glom and produce a quoting list */ -extern List *glom2(Tree *tree, Binding *binding, StrList **quotep) { +extern List *glom2(Tree *tree, Binding *binding, StrList **quotep, int evalflags) { Ref(List *, result, NULL); Ref(List *, tail, NULL); Ref(StrList *, qtail, NULL); @@ -270,7 +277,7 @@ extern List *glom2(Tree *tree, Binding *binding, StrList **quotep) { tp = NULL; break; case nList: - list = glom2(tp->u[0].p, bp, &qlist); + list = glom2(tp->u[0].p, bp, &qlist, evalflags); tp = tp->u[1].p; break; case nConcat: @@ -278,14 +285,14 @@ extern List *glom2(Tree *tree, Binding *binding, StrList **quotep) { Ref(List *, r, NULL); Ref(StrList *, ql, NULL); Ref(StrList *, qr, NULL); - l = glom2(tp->u[0].p, bp, &ql); - r = glom2(tp->u[1].p, bp, &qr); + l = glom2(tp->u[0].p, bp, &ql, evalflags); + r = glom2(tp->u[1].p, bp, &qr, evalflags); list = qconcat(l, r, ql, qr, &qlist); RefEnd4(qr, ql, r, l); tp = NULL; break; default: - list = glom1(tp, bp); + list = glom1(tp, bp, evalflags); Ref(List *, lp, list); for (; lp != NULL; lp = lp->next) qlist = mkstrlist(QUOTED, qlist); @@ -316,14 +323,14 @@ extern List *glom2(Tree *tree, Binding *binding, StrList **quotep) { } /* glom -- top level glom dispatching */ -extern List *glom(Tree *tree, Binding *binding, Boolean globit) { +extern List *glom(Tree *tree, Binding *binding, Boolean globit, int evalflags) { if (globit) { Ref(List *, list, NULL); Ref(StrList *, quote, NULL); - list = glom2(tree, binding, "e); + list = glom2(tree, binding, "e, evalflags); list = glob(list, quote); RefEnd(quote); RefReturn(list); } else - return glom1(tree, binding); + return glom1(tree, binding, evalflags); } diff --git a/initial.es b/initial.es index 987919a0..5ec3470a 100644 --- a/initial.es +++ b/initial.es @@ -178,20 +178,30 @@ fn whatis { # does not catch the return exception. It does, however, catch break. fn-while = $&noreturn @ cond body { + let (result = <=true) catch @ e value { - if {!~ $e break} { + if {~ $e caught-false} { + throw false $value + } {!~ $e break} { throw $e $value } - result $value + result = $value } { - let (result = <=true) - forever { - if {!$cond} { - throw break $result + forever { + if {!$cond} { + throw break $result + } { + result = <={catch @ e rest { + if {~ $e false} { + throw caught-false $rest + } { + throw $e $rest + } } { - result = <=$body - } + $body + }} } + } } } @@ -661,11 +671,13 @@ fn %interactive-loop { throw $e $type $msg } {~ $e error} { echo >[1=2] $msg - $fn-%dispatch false } {~ $e signal} { if {!~ $type sigint sigterm sigquit} { echo >[1=2] caught unexpected signal: $type } + } {~ $e (false caught-false)} { + result = $type $msg + echo >[1=2] result '=' $result } { echo >[1=2] uncaught exception: $e $type $msg } @@ -677,7 +689,15 @@ fn %interactive-loop { } let (code = <={%parse $prompt}) { if {!~ $#code 0} { - result = <={$fn-%dispatch $code} + result = <={catch @ e rest { + if {~ $e false} { + throw caught-false $rest + } { + throw $e $rest + } + } { + $fn-%dispatch $code + }} } } } diff --git a/main.c b/main.c index f8f19ad4..4f92d2df 100644 --- a/main.c +++ b/main.c @@ -54,7 +54,7 @@ static void runesrc(void) { ExceptionHandler runfd(fd, esrc, 0); CatchException (e) - if (termeq(e->term, "exit")) + if (termeq(e->term, "exit") || termeq(e->term, "false")) exit(exitstatus(e->next)); else if (termeq(e->term, "error")) { eprint("%L\n", @@ -221,7 +221,7 @@ int main(int argc, char **argv0) { CatchException (e) - if (termeq(e->term, "exit")) { + if (termeq(e->term, "exit") || termeq(e->term, "false")) { status = exitstatus(e->next); goto return_main; } else if (termeq(e->term, "error")) { diff --git a/prim-ctl.c b/prim-ctl.c index d8d4e31c..5b0a680a 100644 --- a/prim-ctl.c +++ b/prim-ctl.c @@ -17,7 +17,14 @@ PRIM(if) { for (; lp != NULL; lp = lp->next) { List *cond = ltrue; if (lp->next != NULL) { - cond = eval1(lp->term, 0); + ExceptionHandler + cond = eval1(lp->term, evalflags &~ (lp->next == NULL ? 0 : eval_inchild)); + CatchException (e) + if (termeq(e->term, "false")) + cond = e->next; + else + throw(e); + EndExceptionHandler lp = lp->next; } if (istrue(cond)) { diff --git a/share/status.es b/share/status.es index faf5ae3b..50243b6d 100644 --- a/share/status.es +++ b/share/status.es @@ -23,10 +23,21 @@ fn %interactive-loop { catch @ e rest { if {~ $e return} { status = $rest + } {~ $e caught-false} { + status = $rest + e = false } throw $e $rest } { - status = <={$d $*} + status = <={catch @ e rest { + if {~ $e false} { + throw caught-false $rest + } { + throw $e $rest + } + } { + $d $* + }} } } ) $loop $* diff --git a/test/tests/option.es b/test/tests/option.es index 600b172d..29d208ec 100644 --- a/test/tests/option.es +++ b/test/tests/option.es @@ -18,11 +18,15 @@ test 'es -e' { 'if {true} {false; true}' false 'if {false} {true} {false; true}' false '{true; {true; {false; true}}}' false + 'let (fn-x = {false; true}) + if {x} {true} {false}' false + 'let (n=;o=a b) while {!~ $o ()} { + o=$o(2 ...); n=$n $o}' true # assignments - 'x = false' false - 'fn x {false}' false - '{true; {true; {x = false}; true}}' false + 'x = false' true + 'fn x {false}' true + '{true; {true; {x = false}; true}}' true 'let (x = false) true' true 'local (x = false) true' true )) {