m4-commit
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[SCM] GNU M4 source repository branch, argv_ref, updated. branch-cvs-rea


From: Eric Blake
Subject: [SCM] GNU M4 source repository branch, argv_ref, updated. branch-cvs-readonly-52-gffac585
Date: Mon, 14 Jul 2008 23:14:37 +0000

This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GNU M4 source repository".

http://git.sv.gnu.org/gitweb/?p=m4.git;a=commitdiff;h=ffac5851f2b3a287e0308502e79532432553bcf0

The branch, argv_ref has been updated
       via  ffac5851f2b3a287e0308502e79532432553bcf0 (commit)
       via  2497285dfaec9f4b4dc163e033048f1face2f978 (commit)
      from  960322ed3c7bca3b86f3f7068a07a815d36bb71b (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit ffac5851f2b3a287e0308502e79532432553bcf0
Author: Eric Blake <address@hidden>
Date:   Mon Jul 14 16:26:12 2008 -0600

    Stage32: avoid copying text from defn

commit 2497285dfaec9f4b4dc163e033048f1face2f978
Author: Eric Blake <address@hidden>
Date:   Mon Jul 14 12:21:07 2008 -0600

    Stage31: speed up defn by tracking quote_age

-----------------------------------------------------------------------

Summary of changes:
 doc/m4.texinfo       |   17 +++++++
 examples/Makefile.am |    1 +
 examples/append.m4   |   18 +++++++
 src/builtin.c        |   35 +++++++++-----
 src/freeze.c         |    2 +-
 src/input.c          |  122 +++++++++++++++++++++++++++++++++++++++++++++-----
 src/m4.c             |    2 +-
 src/m4.h             |   16 +++++--
 src/macro.c          |   28 +++++++++++-
 9 files changed, 209 insertions(+), 32 deletions(-)
 create mode 100644 examples/append.m4

diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index f81b5c6..755725d 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -2325,6 +2325,23 @@ ifdef(`foo', `yes', `no')
 @result{}yes
 @end example
 
address@hidden
address@hidden Stress test of defn, not worth putting in the manual.
+
address@hidden
+define(`s', `0')define(`l', `0123456789012346789')define(`e', `$@@')
address@hidden
+e(defn(`s'), defn(`s', `s')defn(`s'))
address@hidden,000
+e(defn(`l'), `
+'defn(`l', `l')defn(`l'))
address@hidden,
address@hidden
+e(defn(`l', `s'), defn(`s', `l'))
address@hidden,00123456789012346789
address@hidden example
address@hidden ignore
+
 @node Pushdef
 @section Temporarily redefining macros
 
diff --git a/examples/Makefile.am b/examples/Makefile.am
index 254d2ab..1ece624 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -20,6 +20,7 @@
 ## This file written by Eric Blake <address@hidden>
 
 EXTRA_DIST =\
+append.m4 \
 capitalize.m4 \
 capitalize2.m4 \
 comments.m4 \
diff --git a/examples/append.m4 b/examples/append.m4
new file mode 100644
index 0000000..be6ed3d
--- /dev/null
+++ b/examples/append.m4
@@ -0,0 +1,18 @@
+dnl Stress test for appending algorithm.  Usage:
+dnl m4 -Ipath/to/examples [-Doptions] append.m4
+dnl Options include:
+dnl -Dlimit=<num> - set upper limit of sequence to <num>, default 1000
+dnl -Dverbose - print progress to the screen, rather than discarding
+dnl -Dnext=<code> - append <code> each iteration, default to the current count
+dnl -Ddebug[=<code>] - execute <code> after loop
+dnl -Dsleep=<num> - sleep for <num> seconds before exit, to allow time
+dnl   to examine peak process memory usage
+include(`forloop2.m4')dnl
+ifdef(`limit', `', `define(`limit', `1000')')dnl
+ifdef(`verbose', `', `divert(`-1')')dnl
+ifdef(`next', `', `define(`next', `i')')dnl
+ifdef(`debug', `', `define(`debug')')dnl
+define(`var')define(`append', `define(`var', defn(`var')`$1')')dnl
+forloop(`i', `1', limit, `i
+append(next)')debug
+ifdef(`sleep',`syscmd(`echo done>/dev/tty;sleep 'sleep)')dnl
diff --git a/src/builtin.c b/src/builtin.c
index d257286..4e29d00 100644
--- a/src/builtin.c
+++ b/src/builtin.c
@@ -441,17 +441,19 @@ free_regex (void)
       }
 }
 
-/*------------------------------------------------------------------.
-| Define a predefined or user-defined macro, with name NAME of      |
-| length NAME_LEN, and expansion TEXT of length LEN.  LEN may be    |
-| SIZE_MAX, to use the string length of TEXT instead.  MODE is      |
-| SYMBOL_INSERT for "define" or SYMBOL_PUSHDEF for "pushdef".  This |
-| function is also used from main ().                               |
-`------------------------------------------------------------------*/
+/*-----------------------------------------------------------------.
+| Define a predefined or user-defined macro, with name NAME of     |
+| length NAME_LEN, and expansion TEXT of length LEN.  LEN may be   |
+| SIZE_MAX, to use the string length of TEXT instead.  QUOTE_AGE   |
+| describes the quoting used by TEXT, or zero to force rescanning  |
+| when defn is later used to retrieve TEXT.  MODE is SYMBOL_INSERT |
+| for "define" or SYMBOL_PUSHDEF for "pushdef".  This function is  |
+| also used from main ().                                          |
+`-----------------------------------------------------------------*/
 
 void
 define_user_macro (const char *name, size_t name_len, const char *text,
-                  size_t len, symbol_lookup mode)
+                  size_t len, symbol_lookup mode, unsigned int quote_age)
 {
   symbol *s;
   char *defn;
@@ -467,6 +469,7 @@ define_user_macro (const char *name, size_t name_len, const 
char *text,
   SYMBOL_TYPE (s) = TOKEN_TEXT;
   SYMBOL_TEXT (s) = defn;
   SYMBOL_TEXT_LEN (s) = len;
+  SYMBOL_TEXT_QUOTE_AGE (s) = quote_age;
   SYMBOL_MACRO_ARGS (s) = true;
 
   /* Implement --warn-macro-sequence.  */
@@ -532,13 +535,13 @@ builtin_init (void)
       {
        if (pp->unix_name != NULL)
          define_user_macro (pp->unix_name, strlen (pp->unix_name),
-                            pp->func, SIZE_MAX, SYMBOL_INSERT);
+                            pp->func, SIZE_MAX, SYMBOL_INSERT, 0);
       }
     else
       {
        if (pp->gnu_name != NULL)
          define_user_macro (pp->gnu_name, strlen (pp->gnu_name),
-                            pp->func, SIZE_MAX, SYMBOL_INSERT);
+                            pp->func, SIZE_MAX, SYMBOL_INSERT, 0);
       }
 }
 
@@ -693,7 +696,7 @@ define_macro (int argc, macro_arguments *argv, 
symbol_lookup mode)
 
   if (argc == 2)
     {
-      define_user_macro (ARG (1), ARG_LEN (1), "", 0, mode);
+      define_user_macro (ARG (1), ARG_LEN (1), "", 0, mode, 0);
       return;
     }
 
@@ -704,7 +707,7 @@ define_macro (int argc, macro_arguments *argv, 
symbol_lookup mode)
       /* fallthru */
     case TOKEN_TEXT:
       define_user_macro (ARG (1), ARG_LEN (1), arg_text (argv, 2, true),
-                        arg_len (argv, 2, true), mode);
+                        arg_len (argv, 2, true), mode, arg_quote_age (argv));
       break;
 
     case TOKEN_FUNC:
@@ -1091,7 +1094,7 @@ m4_defn (struct obstack *obs, int argc, macro_arguments 
*argv)
        {
        case TOKEN_TEXT:
          obstack_grow (obs, curr_quote.str1, curr_quote.len1);
-         obstack_grow (obs, SYMBOL_TEXT (s), SYMBOL_TEXT_LEN (s));
+         push_defn (s);
          obstack_grow (obs, curr_quote.str2, curr_quote.len2);
          break;
 
@@ -2458,6 +2461,12 @@ expand_user_macro (struct obstack *obs, symbol *sym,
   int i;
   const char *dollar = memchr (text, '$', len);
 
+  if (!dollar)
+    {
+      push_defn (sym);
+      return;
+    }
+
   while (dollar)
     {
       obstack_grow (obs, text, dollar - text);
diff --git a/src/freeze.c b/src/freeze.c
index 5d4ac42..9ec3aa0 100644
--- a/src/freeze.c
+++ b/src/freeze.c
@@ -354,7 +354,7 @@ reload_frozen_state (const char *name)
              /* Enter a macro having an expansion text as a definition.  */
 
              define_user_macro (string[0], number[0], string[1], number[1],
-                                SYMBOL_PUSHDEF);
+                                SYMBOL_PUSHDEF, 0);
              break;
 
            case 'Q':
diff --git a/src/input.c b/src/input.c
index 312aed7..53eddae 100644
--- a/src/input.c
+++ b/src/input.c
@@ -232,6 +232,7 @@ make_text_link (struct obstack *obs, token_chain **start, 
token_chain **end)
       chain->u.u_s.str = str;
       chain->u.u_s.len = len;
       chain->u.u_s.level = -1;
+      chain->u.u_s.sym = NULL;
     }
 }
 
@@ -342,6 +343,49 @@ push_string_init (const char *file, int line)
   return current_input;
 }
 
+/*-------------------------------------------------------------.
+| Push the text macro definition in SYM onto the input stack.  |
+`-------------------------------------------------------------*/
+void
+push_defn (symbol *sym)
+{
+  token_chain *chain;
+  size_t len = SYMBOL_TEXT_LEN (sym);
+
+  assert (next && SYMBOL_TYPE (sym) == TOKEN_TEXT);
+
+  /* Speed consideration - for short enough tokens, the speed and
+     memory overhead of parsing another INPUT_CHAIN link outweighs the
+     time to inline the token text.  */
+  if (len <= INPUT_INLINE_THRESHOLD)
+    {
+      obstack_grow (current_input, SYMBOL_TEXT (sym), len);
+      return;
+    }
+
+  if (next->type == INPUT_STRING)
+    {
+      next->type = INPUT_CHAIN;
+      next->u.u_c.chain = next->u.u_c.end = NULL;
+    }
+  make_text_link (current_input, &next->u.u_c.chain, &next->u.u_c.end);
+
+  chain = (token_chain *) obstack_alloc (current_input, sizeof *chain);
+  if (next->u.u_c.end)
+    next->u.u_c.end->next = chain;
+  else
+    next->u.u_c.chain = chain;
+  next->u.u_c.end = chain;
+  chain->next = NULL;
+  chain->type = CHAIN_STR;
+  chain->quote_age = SYMBOL_TEXT_QUOTE_AGE (sym);
+  chain->u.u_s.str = SYMBOL_TEXT (sym);
+  chain->u.u_s.len = len;
+  chain->u.u_s.level = -1;
+  chain->u.u_s.sym = sym;
+  SYMBOL_PENDING_EXPANSIONS (sym)++;
+}
+
 /*--------------------------------------------------------------------.
 | This function allows gathering input from multiple locations,              |
 | rather than copying everything consecutively onto the input stack.  |
@@ -442,6 +486,7 @@ push_token (token_data *token, int level, bool inuse)
       chain->u.u_s.str = TOKEN_DATA_TEXT (token);
       chain->u.u_s.len = TOKEN_DATA_LEN (token);
       chain->u.u_s.level = level;
+      chain->u.u_s.sym = NULL;
       adjust_refcount (level, true);
       inuse = true;
     }
@@ -454,7 +499,8 @@ push_token (token_data *token, int level, bool inuse)
          src_chain = src_chain->next;
          continue;
        }
-      if (level == -1)
+      if (level == -1
+         && (src_chain->type != CHAIN_STR || !src_chain->u.u_s.sym))
        {
          /* Nothing to copy, since link already lives on obstack.  */
          assert (src_chain->type != CHAIN_STR
@@ -466,7 +512,8 @@ push_token (token_data *token, int level, bool inuse)
          /* Allow inlining the final link with subsequent text.  */
          if (!src_chain->next && src_chain->type == CHAIN_STR
              && (src_chain->u.u_s.len <= INPUT_INLINE_THRESHOLD
-                 || (!inuse && src_chain->u.u_s.level == -1)))
+                 || (!inuse && src_chain->u.u_s.level == -1
+                     && !src_chain->u.u_s.sym)))
            {
              obstack_grow (current_input, src_chain->u.u_s.str,
                            src_chain->u.u_s.len);
@@ -478,11 +525,12 @@ push_token (token_data *token, int level, bool inuse)
                                                sizeof *chain);
          if (chain->type == CHAIN_STR && chain->u.u_s.level == -1)
            {
-             if (chain->u.u_s.len <= INPUT_INLINE_THRESHOLD || !inuse)
+             if (chain->u.u_s.len <= INPUT_INLINE_THRESHOLD
+                 || (!inuse && !chain->u.u_s.sym))
                chain->u.u_s.str = (char *) obstack_copy (current_input,
                                                          chain->u.u_s.str,
                                                          chain->u.u_s.len);
-             else
+             else if (!chain->u.u_s.sym)
                {
                  chain->u.u_s.level = level;
                  inuse = true;
@@ -499,8 +547,16 @@ push_token (token_data *token, int level, bool inuse)
          assert (!chain->u.u_a.comma && !chain->u.u_a.skip_last);
          inuse |= arg_adjust_refcount (chain->u.u_a.argv, true);
        }
-      else if (chain->type == CHAIN_STR && chain->u.u_s.level >= 0)
-       adjust_refcount (chain->u.u_s.level, true);
+      else if (chain->type == CHAIN_STR)
+       {
+         if (chain->u.u_s.level >= 0)
+           {
+             assert (!chain->u.u_s.sym);
+             adjust_refcount (chain->u.u_s.level, true);
+           }
+         else if (chain->u.u_s.sym)
+           SYMBOL_PENDING_EXPANSIONS (chain->u.u_s.sym)++;
+       }
       src_chain = src_chain->next;
     }
   return inuse;
@@ -542,6 +598,7 @@ push_quote_wrapper (void)
                                                curr_quote.len1);
       chain->u.u_s.len = curr_quote.len1;
       chain->u.u_s.level = -1;
+      chain->u.u_s.sym = NULL;
     }
 }
 
@@ -669,7 +726,17 @@ pop_input (bool cleanup)
              if (chain->u.u_s.len)
                return false;
              if (chain->u.u_s.level >= 0)
-               adjust_refcount (chain->u.u_s.level, false);
+               {
+                 assert (!chain->u.u_s.sym);
+                 adjust_refcount (chain->u.u_s.level, false);
+               }
+             else if (chain->u.u_s.sym)
+               {
+                 assert (SYMBOL_PENDING_EXPANSIONS (chain->u.u_s.sym));
+                 --SYMBOL_PENDING_EXPANSIONS (chain->u.u_s.sym);
+                 if (SYMBOL_DELETED (chain->u.u_s.sym))
+                   free_symbol (chain->u.u_s.sym);
+               }
              break;
            case CHAIN_FUNC:
               if (chain->u.func)
@@ -895,7 +962,17 @@ next_buffer (size_t *len, bool allow_quote)
                      return chain->u.u_s.str;
                    }
                  if (chain->u.u_s.level >= 0)
