From 1cfcf0ae342d262fd5d00154be4425debf8ec4bf Mon Sep 17 00:00:00 2001 From: jpco Date: Thu, 30 Nov 2023 20:20:31 -0800 Subject: [PATCH 01/17] Make $&exitonfalse work via an exception, rather than exit(2). --- doc/es.1 | 20 +++++++++++++++++++- eval.c | 2 +- initial.es | 2 +- main.c | 4 ++-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/doc/es.1 b/doc/es.1 index a62a492e..57e1c383 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -1518,6 +1518,19 @@ 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. .PP See the builtin commands .Cr catch @@ -2325,7 +2338,9 @@ 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 +Runs the command, and throws the +.Cr false +exception if any command (except those executing as the tests of conditional statements) returns a non-zero status. (This function is used as an argument to @@ -2335,6 +2350,9 @@ and 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 diff --git a/eval.c b/eval.c index f3981343..33299355 100644 --- a/eval.c +++ b/eval.c @@ -459,7 +459,7 @@ extern List *eval(List *list0, Binding *binding0, int flags) { done: --evaldepth; if ((flags & eval_exitonfalse) && !istrue(list)) - exit(exitstatus(list)); + throw(mklist(mkterm("false", NULL), list)); RefEnd2(funcname, binding); RefReturn(list); } diff --git a/initial.es b/initial.es index af775b55..07daea85 100644 --- a/initial.es +++ b/initial.es @@ -635,7 +635,7 @@ fn %interactive-loop { catch @ e type msg { if {~ $e eof} { return $result - } {~ $e exit} { + } {~ $e exit || ~ $e false} { throw $e $type $msg } {~ $e error} { echo >[1=2] $msg diff --git a/main.c b/main.c index 3007c1af..4aafa79b 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", @@ -209,7 +209,7 @@ int main(int argc, char **argv) { CatchException (e) - if (termeq(e->term, "exit")) + if (termeq(e->term, "exit") || termeq(e->term, "false")) return exitstatus(e->next); else if (termeq(e->term, "error")) eprint("%L\n", From 893d63b28b7024838b3bc234739dd87b0f66bfae Mon Sep 17 00:00:00 2001 From: jpco Date: Thu, 28 Dec 2023 11:21:00 -0800 Subject: [PATCH 02/17] Two things: Don't exit the interactive loop on false results (instead catch, complain, and retry); and limit the exit-on-false behavior to only be triggered in the context of %exit-on-false. --- initial.es | 4 +++- input.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/initial.es b/initial.es index 07daea85..859ea707 100644 --- a/initial.es +++ b/initial.es @@ -635,7 +635,7 @@ fn %interactive-loop { catch @ e type msg { if {~ $e eof} { return $result - } {~ $e exit || ~ $e false} { + } {~ $e exit} { throw $e $type $msg } {~ $e error} { echo >[1=2] $msg @@ -644,6 +644,8 @@ fn %interactive-loop { if {!~ $type sigint sigterm sigquit} { echo >[1=2] caught unexpected signal: $type } + } {~ $e false} { + echo >[1=2] caught false result: $type $msg } { echo >[1=2] uncaught exception: $e $type $msg } diff --git a/input.c b/input.c index 31d525ae..520e1022 100644 --- a/input.c +++ b/input.c @@ -414,8 +414,10 @@ extern List *runinput(Input *in, int runflags) { = varlookup(dispatcher[((flags & run_printcmds) ? 1 : 0) + ((flags & run_noexec) ? 2 : 0)], NULL); - if (flags & eval_exitonfalse) + if (flags & eval_exitonfalse) { dispatch = mklist(mkstr("%exit-on-false"), dispatch); + flags &= ~eval_exitonfalse; + } varpush(&push, "fn-%dispatch", dispatch); repl = varlookup((flags & run_interactive) From 1e79edce27e282e9f0c4bc09c4c6515f76401edf Mon Sep 17 00:00:00 2001 From: jpco Date: Thu, 28 Dec 2023 12:49:52 -0800 Subject: [PATCH 03/17] Do not %exit-on-false for assignments. This feels like a hack, but it seems to work, and I can't come up with a way to break it right now. --- eval.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eval.c b/eval.c index 33299355..1582c426 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; @@ -76,6 +77,7 @@ static List *assign(Tree *varform, Tree *valueform0, Binding *binding0) { } RefEnd4(values, vars, binding, valueform); + did_assign = TRUE; RefReturn(result); } @@ -374,6 +376,7 @@ extern List *eval(List *list0, Binding *binding0, int flags) { return true; } assert(list->term != NULL); + did_assign = FALSE; if ((cp = getclosure(list->term)) != NULL) { switch (cp->tree->kind) { @@ -458,7 +461,7 @@ extern List *eval(List *list0, Binding *binding0, int flags) { done: --evaldepth; - if ((flags & eval_exitonfalse) && !istrue(list)) + if ((flags & eval_exitonfalse) && !istrue(list) && !did_assign) throw(mklist(mkterm("false", NULL), list)); RefEnd2(funcname, binding); RefReturn(list); From bf64d20c8672e5b901af077d1f38e8fb2b4282e8 Mon Sep 17 00:00:00 2001 From: jpco Date: Thu, 28 Dec 2023 18:09:35 -0800 Subject: [PATCH 04/17] Update man page to account for excluding assignment. --- doc/es.1 | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/doc/es.1 b/doc/es.1 index 57e1c383..0ebdca82 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -2341,12 +2341,11 @@ shell scripts if the kernel does not. Runs the command, and throws the .Cr false exception if any command -(except those executing as the tests of conditional statements) +(except assignments, or those executing as the tests of +conditional statements) returns a non-zero status. -(This function is used as an argument to -.Cr %batch-loop -and -.Cr %interactive-loop +(This function is used as part of the definition of +.Cr %dispatch when the shell is invoked with the .Cr \-e option.) @@ -2814,8 +2813,8 @@ 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. +Exit if any command (except those executing as assignments or +the tests of conditional statements) returns a non-zero status. .TP .Cr \-v Echo all input to standard error. From a4f3b6d93afee2e47faca2f5df995c6f39ec10db Mon Sep 17 00:00:00 2001 From: jpco Date: Thu, 28 Dec 2023 19:25:49 -0800 Subject: [PATCH 05/17] Exit from %interactive-loop when catching a `false` exception. --- initial.es | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/initial.es b/initial.es index 859ea707..0bb34d23 100644 --- a/initial.es +++ b/initial.es @@ -640,12 +640,13 @@ fn %interactive-loop { } {~ $e error} { echo >[1=2] $msg $fn-%dispatch false + } {~ $e false} { + echo >[1=2] caught false result: $type $msg + throw $e $type $msg } {~ $e signal} { if {!~ $type sigint sigterm sigquit} { echo >[1=2] caught unexpected signal: $type } - } {~ $e false} { - echo >[1=2] caught false result: $type $msg } { echo >[1=2] uncaught exception: $e $type $msg } From d98ce9e306b6ee7e1130798a6be77aeaacdf0540 Mon Sep 17 00:00:00 2001 From: jpco Date: Tue, 27 Feb 2024 09:53:15 -0800 Subject: [PATCH 06/17] Revert "Exit from %interactive-loop when catching a `false` exception." This reverts commit a4f3b6d93afee2e47faca2f5df995c6f39ec10db. --- initial.es | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/initial.es b/initial.es index 0bb34d23..859ea707 100644 --- a/initial.es +++ b/initial.es @@ -640,13 +640,12 @@ fn %interactive-loop { } {~ $e error} { echo >[1=2] $msg $fn-%dispatch false - } {~ $e false} { - echo >[1=2] caught false result: $type $msg - throw $e $type $msg } {~ $e signal} { if {!~ $type sigint sigterm sigquit} { echo >[1=2] caught unexpected signal: $type } + } {~ $e false} { + echo >[1=2] caught false result: $type $msg } { echo >[1=2] uncaught exception: $e $type $msg } From f63b060c0c73b7ec6de89a66552c2d92a485122a Mon Sep 17 00:00:00 2001 From: jpco Date: Sat, 14 Sep 2024 07:37:14 -0700 Subject: [PATCH 07/17] Don't exit on an error exception with es -e. --- initial.es | 1 - 1 file changed, 1 deletion(-) diff --git a/initial.es b/initial.es index 859ea707..b998a58d 100644 --- a/initial.es +++ b/initial.es @@ -639,7 +639,6 @@ 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 From e56baa38d92e120c22d4d392c605aceb7ace5cdd Mon Sep 17 00:00:00 2001 From: jpco Date: Sat, 28 Dec 2024 09:04:21 -0800 Subject: [PATCH 08/17] Update tests to reflect changed behavior --- test/tests/option.es | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/tests/option.es b/test/tests/option.es index 26fbe1ca..3f23606f 100644 --- a/test/tests/option.es +++ b/test/tests/option.es @@ -17,9 +17,9 @@ test 'es -e' { '{true; {true; {false; true}}}' false # 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 )) { From 4f333db85b6ae4dfb5f240e18c8a68bbc08bd593 Mon Sep 17 00:00:00 2001 From: jpco Date: Sat, 5 Apr 2025 10:23:49 -0700 Subject: [PATCH 09/17] Fix "else-clause" bug for es -e --- prim-ctl.c | 9 ++++----- test/tests/option.es | 3 +++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/prim-ctl.c b/prim-ctl.c index 8ebae1fd..19cb1dd6 100644 --- a/prim-ctl.c +++ b/prim-ctl.c @@ -15,11 +15,10 @@ PRIM(seq) { PRIM(if) { Ref(List *, lp, list); for (; lp != NULL; lp = lp->next) { - List *cond = eval1(lp->term, evalflags & (lp->next == NULL ? eval_inchild : 0)); - lp = lp->next; - if (lp == NULL) { - RefPop(lp); - return cond; + List *cond = ltrue; + if (lp->next != NULL) { + cond = eval1(lp->term, 0); + lp = lp->next; } if (istrue(cond)) { List *result = eval1(lp->term, evalflags); diff --git a/test/tests/option.es b/test/tests/option.es index 3f23606f..20ca88db 100644 --- a/test/tests/option.es +++ b/test/tests/option.es @@ -14,6 +14,9 @@ test 'es -e' { 'false' false 'if {false} {true}' true 'if {true} {false}' false + 'if {false; true} {true}' true + 'if {true} {false; true}' false + 'if {false} {true} {false; true}' false '{true; {true; {false; true}}}' false # assignments From 2e5d9007b2a47126f5a14d2548faacbe6994c851 Mon Sep 17 00:00:00 2001 From: jpco Date: Sat, 5 Apr 2025 10:27:54 -0700 Subject: [PATCH 10/17] "Fix" how if handles false exceptions in its conditions --- prim-ctl.c | 10 +++++++++- test/tests/option.es | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/prim-ctl.c b/prim-ctl.c index 19cb1dd6..c672054f 100644 --- a/prim-ctl.c +++ b/prim-ctl.c @@ -17,7 +17,15 @@ 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/test/tests/option.es b/test/tests/option.es index 20ca88db..e06f04e8 100644 --- a/test/tests/option.es +++ b/test/tests/option.es @@ -18,6 +18,8 @@ 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 # assignments 'x = false' true From 171201eb3696eb1c79c110edf4af511d2a3fc606 Mon Sep 17 00:00:00 2001 From: jpco Date: Sat, 5 Apr 2025 11:27:38 -0700 Subject: [PATCH 11/17] Change "<=" and "if" to catch "false" exceptions instead of turning them off --- es.h | 4 ++-- eval.c | 40 ++++++++++++++++++++-------------------- glom.c | 39 +++++++++++++++++++++++---------------- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/es.h b/es.h index 472e88ee..0faee791 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 b4669b48..da3d904d 100644 --- a/eval.c +++ b/eval.c @@ -47,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) { @@ -83,7 +83,7 @@ static List *assign(Tree *varform, Tree *valueform0, Binding *binding0) { /* 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); @@ -95,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"); @@ -177,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) { @@ -237,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; @@ -252,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); @@ -282,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); @@ -303,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); @@ -417,7 +417,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; 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); } From f4ed473aab9be6aeeb8a8270d8b35d4bdf184be1 Mon Sep 17 00:00:00 2001 From: jpco Date: Sat, 5 Apr 2025 11:49:23 -0700 Subject: [PATCH 12/17] Man page updates --- doc/es.1 | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/doc/es.1 b/doc/es.1 index bf5ca3a0..3ea67646 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 @@ -1556,6 +1574,7 @@ 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 @@ -2084,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 @@ -2382,9 +2408,7 @@ shell scripts if the kernel does not. .Cr "%exit-on-false \fIcmd\fP" Runs the command, and throws the .Cr false -exception if any command -(except assignments, or those executing as the tests of -conditional statements) +exception if any command (except assignments) returns a non-zero status. (This function is used in the definition of .Cr %dispatch @@ -2857,8 +2881,10 @@ starts with a dash .Rc ( - ). .TP .Cr \-e -Exit if any command (except those executing as assignments or -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. From 84b5c42f44aad85b49836298ee2656ef6d9c984d Mon Sep 17 00:00:00 2001 From: jpco Date: Sun, 6 Apr 2025 11:08:09 -0700 Subject: [PATCH 13/17] Fix control-flow constructs in initial.es with es -e --- initial.es | 17 +++++++++++++---- prim-ctl.c | 3 +-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/initial.es b/initial.es index b7bac6d1..bdf34f65 100644 --- a/initial.es +++ b/initial.es @@ -189,7 +189,7 @@ fn-while = $&noreturn @ cond body { if {!$cond} { throw break $result } { - result = <=$body + result <={result = <=$body} } } } @@ -643,8 +643,9 @@ fn %interactive-loop { if {!~ $type sigint sigterm sigquit} { echo >[1=2] caught unexpected signal: $type } - } {~ $e false} { - echo >[1=2] caught false result: $type $msg + } {~ $e (false caught-false)} { + result = $type $msg + echo >[1=2] result '=' $result } { echo >[1=2] uncaught exception: $e $type $msg } @@ -656,7 +657,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/prim-ctl.c b/prim-ctl.c index c672054f..57ff65a0 100644 --- a/prim-ctl.c +++ b/prim-ctl.c @@ -18,8 +18,7 @@ PRIM(if) { List *cond = ltrue; if (lp->next != NULL) { ExceptionHandler - cond = eval1(lp->term, - evalflags &~ (lp->next == NULL ? 0 : eval_inchild)); + cond = eval1(lp->term, evalflags &~ eval_inchild); CatchException (e) if (termeq(e->term, "false")) cond = e->next; From 478bd02d473f213c2178d5d0a53e0965a2584c5e Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Sun, 7 Sep 2025 16:21:19 -0700 Subject: [PATCH 14/17] Fix while loops that end with assignment --- initial.es | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/initial.es b/initial.es index 1085df83..889b55aa 100644 --- a/initial.es +++ b/initial.es @@ -189,7 +189,15 @@ fn-while = $&noreturn @ cond body { if {!$cond} { throw break $result } { - result <={result = <=$body} + result = <={catch @ e rest { + if {~ $e false} { + throw break $rest + } { + throw $e $rest + } + } { + $body + }} } } } From affcec225fa265c7c1557972027efbb7295eb2a3 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Sun, 7 Sep 2025 17:13:09 -0700 Subject: [PATCH 15/17] Fix memory bug --- eval.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eval.c b/eval.c index f6667418..fb2a3b10 100644 --- a/eval.c +++ b/eval.c @@ -481,8 +481,10 @@ extern List *eval(List *list0, Binding *binding0, int flags) { done: --evaldepth; - if ((flags & eval_exitonfalse) && !istrue(list) && !did_assign) - throw(mklist(mkterm("false", NULL), list)); + if ((flags & eval_exitonfalse) && !istrue(list) && !did_assign) { + Term *t = mkstr("false"); + throw(mklist(t, list)); + } RefEnd2(funcname, binding); RefReturn(list); } From 973d3197694a29877fd5c2e3584847050c88d4a0 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Sun, 7 Sep 2025 17:13:46 -0700 Subject: [PATCH 16/17] Fix share/status.es to work with the false exception --- share/status.es | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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 $* From ed88f7c4ba8840328060426e7a6cb380260ecdcc Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Fri, 12 Sep 2025 08:31:21 -0700 Subject: [PATCH 17/17] Fix 'while' again for `es -e`. Assignments in control flow bodies are tricky to manage. --- initial.es | 34 ++++++++++++++++++---------------- test/tests/option.es | 2 ++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/initial.es b/initial.es index 889b55aa..5ec3470a 100644 --- a/initial.es +++ b/initial.es @@ -178,28 +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 - } { - result = <={catch @ e rest { - if {~ $e false} { - throw break $rest - } { - throw $e $rest - } + forever { + if {!$cond} { + throw break $result + } { + result = <={catch @ e rest { + if {~ $e false} { + throw caught-false $rest } { - $body - }} - } + throw $e $rest + } + } { + $body + }} } + } } } diff --git a/test/tests/option.es b/test/tests/option.es index dd14d266..29d208ec 100644 --- a/test/tests/option.es +++ b/test/tests/option.es @@ -20,6 +20,8 @@ test 'es -e' { '{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' true