about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Stephenson <pws@users.sourceforge.net>2004-06-22 13:09:55 +0000
committerPeter Stephenson <pws@users.sourceforge.net>2004-06-22 13:09:55 +0000
commitd591334e9d616830fbd24909db2e21ac4b959742 (patch)
tree65b44f10e6995c95605c8a56c3ca0fd9bfe6ad9d
parent6a1d913bd6214d7ca7f8cf1a82efef1fa4bae101 (diff)
downloadzsh-d591334e9d616830fbd24909db2e21ac4b959742.tar.gz
zsh-d591334e9d616830fbd24909db2e21ac4b959742.tar.xz
zsh-d591334e9d616830fbd24909db2e21ac4b959742.zip
20076, 20084: { ... } always { ... } syntax.
-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'
 #