-                   adjust_refcount (chain->u.u_s.level, false);
+                   {
+                     assert (!chain->u.u_s.sym);
+                     adjust_refcount (chain->u.u_s.level, false);
+                   }
+                 else if (chain->u.u_s.sym)
+                   {
+                     assert (SYMBOL_PENDING_EXPANSIONS (chain->u.u_s.sym));
+                     --SYMBOL_PENDING_EXPANSIONS (chain->u.u_s.sym);
+                     if (SYMBOL_DELETED (chain->u.u_s.sym))
+                       free_symbol (chain->u.u_s.sym);
+                   }
                  break;
                case CHAIN_FUNC:
                  if (chain->u.func)
@@ -1168,7 +1245,17 @@ next_char_1 (bool allow_quote, bool allow_argv)
                      return to_uchar (*chain->u.u_s.str++);
                    }
                  if (chain->u.u_s.level >= 0)
-                   adjust_refcount (chain->u.u_s.level, false);
+                   {
+                     assert (!chain->u.u_s.sym);
+                     adjust_refcount (chain->u.u_s.level, false);
+                   }
+                 else if (chain->u.u_s.sym)
+                   {
+                     assert (SYMBOL_PENDING_EXPANSIONS (chain->u.u_s.sym));
+                     --SYMBOL_PENDING_EXPANSIONS (chain->u.u_s.sym);
+                     if (SYMBOL_DELETED (chain->u.u_s.sym))
+                       free_symbol (chain->u.u_s.sym);
+                   }
                  break;
                case CHAIN_FUNC:
                  if (chain->u.func)
