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

PATCH: second go at `always' blocks



Here is a second attempt following Oliver's and Bart's suggestions.

The new syntax is:

  {
    # execute normally
  } always {
    # execute always
    # optionally reset error
    (( TRY_BLOCK_ERROR = 0 ))
  }

This doesn't clash with any currently valid zsh syntax, except that
we don't have parameter namespaces so can't say .sh.try_block_error.

Please read the description in grammar.yo (zshmisc.1) and see if
I've made it clear.

There is no special syntax for aborting the `try' block (I've continued
to use that language for clarity even though there is no corresponding
keyword).

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	18 Jun 2004 10:56:08 -0000
@@ -234,6 +234,63 @@
 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), the
+normal shell status after the end of tt(always-list) is the value
+returned from tt(always-list).
+
+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.)
+
+Note that the return status tt($?) after the end of the tt(always) block
+reflects the return status at the end of the var(try-list), consistent
+with other parts of the shell status.
+
+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(}))
Index: Doc/Zsh/params.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/params.yo,v
retrieving revision 1.21
diff -u -r1.21 params.yo
--- Doc/Zsh/params.yo	6 Apr 2004 13:01:09 -0000	1.21
+++ Doc/Zsh/params.yo	18 Jun 2004 10:56:09 -0000
@@ -619,6 +619,14 @@
 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.
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	18 Jun 2004 10:56:09 -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() {
+{
+  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
+}
 
 # 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/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	18 Jun 2004 10:56:16 -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 */
@@ -325,6 +325,9 @@
 {
     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 @@
                 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);
 	    }
 	}
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	18 Jun 2004 10:56:17 -0000
@@ -616,3 +616,68 @@
 
     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;
+}
Index: Src/params.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/params.c,v
retrieving revision 1.85
diff -u -r1.85 params.c
--- Src/params.c	2 Jun 2004 22:14:26 -0000	1.85
+++ Src/params.c	18 Jun 2004 10:56:19 -0000
@@ -106,6 +106,7 @@
  
 /* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */
 
+
 /**/
 mod_export int termflags;
  
@@ -191,6 +192,7 @@
 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),
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	18 Jun 2004 10:56:20 -0000
@@ -1330,25 +1330,55 @@
 }
 
 /*
- * 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));
+    }
 }
 
 /*
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	18 Jun 2004 10:56:21 -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,7 @@
     "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, *
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	18 Jun 2004 10:56:21 -0000
@@ -350,6 +350,8 @@
 		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 @@
 		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 @@
 	    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;
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	18 Jun 2004 10:56:22 -0000
@@ -580,6 +580,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 +661,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 +1702,10 @@
 #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 *

-- 
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