summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog7
-rw-r--r--Doc/Zsh/grammar.yo56
-rw-r--r--Doc/Zsh/params.yo8
-rw-r--r--Src/exec.c10
-rw-r--r--Src/loop.c65
-rw-r--r--Src/params.c2
-rw-r--r--Src/parse.c38
-rw-r--r--Src/prompt.c4
-rw-r--r--Src/text.c28
-rw-r--r--Src/zsh.h11
-rw-r--r--Test/A01grammar.ztst79
11 files changed, 300 insertions, 8 deletions
diff --git a/ChangeLog b/ChangeLog
index 2709ab947..4eac297ff 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2004-06-22 Peter Stephenson <pws@csr.com>
+
+ * 20076, 20084: Doc/Zsh/grammar.yo, Doc/Zsh/params.yo, Src/exec.c,
+ Src/loop.c, Src/params.c, Src/parse.c, Src/prompt.c, Src/text.c,
+ Src/zsh.h, Test/A01grammar.ztst: { ... } always { ... } syntax
+ for making sure tidy-up code is run.
+
2004-06-21 Bart Schaefer <schaefer@zsh.org>
* unposted: Doc/Zsh/zle.yo: copy-prev-shell-word has no default
diff --git a/Doc/Zsh/grammar.yo b/Doc/Zsh/grammar.yo
index 0f717c8c2..49126b9ee 100644
--- a/Doc/Zsh/grammar.yo
+++ b/Doc/Zsh/grammar.yo
@@ -234,6 +234,62 @@ are reset to their default values while executing var(list).
item(tt({) var(list) tt(}))(
Execute var(list).
)
+findex(always)
+cindex(always blocks)
+cindex(try blocks)
+item(tt({) var(try-list) tt(} always {) var(always-list) tt(}))(
+First execute var(try-list). Regardless of errors, or tt(break),
+tt(continue), or tt(return) commands encountered within var(try-list),
+execute var(always-list). Execution then continues from the
+result of the execution of var(try-list); in other words, any error,
+or tt(break), tt(continue), or tt(return) command is treated in the
+normal way, as if var(always-list) were not present. The two
+chunks of code are referred to as the `try block' and the `always block'.
+
+Optional newlines or semicolons may appear after the tt(always);
+note, however, that they may em(not) appear between the preceeding
+closing brace and the tt(always).
+
+An `error' in this context is a condition such as a syntax error which
+causes the shell to abort execution of the current function, script, or
+list. Syntax errors encountered while the shell is parsing the
+code do not cause the var(always-list) to be executed. For example,
+an erroneously constructed tt(if) block in tt(try-list) would cause the
+shell to abort during parsing, so that tt(always-list) would not be
+executed, while an erroneous substitution such as tt(${*foo*}) would
+cause a run-time error, after which tt(always-list) would be executed.
+
+An error condition can be tested and reset with the special integer
+variable tt(TRY_BLOCK_ERROR). Outside an tt(always-list) the value is
+irrelevant, but it is initialised to tt(-1). Inside tt(always-list), the
+value is 1 if an error occurred in the tt(try-list), else 0. If
+tt(TRY_BLOCK_ERROR) is set to 0 during the tt(always-list), the error
+condition caused by the tt(try-list) is reset, and shell execution
+continues normally after the end of tt(always-list). Altering the value
+during the tt(try-list) is not useful (unless this forms part of an
+enclosing tt(always) block).
+
+Regardless of tt(TRY_BLOCK_ERROR), after the end of tt(always-list) the
+normal shell status tt($?) is the value returned from tt(always-list).
+This will be non-zero if there was an error, even if tt(TRY_BLOCK_ERROR)
+was set to zero.
+
+The following executes the given code, ignoring any errors it causes.
+This is an alternative to the usual convention of protecting code by
+executing it in a subshell.
+
+example({
+ # code which may cause an error
+ } always {
+ # This code is executed regardless of the error.
+ (( TRY_BLOCK_ERROR = 0 ))
+}
+# The error condition has been reset.)
+
+An tt(exit) command encountered in tt(try-list) does em(not) cause the
+execution of var(always-list). Instead, the shell exits immediately
+after any tt(EXIT) trap has been executed.
+)
findex(function)
xitem(tt(function) var(word) ... [ tt(()) ] [ var(term) ] tt({) var(list) tt(}))
xitem(var(word) ... tt(()) [ var(term) ] tt({) var(list) tt(}))
diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 650a6e8d8..fd12b4f20 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -619,6 +619,14 @@ vindex(signals)
item(tt(signals))(
An array containing the names of the signals.
)
+vindex(TRY_BLOCK_ERROR)
+item(tt(TRY_BLOCK_ERROR) <S>)(
+In an tt(always) block, indicates whether the preceding list of code
+caused an error. The value is 1 to indicate an error, 0 otherwise.
+It may be reset, clearing the error condition. See
+ifzman(em(Complex Commands) in zmanref(zshmisc))\
+ifnzman(noderef(Complex Commands))
+)
vindex(TTY)
item(tt(TTY))(
The name of the tty associated with the shell, if any.
diff --git a/Src/exec.c b/Src/exec.c
index 418e8c67f..04e0e19ad 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -137,10 +137,10 @@ static char *STTYval;
/* Execution functions. */
-static int (*execfuncs[]) _((Estate, int)) = {
+static int (*execfuncs[WC_COUNT-WC_CURSH]) _((Estate, int)) = {
execcursh, exectime, execfuncdef, execfor, execselect,
execwhile, execrepeat, execcase, execif, execcond,
- execarith, execautofn
+ execarith, execautofn, exectry
};
/* structure for command builtin for when it is used with -v or -V */
@@ -325,6 +325,9 @@ execcursh(Estate state, int do_exec)
{
Wordcode end = state->pc + WC_CURSH_SKIP(state->pc[-1]);
+ /* Skip word only used for try/always */
+ state->pc++;
+
if (!list_pipe && thisjob != list_pipe_job && !hasprocs(thisjob))
deletejob(jobtab + thisjob);
cmdpush(CS_CURSH);
@@ -2475,6 +2478,9 @@ execcmd(Estate state, int input, int output, int how, int last1)
subsh_close = -1;
/* If we're forked (and we should be), no need to return */
DPUTS(last1 != 1 && !forked, "BUG: not exiting?");
+ DPUTS(type != WC_SUBSH, "Not sure what we're doing.");
+ /* Skip word only used for try/always blocks */
+ state->pc++;
execlist(state, 0, 1);
}
}
diff --git a/Src/loop.c b/Src/loop.c
index f52f5e74e..4c45c1f78 100644
--- a/Src/loop.c
+++ b/Src/loop.c
@@ -616,3 +616,68 @@ execcase(Estate state, int do_exec)
return lastval;
}
+
+/*
+ * Errflag from `try' block, may be reset in `always' block.
+ * Accessible from an integer parameter, so needs to be a zlong.
+ */
+
+/**/
+zlong
+try_errflag = -1;
+
+/**/
+int
+exectry(Estate state, int do_exec)
+{
+ Wordcode end, always;
+ int endval;
+ int save_retflag, save_breaks, save_loops, save_contflag;
+ zlong save_try_errflag;
+
+ end = state->pc + WC_TRY_SKIP(state->pc[-1]);
+ always = state->pc + 1 + WC_TRY_SKIP(*state->pc);
+ state->pc++;
+ pushheap();
+ cmdpush(CS_CURSH);
+
+ /* The :try clause */
+ execlist(state, 1, do_exec);
+
+ /* Don't record errflag here, may be reset. */
+ endval = lastval;
+
+ freeheap();
+
+ cmdpop();
+ cmdpush(CS_ALWAYS);
+
+ /* The always clause. */
+ save_try_errflag = try_errflag;
+ try_errflag = (zlong)errflag;
+ errflag = 0;
+ save_retflag = retflag;
+ retflag = 0;
+ save_breaks = breaks;
+ breaks = 0;
+ save_loops = loops;
+ loops = 0;
+ save_contflag = contflag;
+ contflag = 0;
+
+ state->pc = always;
+ execlist(state, 1, do_exec);
+
+ errflag = try_errflag ? 1 : 0;
+ try_errflag = save_try_errflag;
+ retflag = save_retflag;
+ breaks = save_breaks;
+ loops = save_loops;
+ contflag = save_contflag;
+
+ cmdpop();
+ popheap();
+ state->pc = end;
+
+ return endval;
+}
diff --git a/Src/params.c b/Src/params.c
index 8b0c87dac..9d9d39778 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -106,6 +106,7 @@ struct timeval shtimer;
/* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */
+
/**/
mod_export int termflags;
@@ -191,6 +192,7 @@ IPDEF5("COLUMNS", &columns, zlevarsetfn),
IPDEF5("LINES", &lines, zlevarsetfn),
IPDEF5("OPTIND", &zoptind, intvarsetfn),
IPDEF5("SHLVL", &shlvl, intvarsetfn),
+IPDEF5("TRY_BLOCK_ERROR", &try_errflag, intvarsetfn),
#define IPDEF7(A,B) {NULL,A,PM_SCALAR|PM_SPECIAL,BR((void *)B),SFN(strvarsetfn),GFN(strvargetfn),stdunsetfn,0,NULL,NULL,NULL,0}
IPDEF7("OPTARG", &zoptarg),
diff --git a/Src/parse.c b/Src/parse.c
index 2f9319977..6bef195f8 100644
--- a/Src/parse.c
+++ b/Src/parse.c
@@ -1330,25 +1330,55 @@ par_repeat(int *complex)
}
/*
- * subsh : ( INPAR | INBRACE ) list ( OUTPAR | OUTBRACE )
+ * subsh : INPAR list OUTPAR |
+ * INBRACE list OUTBRACE [ "always" INBRACE list OUTBRACE ]
*/
/**/
static void
par_subsh(int *complex)
{
- int oecused = ecused, otok = tok, p;
+ int oecused = ecused, otok = tok, p, pp;
p = ecadd(0);
+ /* Extra word only needed for always block */
+ pp = ecadd(0);
yylex();
par_list(complex);
ecadd(WCB_END());
if (tok != ((otok == INPAR) ? OUTPAR : OUTBRACE))
YYERRORV(oecused);
- ecbuf[p] = (otok == INPAR ? WCB_SUBSH(ecused - 1 - p) :
- WCB_CURSH(ecused - 1 - p));
incmdpos = 1;
yylex();
+
+ /* Optional always block. No intervening SEPERs allowed. */
+ if (otok == INBRACE && tok == STRING && !strcmp(tokstr, "always")) {
+ ecbuf[pp] = WCB_TRY(ecused - 1 - pp);
+ incmdpos = 1;
+ do {
+ yylex();
+ } while (tok == SEPER);
+
+ if (tok != INBRACE)
+ YYERRORV(oecused);
+ cmdpop();
+ cmdpush(CS_ALWAYS);
+
+ yylex();
+ par_save_list(complex);
+ while (tok == SEPER)
+ yylex();
+
+ incmdpos = 1;
+
+ if (tok != OUTBRACE)
+ YYERRORV(oecused);
+ yylex();
+ ecbuf[p] = WCB_TRY(ecused - 1 - p);
+ } else {
+ ecbuf[p] = (otok == INPAR ? WCB_SUBSH(ecused - 1 - p) :
+ WCB_CURSH(ecused - 1 - p));
+ }
}
/*
diff --git a/Src/prompt.c b/Src/prompt.c
index a889f7cd3..56d6a76fd 100644
--- a/Src/prompt.c
+++ b/Src/prompt.c
@@ -49,7 +49,7 @@ int cmdsp;
/* parser states, for %_ */
-static char *cmdnames[] = {
+static char *cmdnames[CS_COUNT] = {
"for", "while", "repeat", "select",
"until", "if", "then", "else",
"elif", "math", "cond", "cmdor",
@@ -57,7 +57,7 @@ static char *cmdnames[] = {
"case", "function", "subsh", "cursh",
"array", "quote", "dquote", "bquote",
"cmdsubst", "mathsubst", "elif-then", "heredoc",
- "heredocd", "brace", "braceparam",
+ "heredocd", "brace", "braceparam", "always",
};
/* The buffer into which an expanded and metafied prompt is being written, *
diff --git a/Src/text.c b/Src/text.c
index 794a04df9..f7d80ae73 100644
--- a/Src/text.c
+++ b/Src/text.c
@@ -350,6 +350,8 @@ gettext2(Estate state)
taddnl();
n = tpush(code, 1);
n->u._subsh.end = state->pc + WC_SUBSH_SKIP(code);
+ /* skip word only use for try/always */
+ state->pc++;
} else {
state->pc = s->u._subsh.end;
tindent--;
@@ -365,6 +367,8 @@ gettext2(Estate state)
taddnl();
n = tpush(code, 1);
n->u._subsh.end = state->pc + WC_CURSH_SKIP(code);
+ /* skip word only use for try/always */
+ state->pc++;
} else {
state->pc = s->u._subsh.end;
tindent--;
@@ -721,6 +725,30 @@ gettext2(Estate state)
taddstr("))");
stack = 1;
break;
+ case WC_TRY:
+ if (!s) {
+ taddstr("{");
+ tindent++;
+ taddnl();
+ n = tpush(code, 0);
+ state->pc++;
+ /* this is the end of the try block alone */
+ n->u._subsh.end = state->pc + WC_CURSH_SKIP(state->pc[-1]);
+ } else if (!s->pop) {
+ state->pc = s->u._subsh.end;
+ tindent--;
+ taddnl();
+ taddstr("} always {");
+ tindent++;
+ taddnl();
+ s->pop = 1;
+ } else {
+ tindent--;
+ taddnl();
+ taddstr("}");
+ stack = 1;
+ }
+ break;
case WC_END:
stack = 1;
break;
diff --git a/Src/zsh.h b/Src/zsh.h
index a455c4f93..c64632f4e 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -580,6 +580,10 @@ struct eccstr {
#define WC_COND 17
#define WC_ARITH 18
#define WC_AUTOFN 19
+#define WC_TRY 20
+
+/* increment as necessary */
+#define WC_COUNT 21
#define WCB_END() wc_bld(WC_END, 0)
@@ -657,6 +661,9 @@ struct eccstr {
#define WC_REPEAT_SKIP(C) wc_data(C)
#define WCB_REPEAT(O) wc_bld(WC_REPEAT, (O))
+#define WC_TRY_SKIP(C) wc_data(C)
+#define WCB_TRY(O) wc_bld(WC_TRY, (O))
+
#define WC_CASE_TYPE(C) (wc_data(C) & 3)
#define WC_CASE_HEAD 0
#define WC_CASE_OR 1
@@ -1695,6 +1702,10 @@ struct ttyinfo {
#define CS_HEREDOCD 28
#define CS_BRACE 29
#define CS_BRACEPAR 30
+#define CS_ALWAYS 31
+
+/* Increment as necessary */
+#define CS_COUNT 32
/*********************
* Memory management *
diff --git a/Test/A01grammar.ztst b/Test/A01grammar.ztst
index 6f40c98ab..a856b8ccc 100644
--- a/Test/A01grammar.ztst
+++ b/Test/A01grammar.ztst
@@ -263,6 +263,85 @@
0:basic [[ ... ]] test
#
+# Current shell execution with try/always form.
+# We put those with errors in subshells so that any unhandled error doesn't
+# propagate.
+#
+
+ {
+ print The try block.
+ } always {
+ print The always block.
+ }
+ print After the always block.
+0:Basic `always' syntax
+>The try block.
+>The always block.
+>After the always block.
+
+ ({
+ print Position one.
+ print ${*this is an error*}
+ print Position two.
+ } always {
+ if (( TRY_BLOCK_ERROR )); then
+ print An error occurred.
+ else
+ print No error occurred.
+ fi
+ }
+ print Position three)
+1:Always block with error not reset
+>Position one.
+>An error occurred.
+?(eval):3: bad substitution
+
+ ({
+ print Stelle eins.
+ print ${*voici une erreur}
+ print Posizione due.
+ } always {
+ if (( TRY_BLOCK_ERROR )); then
+ print Erratum factum est. Retro ponetur.
+ (( TRY_BLOCK_ERROR = 0 ))
+ else
+ print unray touay foay anguageslay
+ fi
+ }
+ print Status after always block is $?.)
+0:Always block with error reset
+>Stelle eins.
+>Erratum factum est. Retro ponetur.
+>Status after always block is 1.
+?(eval):3: bad substitution
+
+# Outputting of structures from the wordcode is distinctly non-trivial,
+# we probably ought to have more like the following...
+ fn1() { { echo foo; } }
+ fn2() { { echo foo; } always { echo bar; } }
+ fn3() { ( echo foo; ) }
+ functions fn1 fn2 fn3
+0:Output of syntactic structures with and without always blocks
+>fn1 () {
+> {
+> echo foo
+> }
+>}
+>fn2 () {
+> {
+> echo foo
+> } always {
+> echo bar
+> }
+>}
+>fn3 () {
+> (
+> echo foo
+> )
+>}
+
+
+#
# Tests for `Alternate Forms For Complex Commands'
#