@@ -1324,13 +1411,24 @@ append_quote_token (struct obstack *obs, token_data *td)
 
   /* Speed consideration - for short enough tokens, the speed and
      memory overhead of parsing another INPUT_CHAIN link outweighs the
-     time to inline the token text.  */
+     time to inline the token text.  Also, if the quoted string does
+     not live in a back-reference, it must be copied.  */
   if (src_chain->type == CHAIN_STR
       && src_chain->u.u_s.len <= INPUT_INLINE_THRESHOLD)
     {
-      assert (src_chain->u.u_s.level >= 0);
       obstack_grow (obs, src_chain->u.u_s.str, src_chain->u.u_s.len);
-      adjust_refcount (src_chain->u.u_s.level, false);
+      if (src_chain->u.u_s.level >= 0)
+       {
+         assert (!src_chain->u.u_s.sym);
+         adjust_refcount (src_chain->u.u_s.level, false);
+       }
+      else if (src_chain->u.u_s.sym)
+       {
+         assert (SYMBOL_PENDING_EXPANSIONS (src_chain->u.u_s.sym));
+         --SYMBOL_PENDING_EXPANSIONS (src_chain->u.u_s.sym);
+         if (SYMBOL_DELETED (src_chain->u.u_s.sym))
+           free_symbol (src_chain->u.u_s.sym);
+       }
       return;
     }
 
