Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1cfcf0a
Make $&exitonfalse work via an exception, rather than exit(2).
jpco Dec 1, 2023
893d63b
Two things: Don't exit the interactive loop on false results (instead…
jpco Dec 28, 2023
1e79edc
Do not %exit-on-false for assignments.
jpco Dec 28, 2023
bf64d20
Update man page to account for excluding assignment.
jpco Dec 29, 2023
a4f3b6d
Exit from %interactive-loop when catching a `false` exception.
jpco Dec 29, 2023
d98ce9e
Revert "Exit from %interactive-loop when catching a `false` exception."
jpco Feb 27, 2024
f63b060
Don't exit on an error exception with es -e.
jpco Sep 14, 2024
7f958e5
Merge remote-tracking branch 'upstream/master' into throwonfalse
jpco Dec 28, 2024
e56baa3
Update tests to reflect changed behavior
jpco Dec 28, 2024
4f333db
Fix "else-clause" bug for es -e
jpco Apr 5, 2025
2e5d900
"Fix" how if handles false exceptions in its conditions
jpco Apr 5, 2025
171201e
Change "<=" and "if" to catch "false" exceptions instead of turning t…
jpco Apr 5, 2025
f4ed473
Man page updates
jpco Apr 5, 2025
84b5c42
Fix control-flow constructs in initial.es with es -e
jpco Apr 6, 2025
da5f0a5
Merge remote-tracking branch 'upstream/master' into throwonfalse
jpco Jun 14, 2025
de14db0
Merge remote-tracking branch 'upstream/master' into throwonfalse
jpco Sep 7, 2025
478bd02
Fix while loops that end with assignment
jpco Sep 7, 2025
affcec2
Fix memory bug
jpco Sep 8, 2025
973d319
Fix share/status.es to work with the false exception
jpco Sep 8, 2025
ed88f7c
Fix 'while' again for `es -e`.
jpco Sep 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 49 additions & 4 deletions doc/es.1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions es.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
49 changes: 27 additions & 22 deletions eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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");
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -235,29 +237,29 @@ 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, &quote);
pattern = glom2(patternform, bp, &quote, evalflags);
result = listmatch(subject, pattern, quote);
RefEnd4(quote, subject, patternform, bp);
return result ? ltrue : lfalse;
}

/* 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, &quote);
pattern = glom2(patternform, bp, &quote, evalflags);
result = (List *) extractmatches(subject, pattern, quote);
RefEnd4(quote, subject, patternform, bp);
RefReturn(result);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
39 changes: 23 additions & 16 deletions glom.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -270,22 +277,22 @@ 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:
Ref(List *, l, NULL);
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);
Expand Down Expand Up @@ -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, &quote);
list = glom2(tree, binding, &quote, evalflags);
list = glob(list, quote);
RefEnd(quote);
RefReturn(list);
} else
return glom1(tree, binding);
return glom1(tree, binding, evalflags);
}
Loading