This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [PATCH] D: Support looking up symbols in the current and imported modules


Iain Buclaw <ibuclaw@gdcproject.org> writes:
> Hi,
>
> D has the notion of importing modules, whether it is public, private,
> or static; basic, selective, or renamed.
>
> This adds support for looking up symbols in both the current and
> imported modules so that it is not a necessity to use the qualified
> name every time.
>
> Example:
>
> module A;
>
> import B;  // Basic import
> import R = C;  // Renamed import
> import D : funD;  // Selective import
> import E : funR = funE;  // Renamed selective import
>
> void funA()
> {
> // <- Breakpoint here
> }
>
> From the given breakpoint, the following should work in:
> - All symbols in module 'A' (our current module) can be looked up by name.
> - All symbols in module 'B' can be looked up by name.
> - All symbols in module 'C' can be looked up through it's alias R.name.
> - Only 'funD' in module 'D' can be looked up by name.
> - Only 'funE' in module 'E' can be looked up through it's alias 'funR'.
> - All fully qualified symbol names can be looked up.
>
>
> The implementation of this itself is mostly borrowed from
> cp-namespace.c, but differs in the follow ways:
> - The separator for modules is a single dot '.'
> - Renamed selective imports need special handling for D.
>
>
> This has a dependency on dwarf2read.c being able to handle language_d
> when reading/parsing module/imported declaration symbols, which has
> been raised as a separate patch.
>
> Regards
> Iain
>
> ---
> 2015-07-14  Iain Buclaw  <ibuclaw@gdcproject.org>
>
> 	* Makefile.in (SFILES): Add d-namespace.c.
> 	(COMMON_OBS): Add d-namespace.o.
> 	* d-lang.c (d_language_defn): Use d_lookup_symbol_nonlocal as the
> 	la_lookup_symbol_nonlocal callback function pointer.
> 	* d-lang.h (d_lookup_symbol_nonlocal): New declaration.
> 	(d_lookup_nested_symbol): New declaration.
> 	* d-namespace.c: New file.

Hi.
I didn't study this too much for d-language correctness.
[If there are issues I'm happy to let them get addressed
in subsequent patches.]
LGTM with the nits below fixed.