diff --git a/src/m4.c b/src/m4.c
index 23a4209..57b3be4 100644
--- a/src/m4.c
+++ b/src/m4.c
@@ -623,7 +623,7 @@ main (int argc, char *const *argv, char *const *envp)
            const char *value = strchr (defines->arg, '=');
            size_t len = value ? value - defines->arg : strlen (defines->arg);
            define_user_macro (defines->arg, len, value ? value + 1 : "",
-                              value ? SIZE_MAX : 0, SYMBOL_INSERT);
+                              value ? SIZE_MAX : 0, SYMBOL_INSERT, 0);
          }
          break;
 
diff --git a/src/m4.h b/src/m4.h
index 89fb294..3305fc2 100644
--- a/src/m4.h
+++ b/src/m4.h
@@ -98,6 +98,7 @@ typedef struct string_pair string_pair;
 /* These must come first.  */
 typedef struct token_data token_data;
 typedef struct macro_arguments macro_arguments;
+typedef struct symbol symbol;
 typedef void builtin_func (struct obstack *, int, macro_arguments *);
 
 /* Gnulib's stdbool doesn't work with bool bitfields.  For nicer
@@ -261,6 +262,7 @@ struct token_chain
          const char *str;      /* Pointer to text.  */
          size_t len;           /* Remaining length of str.  */
          int level;            /* Expansion level of link content, or -1.  */
