Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: `try' syntax



Here's an experimental and possibly controversial (though, I think,
working) new syntax for running code protected from error and other
conditions in an internal block.  Dave Yost suggested something like
this, but I've been missing it for a long time.  Traps are too clumsy to
provide good protection against errors or interrupts.

The basic idea is:

  :try
     # Code here runs normally
  :always
     # Code here always gets run, even if there is a fatal
     # error (unless it crashes the shell), or a return, break,
     # or continue in the :try block.
     #
     # Optionally:
     unset -e
     # removes any error condition.  Apart from that, after exiting
     # the always block, the logic is as if it hadn't been present.
  :tried

Thus it's a bit more like an unwind-protect in Lisp than exception
catching, which is why I deliberately avoided using `catch' in the
name.  (The name `always' was probably suggested by Verilog but
it has a completely different meaning.)

The trickiest bit is the naming.  I wanted something reasonably
memorable, but without polluting the name space, hence the names
with the colons in front.  Note that `disable -r :try :always :tried'
works.

Index: Doc/Zsh/builtins.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/builtins.yo,v
retrieving revision 1.68
diff -u -r1.68 builtins.yo
--- Doc/Zsh/builtins.yo	21 May 2004 20:04:59 -0000	1.68
+++ Doc/Zsh/builtins.yo	14 Jun 2004 11:11:22 -0000
@@ -1472,8 +1472,10 @@
 )
 findex(unset)
 cindex(parameters, unsetting)
-item(tt(unset) [ tt(-fmv) ] var(name) ...)(
-Each named parameter is unset.
+cindex(error condition, resetting)
+xitem(tt(unset) [ tt(-fmv) ] var(name) ...)
+item(tt(unset -e))(
+In the first form, each named parameter is unset.
 Local parameters remain local even if unset; they appear unset within scope,
 but the previous value will still reappear when the scope ends.
 
@@ -1490,6 +1492,11 @@
 default behaviour.
 
 tt(unset -f) is equivalent to tt(unfunction).
+
+tt(unset -e) may only be used in an tt(:always) block of a tt(:try)
+construct, as described in
+ifzman(the section em(Complex Commands) in zmanref(zshmisc))\
+ifnzman(noderef(Complex Commands))
 )
 findex(unsetopt)
 cindex(options, unsetting)
Index: Doc/Zsh/grammar.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/grammar.yo,v
retrieving revision 1.8
diff -u -r1.8 grammar.yo
--- Doc/Zsh/grammar.yo	24 Jul 2001 13:16:09 -0000	1.8
+++ Doc/Zsh/grammar.yo	14 Jun 2004 11:11:26 -0000
@@ -250,6 +250,33 @@
 there is a single var(word);  otherwise, the parentheses will be treated as
 forming a globbing pattern in that case.
 )
