about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Doc/Zsh/contrib.yo91
-rw-r--r--Functions/Exceptions/.distfiles4
-rw-r--r--Functions/Exceptions/catch41
-rw-r--r--Functions/Exceptions/throw30
-rw-r--r--Src/zsh.mdd52
5 files changed, 195 insertions, 23 deletions
diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index 1765918e0..1fc3458f3 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -13,6 +13,7 @@ startmenu()
 menu(Utilities)
 menu(Prompt Themes)
 menu(ZLE Functions)
+menu(Exception Handling)
 menu(MIME Functions)
 menu(Other Functions)
 endmenu()
@@ -345,7 +346,7 @@ normally call a theme's setup function directly.
 )
 enditem()
 
-texinode(ZLE Functions)(MIME Functions)(Prompt Themes)(User Contributions)
+texinode(ZLE Functions)(Exception Handling)(Prompt Themes)(User Contributions)
 sect(ZLE Functions)
 
 subsect(Widgets)
@@ -1042,7 +1043,93 @@ whether the tt(widget) style is used.
 )
 enditem()
 
-texinode(MIME Functions)(Other Functions)(ZLE Functions)(User Contributions)
+texinode(Exception Handling)(MIME Functions)(ZLE Functions)(User Contributions)
+sect(Exception Handling)
+
+Two functions are provided to enable zsh to provide exception handling in a
+form that should be familiar from other languages.
+
+startitem()
+findex(throw)
+item(tt(throw) var(exception))(
+The function tt(throw) throws the named var(exception).  The name is
+an arbitrary string and is only used by the tt(throw) and tt(catch)
+functions.  An exception is for the most part treated the same as a
+shell error, i.e. an unhandled exception will cause the shell to abort all
+processing in a function or script and to return to the top level in an
+interative shell.
+)
+item(tt(catch) var(exception-pattern))(
+The function tt(catch) returns status zero if an exception was thrown and
+the pattern var(exception-pattern) matches its name.  Otherwise it
+returns status 1.  var(exception-pattern) is a standard
+shell pattern, respecting the current setting of the tt(EXTENDED_GLOB)
+option.  An alias tt(catch) is also defined to prevent the argument to the
+function from matching filenames, so patterns may be used unquoted.  Note
+that as exceptions are not fundamentally different from other shell errors
+it is possible to catch shell errors by using an empty string as the
+exception name.  The shell variable tt(CAUGHT) is set by tt(catch) to the
+name of the exception caught.  It is possible to rethrow an exception by
+calling the tt(throw) function again once an exception has been caught.
+findex(catch)
+)
+enditem()
+
+The functions are designed to be used together with the tt(always) construct
+described in
+ifzman(zmanref(zshmisc))\
+ifnzman(noderef(Complex Commands)).  This is important as only this
+construct provides the required support for exceptions.  A typical example
+is as follows.
+
+example({
+  # "try" block
+  # ... nested code here calls "throw MyExcept"
+} always {
+  # "always" block
+  if catch MyExcept; then
+    print "Caught exception MyExcept"
+  elif catch ''; then
+    print "Caught a shell error.  Propagating..."
+    throw ''
+  fi
+  # Other exceptions are not handled but may be caught further
+  # up the call stack.
+})
+
+If all exceptions should be caught, the following idiom might be
+preferable.
+
+example({
+  # ... nested code here throws an exception
+} always {
+  if catch *; then
+    case $CAUGHT in
+      LPAR()MyExcept+RPAR()
+      print "Caught my own exception"
+      ;;
+      LPAR()*RPAR()
+      print "Caught some other exception"
+      ;;
+    esac
+  fi
+})
+
+In common with exception handling in other languages, the exception may be
+thrown by code deeply nested inside the `try' block.  However, note that it
+must be thrown inside the current shell, not in a subshell forked for a
+pipline, parenthesised current-shell construct, or some form of
+substitution.
+
+The system internally uses the shell variable tt(EXCEPTION) to record the
+name of the exception between throwing and catching.  One drawback of this
+scheme is that if the exception is not handled the variable tt(EXCEPTION)
+remains set and may be incorrectly recognised as the name of an exception
+if a shell error subsequently occurs.  Adding tt(unset EXCEPTION) at the
+start of the outermost layer of any code that uses exception handling will
+eliminate this problem.
+
+texinode(MIME Functions)(Other Functions)(Exception Handling)(User Contributions)
 sect(MIME Functions)
 
 Three functions are available to provide handling of files recognised by
diff --git a/Functions/Exceptions/.distfiles b/Functions/Exceptions/.distfiles
new file mode 100644
index 000000000..8b697438c
--- /dev/null
+++ b/Functions/Exceptions/.distfiles
@@ -0,0 +1,4 @@
+DISTFILES_SRC='
+.distfiles
+catch   throw
+'
diff --git a/Functions/Exceptions/catch b/Functions/Exceptions/catch
new file mode 100644
index 000000000..6afd664da
--- /dev/null
+++ b/Functions/Exceptions/catch
@@ -0,0 +1,41 @@
+# Catch an exception.  Returns 0 if the exception in question was caught.
+# The first argument gives the exception to catch, which may be a
+# pattern.
+# This must be within an always-block.  A typical set of handlers looks
+# like:
+#   {
+#     # try block; something here throws exceptions
+#   } always {
+#      if catch MyExcept; then
+#         # Handler code goes here.
+#         print Handling exception MyExcept
+#      elif catch *; then
+#         # This is the way to implement a catch-all.
+#         print Handling any other exception
+#      fi
+#   }
+# As with other languages, exceptions do not need to be handled
+# within an always block and may propagate to a handler further up the
+# call chain.
+#
+# It is possible to throw an exception from within the handler by
+# using "throw".
+#
+# The shell variable $CAUGHT is set to the last exception caught,
+# which is useful if the argument to "catch" was a pattern.
+#
+# Use "function" keyword in case catch is already an alias.
+function catch {
+  if [[ $TRY_BLOCK_ERROR -gt 0 && $EXCEPTION = ${~1} ]]; then
+    (( TRY_BLOCK_ERROR = 0 ))
+    typeset -g CAUGHT="$EXCEPTION"
+    unset EXCEPTION
+    return 0
+  fi
+
+  return 1
+}
+# Never use globbing with "catch".
+alias catch="noglob catch"
+
+catch "$@"
diff --git a/Functions/Exceptions/throw b/Functions/Exceptions/throw
new file mode 100644
index 000000000..5c7326999
--- /dev/null
+++ b/Functions/Exceptions/throw
@@ -0,0 +1,30 @@
+# Throw an exception.
+# The first argument is a string giving the exception.  Other arguments
+# are ignored.
+#
+# This is designed to be called somewhere inside a "try-block", i.e.
+# some code of the form:
+#   {
+#     # try-block
+#   } always {
+#     # always-block
+#   }
+# although as normal with exceptions it might be hidden deep inside
+# other code.  Note, however, that it must be code running within the
+# current shell; with shells, unlike other languages, it is quite easy
+# to miss points at which the shell forks.
+#
+# If there is nothing to catch an exception, this behaves like any
+# other shell error, aborting to the command prompt or abandoning a
+# script.
+
+# The following must not be local.
+typeset -g EXCEPTION="$1"
+readonly THROW
+if (( TRY_BLOCK_ERROR == 0 )); then
+  # We are throwing an exception from the middle of an always-block.
+  # We can do this by restoring the error status from the try-block.
+  (( TRY_BLOCK_ERROR = 1 ))
+fi
+# Raise an error, but don't show an error message.
+THROW= 2>/dev/null
diff --git a/Src/zsh.mdd b/Src/zsh.mdd
index f78e0694e..e4dae2a2d 100644
--- a/Src/zsh.mdd
+++ b/Src/zsh.mdd
@@ -1,4 +1,8 @@
 name=zsh/main
+link=static
+load=yes
+# load=static should replace use of alwayslink
+functions='Functions/Exceptions/* Functions/Misc/* Functions/MIME/* Functions/Prompts/*'
 
 nozshdep=1
 alwayslink=1
@@ -8,7 +12,7 @@ alwayslink=1
 objects="builtin.o compat.o cond.o exec.o glob.o hashtable.o \
 hist.o init.o input.o jobs.o lex.o linklist.o loop.o math.o \
 mem.o module.o options.o params.o parse.o pattern.o prompt.o signals.o \
-signames.o subst.o text.o utils.o watch.o"
+signames.o string.o subst.o text.o utils.o watch.o"
 
 headers="../config.h system.h zsh.h sigcount.h signals.h \
 prototypes.h hashtable.h ztype.h"
@@ -27,7 +31,7 @@ sigcount.h: signames.c
 
 init.o: bltinmods.list zshpaths.h zshxmods.h
 
-params.o: version.h
+init.o params.o: version.h
 
 version.h: $(sdir_top)/Config/version.mk
 	echo '#define ZSH_VERSION "'$(VERSION)'"' > $@
@@ -41,10 +45,13 @@ zshpaths.h: Makemod $(CONFIG_INCS)
 	  echo '#define FPATH_DIR "'$(fndir)'"' >> zshpaths.h.tmp; \
 	  if test x$(FUNCTIONS_SUBDIRS) != x -a \
 	  x$(FUNCTIONS_SUBDIRS) != xno; then \
-	    fpath_tmp="`for f in $$FUNCTIONS_INSTALL; do \
-	      echo $$f | sed s%/.*%%; \
-	    done | sort | uniq`"; \
-	    fpath_tmp="`echo $$fpath_tmp | sed 's/ /\", \"/g'`"; \
+	    fpath_tmp="`grep ' functions=.' \
+	    $(dir_top)/config.modules | sed -e '/^#/d' -e '/ link=no/d' \
+	    -e 's/^.* functions=//'`"; \
+	    fpath_tmp=`for f in $$fpath_tmp; do \
+	      echo $$f | sed -e 's%^Functions/%%' -e 's%/[^/]*$$%%' -e 's%/\*%%'; \
+	    done | sort | uniq`; \
+	    fpath_tmp=`echo $$fpath_tmp | sed 's/ /\", \"/g'`; \
 	    echo "#define FPATH_SUBDIRS { \"$$fpath_tmp\" }" \
 	    >>zshpaths.h.tmp; \
 	  fi; \
@@ -57,24 +64,27 @@ zshpaths.h: Makemod $(CONFIG_INCS)
 	    echo "Updated \`zshpaths.h'." ; \
 	fi
 
-bltinmods.list: modules.stamp modules-bltin xmods.conf mkbltnmlst.sh
-	srcdir='$(sdir)' MODBINS='modules-bltin' \
-	XMODCF='$(sdir)/xmods.conf' $(SHELL) $(sdir)/mkbltnmlst.sh $@
+bltinmods.list: modules.stamp mkbltnmlst.sh $(dir_top)/config.modules
+	srcdir='$(sdir)' CFMOD='$(dir_top)/config.modules' \
+	  $(SHELL) $(sdir)/mkbltnmlst.sh $@
 
-zshxmods.h: modules-bltin xmods.conf
+zshxmods.h: $(dir_top)/config.modules
 	@echo "Creating \`$@'."
 	@( \
-	    binmods=`sed 's/^/ /;s/$$/ /' modules-bltin`; \
-	    for mod in `sed 's/^.* //' $(sdir_src)/xmods.conf`; do \
-		q_mod=`echo $$mod | sed 's,Q,Qq,g;s,_,Qu,g;s,/,Qs,g'`; \
-		case $$binmods in \
-		    *" $$mod "*) \
-			echo "#define    LINKED_XMOD_$$q_mod 1" ;; \
-		    *)  echo "#ifdef DYNAMIC"; \
-			echo "# define UNLINKED_XMOD_$$q_mod 1"; \
-			echo "#endif" ;; \
-		esac; \
-	    done \
+	    for q_mod in `grep ' load=yes' $(dir_top)/config.modules | \
+	      grep ' link=static' | sed -e '/^#/d' -e 's/ .*//' \
+	        -e 's/^name=//' -e 's,Q,Qq,g;s,_,Qu,g;s,/,Qs,g'`; do \
+		test x$q_mod = xzshQsmain && continue; \
+	        echo "#define LINKED_XMOD_$$q_mod 1"; \
+	    done; \
+	    for q_mod in `grep ' load=yes' $(dir_top)/config.modules | \
+	      grep ' link=dynamic' | sed -e '/^#/d' -e 's/ .*//' \
+	        -e 's/^name=//' -e 's,Q,Qq,g;s,_,Qu,g;s,/,Qs,g'`; do \
+		test x$q_mod = x && continue; \
+	        echo "#ifdef DYNAMIC"; \
+		echo "# define UNLINKED_XMOD_$$q_mod 1"; \
+		echo "#endif"; \
+	    done; \
 	) > $@
 
 clean-here: clean.zsh