+         symbol *sym;          /* Symbol that owns str, or NULL.  */
        }
       u_s;
       builtin_func *func;              /* Builtin token from defn.  */
@@ -357,6 +359,7 @@ void append_macro (struct obstack *, builtin_func *, 
token_chain **,
                   token_chain **);
 void push_macro (struct obstack *, builtin_func *);
 struct obstack *push_string_init (const char *, int);
+void push_defn (symbol *);
 bool push_token (token_data *, int, bool);
 void push_quote_wrapper (void);
 void push_string_finish (void);
@@ -440,12 +443,16 @@ struct symbol
 #define SYMBOL_NAME(S)         ((S)->name)
 #define SYMBOL_NAME_LEN(S)     ((S)->len)
 #define SYMBOL_TYPE(S)         (TOKEN_DATA_TYPE (&(S)->data))
-#define SYMBOL_TEXT(S)         (TOKEN_DATA_TEXT (&(S)->data))
-#define SYMBOL_TEXT_LEN(S)     (TOKEN_DATA_LEN (&(S)->data))
+
+/* Only safe when SYMBOL_TYPE(S) == TOKEN_TEXT:  */
+#define SYMBOL_TEXT(S)                 (TOKEN_DATA_TEXT (&(S)->data))
+#define SYMBOL_TEXT_LEN(S)             (TOKEN_DATA_LEN (&(S)->data))
+#define SYMBOL_TEXT_QUOTE_AGE(S)       (TOKEN_DATA_QUOTE_AGE (&(S)->data))
+
+/* Only safe when SYMBOL_TYPE(S) == TOKEN_FUNC:  */
 #define SYMBOL_FUNC(S)         (TOKEN_DATA_FUNC (&(S)->data))
 
 typedef enum symbol_lookup symbol_lookup;
