diff options
-rw-r--r-- | ChangeLog | 7 | ||||
-rw-r--r-- | Doc/Zsh/grammar.yo | 56 | ||||
-rw-r--r-- | Doc/Zsh/params.yo | 8 | ||||
-rw-r--r-- | Src/exec.c | 10 | ||||
-rw-r--r-- | Src/loop.c | 65 | ||||
-rw-r--r-- | Src/params.c | 2 | ||||
-rw-r--r-- | Src/parse.c | 38 | ||||
-rw-r--r-- | Src/prompt.c | 4 | ||||
-rw-r--r-- | Src/text.c | 28 | ||||
-rw-r--r-- | Src/zsh.h | 11 | ||||
-rw-r--r-- | Test/A01grammar.ztst | 79 |
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' # |