+findex(:always)
+findex(:try)
+cindex(always blocks)
+cindex(try blocks)
+item(tt(:try) var(try-list) tt(:always) var(always-list) tt(:tried))(
+First var(try-list) is executed.  Regardless of errors, or tt(break),
+tt(continue), or tt(return) commands encountered within var(try-list),
+var(always-list) is then executed.  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 the tt(:try) statement and tt(:always) block were not
+present.
+
+Newlines or semicolons may optionally appear between the tt(:try:)
+or tt(:always:) and the following list.
+
+The command `tt(unset -e)' may be used within the tt(:always) block to
+unset any error condition caused by the tt(:try) block.  Hence the
+following idiom causes errors in var(commands...) to be ignored, without
+the need to run var(commands...) in a subshell.
+
+example(:try
+  var(commands)
+:always
+  unset -e
+:tried)
+)
 cindex(timing)
 findex(time)
 item(tt(time) [ var(pipeline) ])(
Index: Functions/MIME/zsh-mime-setup
===================================================================
RCS file: /cvsroot/zsh/zsh/Functions/MIME/zsh-mime-setup,v
retrieving revision 1.1
diff -u -r1.1 zsh-mime-setup
--- Functions/MIME/zsh-mime-setup	14 Sep 2003 19:37:35 -0000	1.1
+++ Functions/MIME/zsh-mime-setup	14 Jun 2004 11:11:26 -0000
@@ -62,9 +62,8 @@
 zstyle -a :mime: mailcap cap_files ||
   cap_files=(~/.mailcap /etc/mailcap)
 
-TRAPEXIT() { unfunction mime-setup-add-type >&/dev/null; return 0; }
-
-mime-setup-add-type() {
+:try
+  mime-setup-add-type() {
     local type suffix
     local -a array
 
@@ -90,115 +89,117 @@
 	    fi
 	fi
     done
-}
+  }
 
-# Loop through files to find suffixes for MIME types.
-# Earlier entries take precedence, so the files need to be listed
-# with the user's own first.  This also means pre-existing
-# values in suffix_type_map are respected.
-for file in $type_files; do
+  # Loop through files to find suffixes for MIME types.
+  # Earlier entries take precedence, so the files need to be listed
+  # with the user's own first.  This also means pre-existing
+  # values in suffix_type_map are respected.
+  for file in $type_files; do
     [[ -r $file ]] || continue
 
     # For once we rely on the fact that read handles continuation
     # lines ending in backslashes, i.e. there's no -r.
     while read line; do
-	# Skip blank or comment lines.
-	[[ $line = [[:space:]]#(\#*|) ]] && continue
-
-	# There are two types of line you find in MIME type files.
-	# The original simple sort contains the type name then suffixes
-	# separated by whitespace.  However, Netscape insists
-	# on adding lines with backslash continuation with
-	# key="value" pairs.  So we'd better handle both.
-	if [[ $line = *=* ]]; then
-	    # Gory.
-	    # This relies on the fact that a typical entry:
-	    #   type=video/x-mpeg2 desc="MPEG2 Video" exts="mpv2,mp2v"
-	    # looks like a parameter assignment.  However, we really
-	    # don't want to be screwed up by future extensions,
-	    # so we split the elements to an array and pick out the
-	    # ones we're interested in.
-	    type= exts=
-
-	    # Syntactically split line to preserve quoted words.
-	    array=(${(z)line})
-	    for elt in $array; do
-		if [[ $elt = (type|exts)=* ]]; then
-		    eval $elt
-		fi
-	    done
+      # Skip blank or comment lines.
+      [[ $line = [[:space:]]#(\#*|) ]] && continue
 
-	    # Get extensions by splitting on comma
-	    array=(${(s.,.)exts})
-
-	    [[ -n $type ]] && mime-setup-add-type $type $array
-	else
-	    # Simple.
-	    mime-setup-add-type ${=line}
-	fi
+      # There are two types of line you find in MIME type files.
+      # The original simple sort contains the type name then suffixes
+      # separated by whitespace.  However, Netscape insists
+      # on adding lines with backslash continuation with
+      # key="value" pairs.  So we'd better handle both.
+      if [[ $line = *=* ]]; then
+        # Gory.
+        # This relies on the fact that a typical entry:
+        #   type=video/x-mpeg2 desc="MPEG2 Video" exts="mpv2,mp2v"
+        # looks like a parameter assignment.  However, we really
+        # don't want to be screwed up by future extensions,
+        # so we split the elements to an array and pick out the
+        # ones we're interested in.
+        type= exts=
+
+        # Syntactically split line to preserve quoted words.
+        array=(${(z)line})
+        for elt in $array; do
+          if [[ $elt = (type|exts)=* ]]; then
+            eval $elt
+          fi
+        done
+
+        # Get extensions by splitting on comma
+        array=(${(s.,.)exts})
+
+        [[ -n $type ]] && mime-setup-add-type $type $array
+      else
+        # Simple.
+        mime-setup-add-type ${=line}
+      fi
     done <$file
-done
-
+  done
+:always
+  unfunction mime-setup-add-type >&/dev/null
+:tried
 
 # Loop through files to find handlers for types.
 for file in $cap_files; do
-    [[ -r $file ]] || continue
+  [[ -r $file ]] || continue
 
-    # Oh, great.  We need to preserve backslashes inside the line,
-    # but need to manage continuation lines.
-    while read -r line; do
-	# Skip blank or comment lines.
-	[[ $line = [[:space:]]#(\#*|) ]] && continue
-
-	while [[ $line = (#b)(*)\\ ]]; do
-	    line=$match[1]
-	    read -r line2 || break
-	    line+=$line2
-	done
-
-	# Guess what, this file has a completely different format.
-	# See mailcap(4).
-	# The biggest unpleasantness here is that the fields are
-	# delimited by semicolons, but the command field, which
-	# is the one we want to extract, may itself contain backslashed
-	# semicolons.
-	if [[ $line = (#b)[[:space:]]#([^[:space:]\;]##)[[:space:]]#\;(*) ]]
-	then
-	    # this is the only form we can handle, but there's no point
-	    # issuing a warning for other forms.
-	    type=$match[1]
-            line=$match[2]
-	    # See if it has flags after the command.
-	    if [[ $line = (#b)(([^\;\\]|\\\;|\\[^\;])#)\;(*) ]]; then
-		line=$match[1]
-		flags=$match[3]
-	    else
-		flags=
-	    fi
-	    # Remove quotes from semicolons
-	    line=${line//\\\;/\;}
-	    # and remove any surrounding white space --- this might
-	    # make the handler empty.
-	    line=${${line##[[:space:]]#}%%[[:space:]]}
-	    if [[ -z $type_handler_map[$type] ]]; then
-		if [[ -n $o_verbose ]]; then
-		    print -r "Adding handler for type $type:
+  # Oh, great.  We need to preserve backslashes inside the line,
+  # but need to manage continuation lines.
+  while read -r line; do
+    # Skip blank or comment lines.
+    [[ $line = [[:space:]]#(\#*|) ]] && continue
+
+    while [[ $line = (#b)(*)\\ ]]; do
+      line=$match[1]
+      read -r line2 || break
+      line+=$line2
+    done
+
+    # Guess what, this file has a completely different format.
+    # See mailcap(4).
+    # The biggest unpleasantness here is that the fields are
+    # delimited by semicolons, but the command field, which
+    # is the one we want to extract, may itself contain backslashed
+    # semicolons.
+    if [[ $line = (#b)[[:space:]]#([^[:space:]\;]##)[[:space:]]#\;(*) ]]
+    then
+      # this is the only form we can handle, but there's no point
+      # issuing a warning for other forms.
+      type=$match[1]
+      line=$match[2]
+      # See if it has flags after the command.
+      if [[ $line = (#b)(([^\;\\]|\\\;|\\[^\;])#)\;(*) ]]; then
+        line=$match[1]
+        flags=$match[3]
+      else
+        flags=
+      fi
+      # Remove quotes from semicolons
+      line=${line//\\\;/\;}
+      # and remove any surrounding white space --- this might
+      # make the handler empty.
+      line=${${line##[[:space:]]#}%%[[:space:]]}
+      if [[ -z $type_handler_map[$type] ]]; then
+        if [[ -n $o_verbose ]]; then
+          print -r "Adding handler for type $type:
   $line" >&2
-		fi
-		type_handler_map[$type]=$line
-		type_flags_map[$type]=$flags
-		if [[ -n $flags && -n $o_verbose ]]; then
-		    print -r "  with flags $flags" >&2
-		fi
-	    elif [[ -n $o_verbose ]]; then
-		print -r "Skipping handler for already defined type $type:
+	fi
+	type_handler_map[$type]=$line
+	type_flags_map[$type]=$flags
+	if [[ -n $flags && -n $o_verbose ]]; then
+	  print -r "  with flags $flags" >&2
+	fi
+      elif [[ -n $o_verbose ]]; then
+	print -r "Skipping handler for already defined type $type:
   $line" >&2
-		if [[ -n $flags ]]; then
-		    print -r " with flags $flags" >&2
-		fi
-	    fi
+	if [[ -n $flags ]]; then
+	  print -r " with flags $flags" >&2
 	fi
-    done <$file
+      fi
+    fi
+  done <$file
 done
 
 
Index: Src/builtin.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/builtin.c,v
retrieving revision 1.121
diff -u -r1.121 builtin.c
--- Src/builtin.c	2 Jun 2004 22:14:25 -0000	1.121
+++ Src/builtin.c	14 Jun 2004 11:11:34 -0000
@@ -125,7 +125,7 @@
     BUILTIN("unalias", 0, bin_unhash, 1, -1, 0, "ms", "a"),
     BUILTIN("unfunction", 0, bin_unhash, 1, -1, 0, "m", "f"),
     BUILTIN("unhash", 0, bin_unhash, 1, -1, 0, "adfms", NULL),
-    BUILTIN("unset", BINF_PSPECIAL, bin_unset, 1, -1, 0, "fmv", NULL),
+    BUILTIN("unset", BINF_PSPECIAL, bin_unset, 0, -1, 0, "efmv", NULL),
     BUILTIN("unsetopt", 0, bin_setopt, 0, -1, BIN_UNSETOPT, NULL, NULL),
     BUILTIN("wait", 0, bin_fg, 0, -1, BIN_WAIT, NULL, NULL),
     BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsw", NULL),
@@ -2614,6 +2614,23 @@
     int match = 0, returnval = 0;
     int i;
 
+    if (OPT_ISSET(ops,'e')) {
+	if (*argv) {
+	    zwarnnam(name, "arguments not allowed with -e", NULL, 0);
+	    return 1;
+	}
+	if (try_errflag < 0) {
+	    zwarnnam(name, "-e can only be used in an :always block", NULL, 0);
+	    return 1;
+	}
+	try_errflag = 0;
+	return 0;
+    }
+    if (!*argv) {
+	zwarnnam(name, "not enough arguments", NULL, 0);
+	return 1;
+    }
+
     /* unset -f is the same as unfunction */
     if (OPT_ISSET(ops,'f'))
 	return bin_unhash(name, argv, ops, func);
Index: Src/exec.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/exec.c,v
retrieving revision 1.64
diff -u -r1.64 exec.c
--- Src/exec.c	2 Jun 2004 22:14:25 -0000	1.64
+++ Src/exec.c	14 Jun 2004 11:11:39 -0000
@@ -137,10 +137,10 @@
 
 /* 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 */
Index: Src/hashtable.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/hashtable.c,v
retrieving revision 1.20
diff -u -r1.20 hashtable.c
--- Src/hashtable.c	2 Jun 2004 22:14:25 -0000	1.20
+++ Src/hashtable.c	14 Jun 2004 11:11:40 -0000
@@ -946,6 +946,9 @@
     {NULL, "time", 0, TIME},
     {NULL, "until", 0, UNTIL},
     {NULL, "while", 0, WHILE},
+    {NULL, ":try", 0, TRY},
+    {NULL, ":always", 0, ALWAYS},
+    {NULL, ":tried", 0, TRIED},
     {NULL, NULL, 0, 0}
 };
 
Index: Src/loop.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/loop.c,v
retrieving revision 1.13
diff -u -r1.13 loop.c
--- Src/loop.c	2 Jun 2004 22:14:26 -0000	1.13
+++ Src/loop.c	14 Jun 2004 11:11:41 -0000
@@ -616,3 +616,64 @@
 
     return lastval;
 }
+
+/* Errflag from :try block, may be reset in :always block. */
+
+/**/
+int
+try_errflag = -1;
+
+/**/
+int
+exectry(Estate state, int do_exec)
+{
+    Wordcode end, always;
+    int endval;
+    int save_try_errflag, save_retflag, save_breaks, save_loops, save_contflag;
+
+    end = state->pc + WC_TRY_SKIP(state->pc[-1]);
+    always = state->pc + 1 + WC_TRY_SKIP(*state->pc);
+    state->pc++;
+    pushheap();
+    cmdpush(CS_TRY);
+
+    /* 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 = 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;
+    try_errflag = save_try_errflag;
+    retflag = save_retflag;
+    breaks = save_breaks;
+    loops = save_loops;
+    contflag = save_contflag;
+
+    cmdpop();
+    popheap();
+    state->pc = end;
+
+    return endval || errflag;
+}
Index: Src/parse.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/parse.c,v
retrieving revision 1.44
diff -u -r1.44 parse.c
--- Src/parse.c	2 Jun 2004 22:14:26 -0000	1.44
+++ Src/parse.c	14 Jun 2004 11:11:43 -0000
@@ -760,7 +760,7 @@
 }
 
 /*
- * cmd	: { redir } ( for | case | if | while | repeat |
+ * cmd	: { redir } ( for | case | if | while | repeat | try
  *				subsh | funcdef | time | dinbrack | dinpar | simple ) { redir }
  */
 
@@ -819,6 +819,11 @@
 	par_repeat(complex);
 	cmdpop();
 	break;
+    case TRY:
+	cmdpush(CS_TRY);
+	par_try(complex);
+	cmdpop();
+	break;
     case INPAR:
 	*complex = 1;
 	cmdpush(CS_SUBSH);
@@ -1330,6 +1335,49 @@
 }
 
 /*
+ * try          : TRY list ALWAYS list TRIED
+ */
+
+/**/
+static void
+par_try(int *complex)
+{
+    int oecused = ecused, p, pp;
+
+    p = ecadd(0);
+    pp = ecadd(0);
+
+    do {
+	yylex();
+    } while (tok == SEPER);
+
+    par_save_list(complex);
+    ecbuf[pp] = WCB_TRY(ecused - 1 - pp);
+
+    incmdpos = 1;
+
+    while (tok == SEPER)
+	yylex();
+
+    if (tok != ALWAYS)
+	YYERRORV(oecused);
+
+    cmdpop();
+    cmdpush(CS_ALWAYS);
+    yylex();
+    par_save_list(complex);
+    while (tok == SEPER)
+	yylex();
+
+    incmdpos = 1;
+
+    if (tok != TRIED)
+	YYERRORV(oecused);
+    yylex();
+    ecbuf[p] = WCB_TRY(ecused - 1 - p);
+}
+
+/*
  * subsh	: ( INPAR | INBRACE ) list ( OUTPAR | OUTBRACE )
  */
 
Index: Src/prompt.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/prompt.c,v
retrieving revision 1.18
diff -u -r1.18 prompt.c
--- Src/prompt.c	28 May 2004 19:21:46 -0000	1.18
+++ Src/prompt.c	14 Jun 2004 11:11:44 -0000
@@ -49,7 +49,7 @@
 
 /* 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,8 @@
     "case",     "function",  "subsh",     "cursh",
     "array",    "quote",     "dquote",    "bquote",
     "cmdsubst", "mathsubst", "elif-then", "heredoc",
-    "heredocd", "brace",     "braceparam",
+    "heredocd", "brace",     "braceparam", ":try",
+    ":always"
 };
  
 /* The buffer into which an expanded and metafied prompt is being written, *
Index: Src/text.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/text.c,v
retrieving revision 1.13
diff -u -r1.13 text.c
--- Src/text.c	4 Feb 2003 11:23:05 -0000	1.13
+++ Src/text.c	14 Jun 2004 11:11:45 -0000
@@ -721,6 +721,31 @@
 	    taddstr("))");
 	    stack = 1;
 	    break;
+	case WC_TRY:
+	    if (!s) {
+		taddstr(":try");
+		/*
+		 * skip location of end of first list, we will
+		 * go all the way through it since we're just printing.
+		 */
+		state->pc++;
+		tindent++;
+		taddnl();
+		tpush(code, 0);
+	    } else if (!s->pop) {
+		tindent--;
+		taddnl();
+		taddstr(":always");
+		tindent++;
+		taddnl();
+		s->pop = 1;
+	    } else {
+		tindent--;
+		taddnl();
+		taddstr(":tried");
+		stack = 1;
+	    }
+	    break;
 	case WC_END:
 	    stack = 1;
 	    break;
Index: Src/zsh.h
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/zsh.h,v
retrieving revision 1.56
diff -u -r1.56 zsh.h
--- Src/zsh.h	20 Apr 2004 12:11:16 -0000	1.56
+++ Src/zsh.h	14 Jun 2004 11:11:47 -0000
@@ -226,7 +226,10 @@
     THEN,	/* then      */
     TIME,	/* time      */
     UNTIL,	/* until     */ /* 60 */
-    WHILE	/* while     */
+    WHILE,	/* while     */
+    TRY,	/* :try      */
+    ALWAYS,	/* :always   */
+    TRIED	/* :tried    */
 };
 
 /* Redirection types.  If you modify this, you may also have to modify *
@@ -580,6 +583,10 @@
 #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 +664,9 @@
 #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 +1705,11 @@
 #define CS_HEREDOCD    28
 #define CS_BRACE       29
 #define CS_BRACEPAR    30
+#define CS_TRY	       31
+#define CS_ALWAYS      32
+
+/* Increment as necessary */
+#define CS_COUNT       33
 
 /*********************
  * Memory management *

-- 
Peter Stephenson <pws@xxxxxxx>                  Software Engineer
CSR Ltd., Science Park, Milton Road,
Cambridge, CB4 0WH, UK                          Tel: +44 (0)1223 692070


**********************************************************************
This email and any files transmitted with it are confidential and
intended solely for the use of the individual or entity to whom they
are addressed. If you have received this email in error please notify
the system manager.

This footnote also confirms that this email message has been swept by
MIMEsweeper for the presence of computer viruses.

www.mimesweeper.com
**********************************************************************



Messages sorted by: Reverse Date, Date, Thread, Author