-typedef struct symbol symbol;
 typedef void hack_symbol (symbol *, void *);
 
 #define HASHMAX 509            /* default, overridden by -Hsize */
@@ -468,6 +475,7 @@ size_t adjust_refcount (int, bool);
 bool arg_adjust_refcount (macro_arguments *, bool);
 unsigned int arg_argc (macro_arguments *);
 const call_info *arg_info (macro_arguments *);
+unsigned int arg_quote_age (macro_arguments *);
 token_data_type arg_type (macro_arguments *, unsigned int);
 const char *arg_text (macro_arguments *, unsigned int, bool);
 bool arg_equal (macro_arguments *, unsigned int, unsigned int);
@@ -529,7 +537,7 @@ void define_builtin (const char *, size_t, const builtin *, 
symbol_lookup);
 void set_macro_sequence (const char *);
 void free_regex (void);
 void define_user_macro (const char *, size_t, const char *, size_t,
-                       symbol_lookup);
+                       symbol_lookup, unsigned int);
 void undivert_all (void);
 void expand_user_macro (struct obstack *, symbol *, int, macro_arguments *);
 void m4_placeholder (struct obstack *, int, macro_arguments *);
diff --git a/src/macro.c b/src/macro.c
index 4a221bd..4d16f76 100644
--- a/src/macro.c
+++ b/src/macro.c
@@ -678,6 +678,7 @@ expand_macro (symbol *sym)
   /* Cleanup.  */
   argv->info = NULL;
   --expansion_level;
+  assert (SYMBOL_PENDING_EXPANSIONS (sym));
   --SYMBOL_PENDING_EXPANSIONS (sym);
 
   if (SYMBOL_DELETED (sym))
@@ -758,7 +759,23 @@ arg_adjust_refcount (macro_arguments *argv, bool increase)
                {
                case CHAIN_STR:
                  if (chain->u.u_s.level >= 0)
-                   adjust_refcount (chain->u.u_s.level, increase);
+                   {
+                     assert (!chain->u.u_s.sym);
+                     adjust_refcount (chain->u.u_s.level, increase);
+                   }
+                 else if (chain->u.u_s.sym)
+                   {
+                     symbol *sym = chain->u.u_s.sym;
+                     if (increase)
+                       SYMBOL_PENDING_EXPANSIONS (sym)++;
+                     else
+                       {
+                         assert (SYMBOL_PENDING_EXPANSIONS (sym));
+                         --SYMBOL_PENDING_EXPANSIONS (sym);
+                         if (SYMBOL_DELETED (sym))
+                           free_symbol (sym);
+                       }
+                   }
                  break;
                case CHAIN_FUNC:
                  break;
@@ -867,6 +884,15 @@ arg_info (macro_arguments *argv)
   return argv->info;
 }
 
+/* Given ARGV, return the quote age in effect when argument collection
+   completed, or zero if all arguments do not have the same quote
+   age.  */
+unsigned int
+arg_quote_age (macro_arguments *argv)
+{
+  return argv->quote_age;
+}
+
 /* Given ARGV, return the type of argument ARG.  Arg 0 is always text,
    and indices beyond argc are likewise treated as text.  */
 token_data_type


hooks/post-receive
--
GNU M4 source repository




reply via email to

[Prev in Thread] Current Thread [Next in Thread]