From 96fc5e800a1437a433c963267cd1c9d4de266438 Mon Sep 17 00:00:00 2001 From: Paul Ackersviller Date: Sun, 8 Apr 2007 01:35:42 +0000 Subject: [PATCH] Merge in workers/20812 for exception handling using "always" block. --- Doc/Zsh/contrib.yo | 91 ++++++++++++++++++++++++++++++++- Functions/Exceptions/.distfiles | 4 ++ Functions/Exceptions/catch | 41 +++++++++++++++ Functions/Exceptions/throw | 30 +++++++++++ Src/zsh.mdd | 52 +++++++++++-------- 5 files changed, 195 insertions(+), 23 deletions(-) create mode 100644 Functions/Exceptions/.distfiles create mode 100644 Functions/Exceptions/catch create mode 100644 Functions/Exceptions/throw 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