>
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -835,7 +835,7 @@ SFILES = ada-exp.y ada-lang.c ada-typeprint.c ada-valprint.c ada-tasks.c \
>  	charset.c common/cleanups.c cli-out.c coffread.c coff-pe-read.c \
>  	complaints.c completer.c continuations.c corefile.c corelow.c \
>  	cp-abi.c cp-support.c cp-namespace.c cp-valprint.c \
> -	d-exp.y d-lang.c d-valprint.c \
> +	d-exp.y d-lang.c d-namespace.c d-valprint.c \
>  	cp-name-parser.y \
>  	dbxread.c demangle.c dictionary.c disasm.c doublest.c \
>  	dtrace-probe.c dummy-frame.c \
> @@ -1070,7 +1070,7 @@ COMMON_OBS = $(DEPFILES) $(CONFIG_OBS) $(YYOBJ) \
>  	frame-base.o \
>  	inline-frame.o \
>  	gnu-v2-abi.o gnu-v3-abi.o cp-abi.o cp-support.o \
> -	cp-namespace.o \
> +	cp-namespace.o d-namespace.o \
>  	reggroups.o \
>  	trad-frame.o \
>  	tramp-frame.o \
> --- a/gdb/d-lang.c
> +++ b/gdb/d-lang.c
> @@ -214,7 +214,7 @@ static const struct language_defn d_language_defn =
>    default_read_var_value,	/* la_read_var_value */
>    NULL,				/* Language specific skip_trampoline.  */
>    "this",
> -  basic_lookup_symbol_nonlocal, 
> +  d_lookup_symbol_nonlocal,
>    basic_lookup_transparent_type,
>    d_demangle,			/* Language specific symbol demangler.  */
>    NULL,				/* Language specific
> --- a/gdb/d-lang.h
> +++ b/gdb/d-lang.h
> @@ -68,6 +68,16 @@ extern char *d_demangle (const char *mangled, int options);
>  
>  extern const struct builtin_d_type *builtin_d_type (struct gdbarch *);
>  
> +/* Defined in d-namespace.c  */
> +
> +extern struct symbol *d_lookup_symbol_nonlocal (const struct language_defn *,
> +						const char *,
> +						const struct block *,
> +						const domain_enum);
> +
> +extern struct symbol *d_lookup_nested_symbol (struct type *, const char *,
> +					      const struct block *);
> +
>  /* Defined in d-valprint.c  */
>  
>  extern void d_val_print (struct type *type, const gdb_byte *valaddr,
> --- /dev/null
> +++ b/gdb/d-namespace.c
> @@ -0,0 +1,574 @@
> +/* Helper routines for D support in GDB.
> +
> +   Copyright (C) 2014-2015 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#include "defs.h"
> +#include "symtab.h"
> +#include "block.h"
> +#include "language.h"
> +#include "d-lang.h"
> +#include "cp-support.h"

Is including cp-support.h necessary?

> +#include "gdb_obstack.h"
> +
> +static struct symbol *lookup_module_scope (const char *,
> +					   const struct block *,
> +					   const domain_enum,
> +					   const char *, int);
> +
> +static struct symbol *lookup_symbol_file (const char *,
> +					  const struct block *,
> +					  const domain_enum, int);
> +
> +
> +/* This returns the length of first component of NAME, which should be
> +   the demangled name of a D variable/function/method/etc.
> +   Specifically, it returns the index of the first dot forming the
> +   boundary of the first component: so, given 'A.foo' or 'A.B.foo'
> +   it returns the 1, and given 'foo', it returns 0.  */
> +
> +/* The character in NAME indexed by the return value is guaranteed to
> +   always be either '.' or '\0'.  */
> +
> +static unsigned int
> +d_find_first_component (const char *name)
> +{
> +  unsigned int index = 0;
> +
> +  for (;; ++index)
> +    {
> +      if (name[index] == '.' || name[index] == '\0')
> +	return index;
> +    }
> +}
> +
> +/* Look up NAME in the D module MODULE.  Other arguments are as in
> +   d_lookup_symbol_nonlocal.  If SEARCH is non-zero, search through
> +   base classes for a matching symbol.  */
> +
> +static struct symbol *
> +d_lookup_symbol_in_module (const char *module, const char *name,
> +			   const struct block *block,
> +			   const domain_enum domain, int search)
> +{
> +  if (module[0] == '\0')
> +    {
> +      return lookup_symbol_file (name, block, domain, search);
> +    }
> +  else
> +    {
> +      char *concatenated_name = alloca (strlen (module)
> +					+ strlen (name) + 2);
> +
> +      strcpy (concatenated_name, module);
> +      strcat (concatenated_name, ".");
> +      strcat (concatenated_name, name);
> +      return lookup_symbol_file (concatenated_name, block,
> +				 domain, search);
> +    }
> +}
> +
> +/* Lookup NAME at module scope.  SCOPE is the module that the current
> +   function is defined within; only consider modules whose length is at
> +   least SCOPE_LEN.  Other arguments are as in d_lookup_symbol_nonlocal.
> +
> +   For example, if we're within a function A.B.f and looking for a
> +   symbol x, this will get called with NAME = "x", SCOPE = "A.B", and
> +   SCOPE_LEN = 0.  It then calls itself with NAME and SCOPE the same,
> +   but with SCOPE_LEN = 1.  And then it calls itself with NAME and
> +   SCOPE the same, but with SCOPE_LEN = 4.  This third call looks for
> +   "A.B.x"; if it doesn't find it, then the second call looks for "A.x",
> +   and if that call fails, then the first call looks for "x".  */
> +
> +static struct symbol *
> +lookup_module_scope (const char *name, const struct block *block,
> +		     const domain_enum domain, const char *scope,
> +		     int scope_len)
> +{
> +  char *module;
> +
> +  if (scope[scope_len] != '\0')
> +    {
> +      /* Recursively search for names in child modules first.  */
> +
> +      struct symbol *sym;
> +      int new_scope_len = scope_len;
> +
> +      /* If the current scope is followed by ".", skip past that.  */
> +      if (new_scope_len != 0)
> +	{
> +	  gdb_assert (scope[new_scope_len] == '.');
> +	  new_scope_len++;
> +	}
> +      new_scope_len += d_find_first_component (scope + new_scope_len);
> +      sym = lookup_module_scope (name, block, domain,
> +				 scope, new_scope_len);
> +      if (sym != NULL)
> +	return sym;
> +    }
> +
> +  /* Okay, we didn't find a match in our children, so look for the
> +     name in the current module.  */
> +
> +  module = alloca (scope_len + 1);
> +  strncpy (module, scope, scope_len);
> +  module[scope_len] = '\0';
> +  return d_lookup_symbol_in_module (module, name,
> +				    block, domain, 1);
> +}
> +
> +/* If NAME is the fully-qualified name of a D function/variable/method,
> +   this returns the length of its entire prefix: all of the modules and
> +   classes that make up its name.  Given 'A.foo', it returns 1, given
> +   'A.B.foo', it returns 4, given 'foo', it returns 0.  */
> +
> +static unsigned int
> +d_entire_prefix_len (const char *name)
> +{
> +  unsigned int current_len = d_find_first_component (name);
> +  unsigned int previous_len = 0;
> +
> +  while (name[current_len] != '\0')
> +    {
> +      gdb_assert (name[current_len] == '.');
> +      previous_len = current_len;
> +      /* Skip the '.'  */
> +      current_len++;
> +      current_len += d_find_first_component (name + current_len);
> +    }
> +
> +  return previous_len;
> +}
> +
> +/* Search through the base classes of PARENT_TYPE for a symbol named
> +   NAME in block BLOCK.  */
> +
> +static struct symbol *
> +find_symbol_in_baseclass (struct type *parent_type, const char *name,
> +			  const struct block *block)
> +{
> +  int i;
> +  struct symbol *sym;
> +  struct cleanup *cleanup;
> +  char *concatenated_name;
> +
> +  sym = NULL;
> +  concatenated_name = NULL;
> +  cleanup = make_cleanup (free_current_contents, &concatenated_name);
> +  for (i = 0; i < TYPE_N_BASECLASSES (parent_type); ++i)
> +    {
> +      size_t len;
> +      struct type *base_type = TYPE_BASECLASS (parent_type, i);
> +      const char *base_name = TYPE_BASECLASS_NAME (parent_type, i);
> +
> +      if (base_name == NULL)
> +	continue;
> +
> +      /* Search this particular base class.  */
> +      sym = d_lookup_symbol_in_module (base_name, name, block,
> +				       VAR_DOMAIN, 0);
> +      if (sym != NULL)
> +	break;
> +
> +      /* Now search all static file-level symbols.  We have to do this for
> +	 things like typedefs in the class.  First search in this symtab,
> +	 what we want is possibly there.  */
> +      len = strlen (base_name) + strlen (name) + 2;
> +      concatenated_name = xrealloc (concatenated_name, len);
> +      xsnprintf (concatenated_name, len, "%s.%s", base_name, name);
> +      sym = lookup_symbol_in_static_block (concatenated_name, block,
> +					   VAR_DOMAIN);
> +      if (sym != NULL)
> +	break;
> +
> +      /* Nope.  We now have to search all static blocks in all objfiles,
> +	 even if block != NULL, because there's no guarantees as to which
> +	 symtab the symbol we want is in.  */
> +      sym = lookup_static_symbol (concatenated_name, VAR_DOMAIN);
> +      if (sym != NULL)
> +	break;
> +
> +      /* If this class has base classes, search them next.  */
> +      base_type = check_typedef (base_type);
> +      if (TYPE_N_BASECLASSES (base_type) > 0)
> +	{
> +	  sym = find_symbol_in_baseclass (base_type, name, block);
> +	  if (sym != NULL)
> +	    break;
> +	}
> +    }
> +
> +  do_cleanups (cleanup);
> +  return sym;
> +}
> +
> +/* Look up a symbol named NESTED_NAME that is nested inside the D
> +   class or module given by PARENT_TYPE, from within the context
> +   given by BLOCK.  Return NULL if there is no such nested type.  */
> +
> +struct symbol *

Make static.
[Or is there another patch that will use this function?
E.g., c-exp.y also uses cp_lookup_nested_symbol.]

> +d_lookup_nested_symbol (struct type *parent_type,
> +			const char *nested_name,
> +			const struct block *block)
> +{
> +  /* type_name_no_tag_required provides better error reporting using the
> +     original type.  */
> +  struct type *saved_parent_type = parent_type;
> +
> +  parent_type = check_typedef (parent_type);
> +
> +  switch (TYPE_CODE (parent_type))
> +    {
> +    case TYPE_CODE_STRUCT:
> +    case TYPE_CODE_UNION:
> +    case TYPE_CODE_MODULE:
> +	{
> +	  int size;
> +	  const char *parent_name = type_name_no_tag_or_error (saved_parent_type);
> +	  struct symbol *sym
> +	    = d_lookup_symbol_in_module (parent_name, nested_name,
> +					 block, VAR_DOMAIN, 0);
> +	  char *concatenated_name;
> +
> +	  if (sym != NULL)
> +	    return sym;
> +
> +	  /* Now search all static file-level symbols.  We have to do this
> +	     for things like typedefs in the class.  We do not try to
> +	     guess any imported module as even the fully specified
> +	     module search is already not D compliant and more assumptions
> +	     could make it too magic.  */
> +	  size = strlen (parent_name) + strlen (nested_name) + 2;
> +	  concatenated_name = alloca (size);
> +
> +	  xsnprintf (concatenated_name, size, "%s.%s",
> +		     parent_name, nested_name);
> +
> +	  sym = lookup_static_symbol (concatenated_name, VAR_DOMAIN);
> +	  if (sym != NULL)
> +	    return sym;
> +
> +	  /* If no matching symbols were found, try searching any
> +	     base classes.  */
> +	  return find_symbol_in_baseclass (parent_type, nested_name, block);
> +	}
> +
> +    case TYPE_CODE_FUNC:
> +    case TYPE_CODE_METHOD:
> +      return NULL;
> +
> +    default:
> +      internal_error (__FILE__, __LINE__,
> +		      _("d_lookup_nested_symbol called "
> +			"on a non-aggregate type."));

gdb_assert_not_reached would be simpler.

> +    }
> +}
> +
> +/* Look up NAME in BLOCK's static block and in global blocks.
> +   If SEARCH is non-zero, search through base classes for a matching
> +   symbol.  Other arguments are as in d_lookup_symbol_nonlocal.  */
> +
> +static struct symbol *
> +lookup_symbol_file (const char *name, const struct block *block,
> +		    const domain_enum domain, int search)
> +{
> +  struct symbol *sym = NULL;
> +
> +  sym = lookup_symbol_in_static_block (name, block, domain);
> +  if (sym != NULL)
> +    return sym;
> +
> +  sym = lookup_global_symbol (name, block, domain);
> +
> +  if (sym != NULL)
> +    return sym;
> +
> +  if (search)
> +    {
> +      char *classname, *nested;
> +      unsigned int prefix_len;
> +      struct cleanup *cleanup;
> +      struct symbol *class_sym;
> +
> +      /* A simple lookup failed.  Check if the symbol was defined in
> +	 a base class.  */
> +
> +      cleanup = make_cleanup (null_cleanup, NULL);
> +
> +      /* Find the name of the class and the name of the method,
> +	 variable, etc.  */
> +      prefix_len = d_entire_prefix_len (name);
> +
> +      /* If no prefix was found, search "this".  */
> +      if (prefix_len == 0)
> +	{
> +	  struct type *type;
> +	  struct symbol *this;
> +
> +	  this = lookup_language_this (language_def (language_d), block);
> +	  if (this == NULL)
> +	    {
> +	      do_cleanups (cleanup);
> +	      return NULL;
> +	    }
> +
> +	  type = check_typedef (TYPE_TARGET_TYPE (SYMBOL_TYPE (this)));
> +	  classname = xstrdup (TYPE_NAME (type));
> +	  nested = xstrdup (name);
> +	}
> +      else
> +	{
> +	  /* The class name is everything up to and including PREFIX_LEN.  */
> +	  classname = savestring (name, prefix_len);
> +
> +	  /* The rest of the name is everything else past the initial scope
> +	     operator.  */
> +	  nested = xstrdup (name + prefix_len + 1);
> +	}
> +
> +      /* Add cleanups to free memory for these strings.  */
> +      make_cleanup (xfree, classname);
> +      make_cleanup (xfree, nested);
> +
> +      /* Lookup a class named CLASSNAME.  If none is found, there is nothing
> +	 more that can be done.  */
> +      class_sym = lookup_global_symbol (classname, block, domain);
> +      if (class_sym == NULL)
> +	{
> +	  do_cleanups (cleanup);
> +	  return NULL;
> +	}
> +
> +      /* Look for a symbol named NESTED in this class.  */
> +      sym = d_lookup_nested_symbol (SYMBOL_TYPE (class_sym), nested, block);
> +      do_cleanups (cleanup);
> +    }
> +
> +  return sym;
> +}
> +
> +/* Used for cleanups to reset the "searched" flag incase
> +   of an error.  */
> +
> +static void
> +reset_directive_searched (void *data)
> +{
> +  struct using_direct *direct = data;
> +  direct->searched = 0;
> +}
> +
> +/* Search for NAME by applying all import statements belonging to
> +   BLOCK which are applicable in SCOPE.
> +
> +   If SEARCH_PARENTS the search will include imports which are
> +   applicable in parents of SCOPE.
> +   Example:
> +
> +     module A;
> +     import X;
> +     void B() {
> +       import Y;
> +     }
> +
> +   If SCOPE is "A.B" and SEARCH_PARENTS is true, the imports of
> +   modules X and Y will be considered.  If SEARCH_PARENTS is false
> +   only the import of Y is considered.  */
> +
> +static struct symbol *
> +d_lookup_symbol_imports (const char *scope, const char *name,
> +			 const struct block *block,
> +			 const domain_enum domain,
> +			 const int search_parents)
> +{
> +  struct using_direct *current;
> +  struct symbol *sym = NULL;
> +  int directive_match;
> +  struct cleanup *searched_cleanup;
> +
> +  /* First, try to find the symbol in the given module.  */
> +  sym = d_lookup_symbol_in_module (scope, name, block, domain, 1);
> +
> +  if (sym != NULL)
> +    return sym;
> +
> +  /* Go through the using directives.  If any of them add new names to
> +     the module we're searching in, see if we can find a match by
> +     applying them.  */
> +
> +  for (current = block_using (block);
> +       current != NULL;
> +       current = current->next)
> +    {
> +      const char **excludep;
> +      int len = strlen (current->import_dest);
> +
> +      directive_match = (search_parents
> +			 ? (strncmp (scope, current->import_dest, len) == 0
> +			    && (len == 0
> +				|| scope[len] == '.'
> +				|| scope[len] == '\0'))
> +			 : strcmp (scope, current->import_dest) == 0);
> +
> +      /* If the import destination is the current scope or one of its
> +	 ancestors then it is applicable.  */
> +      if (directive_match && !current->searched)
> +	{
> +	  /* Mark this import as searched so that the recursive call
> +	     does not search it again.  */
> +	  current->searched = 1;
> +	  searched_cleanup = make_cleanup (reset_directive_searched,
> +					   current);
> +
> +	  /* If there is an import of a single declaration, compare the
> +	     imported declaration (after optional renaming by its alias)
> +	     with the sought out name.  If there is a match pass
> +	     current->import_src as MODULE to direct the search towards
> +	     the imported module.  */
> +	  if (current->declaration
> +	      && strcmp (name, current->alias
> +			 ? current->alias : current->declaration) == 0)
> +	    sym = d_lookup_symbol_in_module (current->import_src,
> +					     current->declaration,
> +					     block, domain, 1);
> +
> +	  /* If a symbol was found or this import statement was an import
> +	     declaration, the search of this import is complete.  */
> +	  if (sym != NULL || current->declaration)
> +	    {
> +	      current->searched = 0;
> +	      discard_cleanups (searched_cleanup);
> +
> +	      if (sym != NULL)
> +		return sym;
> +
> +	      continue;
> +	    }
> +
> +	  /* Do not follow CURRENT if NAME matches its EXCLUDES.  */
> +	  for (excludep = current->excludes; *excludep; excludep++)
> +	    if (strcmp (name, *excludep) == 0)
> +	      break;
> +	  if (*excludep)
> +	    {
> +	      discard_cleanups (searched_cleanup);
> +	      continue;
> +	    }
> +
> +	  /* If the import statement is creating an alias.  */
> +	  if (current->alias != NULL)
> +	    {
> +	      if (strcmp (name, current->alias) == 0)
> +		{
> +		  /* If the alias matches the sought name.  Pass
> +		     current->import_src as the NAME to direct the
> +		     search towards the aliased module.  */
> +		  sym = lookup_module_scope (current->import_src, block,
> +					     domain, scope, 0);
> +		}
> +	      else
> +		{
> +		  /* If the alias matches the first component of the
> +		     sought name, pass current->import_src as MODULE
> +		     to direct the search, skipping over the aliased
> +		     component in NAME.  */
> +		  int name_scope = d_find_first_component (name);
> +
> +		  if (name[name_scope] != '\0'
> +		      && strncmp (name, current->alias, name_scope) == 0)
> +		    {
> +		      /* Skip the '.'  */
> +		      name_scope++;
> +		      sym = d_lookup_symbol_imports (current->import_src,
> +						     name + name_scope,
> +						     block, domain, 0);
> +		    }
> +		}
> +	    }
> +	  else
> +	    {
> +	      /* If this import statement creates no alias, pass
> +		 current->import_src as MODULE to direct the search
> +		 towards the imported module.  */
> +	      sym = d_lookup_symbol_imports (current->import_src,
> +					     name, block, domain, 0);
> +	    }
> +	  current->searched = 0;
> +	  discard_cleanups (searched_cleanup);
> +
> +	  if (sym != NULL)
> +	    return sym;
> +	}
> +    }
> +
> +  return NULL;
> +}
> +
> +/* Searches for NAME in the current module, and by applying relevant
> +   import statements belonging to BLOCK and its parents.  SCOPE is the
> +   module scope of the context in which the search is being evaluated.  */
> +
> +static struct symbol*
> +d_lookup_symbol_module (const char *scope, const char *name,
> +			const struct block *block,
> +			const domain_enum domain)
> +{
> +  struct symbol *sym;
> +
> +  /* First, try to find the symbol in the given module.  */
> +  sym = d_lookup_symbol_in_module (scope, name,
> +				   block, domain, 1);
> +  if (sym != NULL)
> +    return sym;
> +
> +  /* Search for name in modules imported to this and parent
> +     blocks.  */
> +  while (block != NULL)
> +    {
> +      sym = d_lookup_symbol_imports (scope, name, block, domain, 1);
> +
> +      if (sym)
> +	return sym;
> +
> +      block = BLOCK_SUPERBLOCK (block);
> +    }
> +
> +  return NULL;
> +}
> +
> +/* The D-specific version of name lookup for static and global names
> +   This makes sure that names get looked for in all modules that are
> +   in scope.  NAME is the natural name of the symbol that we're looking
> +   looking for, BLOCK is the block that we're searching within, DOMAIN
> +   says what kind of symbols we're looking for, and if SYMTAB is non-NULL,
> +   we should store the symtab where we found the symbol in it.  */
> +
> +struct symbol *
> +d_lookup_symbol_nonlocal (const struct language_defn *langdef,
> +			  const char *name,
> +			  const struct block *block,
> +			  const domain_enum domain)
> +{
> +  struct symbol *sym;
> +  const char *scope = block_scope (block);
> +
> +  sym = lookup_module_scope (name, block, domain, scope, 0);
> +  if (sym != NULL)
> +    return sym;
> +
> +  return d_lookup_symbol_module (scope, name, block, domain);
> +}
> +


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]