This file is declare.def, from which is created declare.c.
It implements the builtins "declare" and "local" in Bash.

Copyright (C) 1987-2012 Free Software Foundation, Inc.

This file is part of GNU Bash, the Bourne Again SHell.

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

Bash 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 Bash.  If not, see <http://www.gnu.org/licenses/>.

$PRODUCES declare.c

$BUILTIN declare
$FUNCTION declare_builtin
$SHORT_DOC declare [-aAfFgilnrtux] [-p] [name[=value] ...]
Set variable values and attributes.

Declare variables and give them attributes.  If no NAMEs are given,
display the attributes and values of all variables.

Options:
  -f	restrict action or display to function names and definitions
  -F	restrict display to function names only (plus line number and
	source file when debugging)
  -g	create global variables when used in a shell function; otherwise
	ignored
  -p	display the attributes and value of each NAME

Options which set attributes:
  -a	to make NAMEs indexed arrays (if supported)
  -A	to make NAMEs associative arrays (if supported)
  -i	to make NAMEs have the `integer' attribute
  -l	to convert NAMEs to lower case on assignment
  -n	make NAME a reference to the variable named by its value
  -r	to make NAMEs readonly
  -t	to make NAMEs have the `trace' attribute
  -u	to convert NAMEs to upper case on assignment
  -x	to make NAMEs export

Using `+' instead of `-' turns off the given attribute.

Variables with the integer attribute have arithmetic evaluation (see
the `let' command) performed when the variable is assigned a value.

When used in a function, `declare' makes NAMEs local, as with the `local'
command.  The `-g' option suppresses this behavior.

Exit Status:
Returns success unless an invalid option is supplied or a variable
assignment error occurs.
$END

$BUILTIN typeset
$FUNCTION declare_builtin
$SHORT_DOC typeset [-aAfFgilrtux] [-p] name[=value] ...
Set variable values and attributes.

Obsolete.  See `help declare'.
$END

#include <config.h>

#if defined (HAVE_UNISTD_H)
#  ifdef _MINIX
#    include <sys/types.h>
#  endif
#  include <unistd.h>
#endif

#include <stdio.h>

#include "../bashansi.h"
#include "../bashintl.h"

#include "../shell.h"
#include "common.h"
#include "builtext.h"
#include "bashgetopt.h"

extern int array_needs_making;
extern int posixly_correct;

static int declare_internal __P((register WORD_LIST *, int));

/* Declare or change variable attributes. */
int
declare_builtin (list)
     register WORD_LIST *list;
{
  return (declare_internal (list, 0));
}

$BUILTIN local
$FUNCTION local_builtin
$SHORT_DOC local [option] name[=value] ...
Define local variables.

Create a local variable called NAME, and give it VALUE.  OPTION can
be any option accepted by `declare'.

Local variables can only be used within a function; they are visible
only to the function where they are defined and its children.

Exit Status:
Returns success unless an invalid option is supplied, a variable
assignment error occurs, or the shell is not executing a function.
$END
int
local_builtin (list)
     register WORD_LIST *list;
{
  if (variable_context)
    return (declare_internal (list, 1));
  else
    {
      builtin_error (_("can only be used in a function"));
      return (EXECUTION_FAILURE);
    }
}

#if defined (ARRAY_VARS)
#  define DECLARE_OPTS	"+acfgilnprtuxAF"
#else
#  define DECLARE_OPTS	"+cfgilnprtuxF"
#endif

/* The workhorse function. */
static int
declare_internal (list, local_var)
     register WORD_LIST *list;
     int local_var;
{
  int flags_on, flags_off, *flags;
  int any_failed, assign_error, pflag, nodefs, opt, mkglobal, onref, offref;
  char *t, *subscript_start;
  SHELL_VAR *var, *refvar;
  FUNCTION_DEF *shell_fn;

  flags_on = flags_off = any_failed = assign_error = pflag = nodefs = mkglobal = 0;
  refvar = (SHELL_VAR *)NULL;
  reset_internal_getopt ();
  while ((opt = internal_getopt (list, DECLARE_OPTS)) != EOF)
    {
      flags = list_opttype == '+' ? &flags_off : &flags_on;

      switch (opt)
	{
	case 'a':
#if defined (ARRAY_VARS)
	  *flags |= att_array;
	  break;
#else
	  builtin_usage ();
	  return (EX_USAGE);
#endif
	case 'A':
#if defined (ARRAY_VARS)
	  *flags |= att_assoc;
	  break;
#else
	  builtin_usage ();
	  return (EX_USAGE);
#endif
	case 'p':
	  if (local_var == 0)
	    pflag++;
	  break;
        case 'F':
	  nodefs++;
	  *flags |= att_function;
	  break;
	case 'f':
	  *flags |= att_function;
	  break;
	case 'g':
	  if (flags == &flags_on)
	    mkglobal = 1;
	  break;
	case 'i':
	  *flags |= att_integer;
	  break;
	case 'n':
	  *flags |= att_nameref;
	  break;
	case 'r':
	  *flags |= att_readonly;
	  break;
	case 't':
	  *flags |= att_trace;
	  break;
	case 'x':
	  *flags |= att_exported;
	  array_needs_making = 1;
	  break;
#if defined (CASEMOD_ATTRS)
#  if defined (CASEMOD_CAPCASE)
	 case 'c':
	  *flags |= att_capcase;
	  if (flags == &flags_on)
	    flags_off |= att_uppercase|att_lowercase;
	  break;
#  endif
	case 'l':
	  *flags |= att_lowercase;
	  if (flags == &flags_on)
	    flags_off |= att_capcase|att_uppercase;
	  break;
	case 'u':
	  *flags |= att_uppercase;
	  if (flags == &flags_on)
	    flags_off |= att_capcase|att_lowercase;
	  break;
#endif /* CASEMOD_ATTRS */
	default:
	  builtin_usage ();
	  return (EX_USAGE);
	}
    }

  list = loptend;

  /* If there are no more arguments left, then we just want to show
     some variables. */
  if (list == 0)	/* declare -[aAfFirtx] */
    {
      /* Show local variables defined at this context level if this is
	 the `local' builtin. */
      if (local_var)
	{
	  register SHELL_VAR **vlist;
	  register int i;

	  vlist = all_local_variables ();

	  if (vlist)
	    {
	      for (i = 0; vlist[i]; i++)
		print_assignment (vlist[i]);

	      free (vlist);
	    }
	}
      else if (pflag && (flags_on == 0 || flags_on == att_function))
	show_all_var_attributes (flags_on == 0, nodefs);
      else if (flags_on == 0)
	return (set_builtin ((WORD_LIST *)NULL));
      else
	set_or_show_attributes ((WORD_LIST *)NULL, flags_on, nodefs);

      return (sh_chkwrite (EXECUTION_SUCCESS));
    }

  if (pflag)	/* declare -p [-aAfFirtx] name [name...] */
    {
      for (any_failed = 0; list; list = list->next)
	{
	  pflag = show_name_attributes (list->word->word, nodefs);
	  if (pflag)
	    {
	      sh_notfound (list->word->word);
	      any_failed++;
	    }
	}
      return (sh_chkwrite (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS));
    }

#define NEXT_VARIABLE() free (name); list = list->next; continue

  /* There are arguments left, so we are making variables. */
  while (list)		/* declare [-aAfFirx] name [name ...] */
    {
      char *value, *name;
      int offset, aflags;
#if defined (ARRAY_VARS)
      int making_array_special, compound_array_assign, simple_array_assign;
#endif

      name = savestring (list->word->word);
      offset = assignment (name, 0);
      aflags = 0;

      if (offset)	/* declare [-aAfFirx] name=value */
	{
	  name[offset] = '\0';
	  value = name + offset + 1;
	  if (name[offset - 1] == '+')
	    {
	      aflags |= ASS_APPEND;
	      name[offset - 1] = '\0';
	    }
	}
      else
	value = "";

      /* Do some lexical error checking on the LHS and RHS of the assignment
	 that is specific to nameref variables. */
      if (flags_on & att_nameref)
	{
	  if (valid_array_reference (name))
	    {
	      builtin_error (_("%s: reference variable cannot be an array"), name);
	      assign_error++;
	      NEXT_VARIABLE ();
	    }
	  else if (STREQ (name, value))
	    {
	      builtin_error (_("%s: nameref variable self references not allowed"), name);
	      assign_error++;
	      NEXT_VARIABLE ();
	    }
	}

#if defined (ARRAY_VARS)
      compound_array_assign = simple_array_assign = 0;
      subscript_start = (char *)NULL;
      if (t = strchr (name, '['))	/* ] */
	{
	  /* If offset != 0 we have already validated any array reference */
	  if (offset == 0 && valid_array_reference (name) == 0)
	    {
	      sh_invalidid (name);
	      assign_error++;
	      NEXT_VARIABLE ();
	    }
	  subscript_start = t;
	  *t = '\0';
	  making_array_special = 1;
	}
      else
	making_array_special = 0;
#endif

      /* If we're in posix mode or not looking for a shell function (since
	 shell function names don't have to be valid identifiers when the
	 shell's not in posix mode), check whether or not the argument is a
	 valid, well-formed shell identifier. */
      if ((posixly_correct || (flags_on & att_function) == 0) && legal_identifier (name) == 0)
	{
	  sh_invalidid (name);
	  assign_error++;
	  NEXT_VARIABLE ();
	}

      /* If VARIABLE_CONTEXT has a non-zero value, then we are executing
	 inside of a function.  This means we should make local variables,
	 not global ones. */

      /* XXX - this has consequences when we're making a local copy of a
	       variable that was in the temporary environment.  Watch out
	       for this. */
      refvar = (SHELL_VAR *)NULL;
      if (variable_context && mkglobal == 0 && ((flags_on & att_function) == 0))
	{
#if defined (ARRAY_VARS)
	  if (flags_on & att_assoc)
	    var = make_local_assoc_variable (name);
	  else if ((flags_on & att_array) || making_array_special)
	    var = make_local_array_variable (name, making_array_special);
	  else
#endif
#if 0
	  /* XXX - this doesn't work right yet. */
	  /* See below for rationale for doing this. */
	  if (flags_on & att_nameref)
	    {
	      /* See if we are trying to modify an existing nameref variable */
	      var = find_variable_last_nameref (name);
	      if (var && nameref_p (var) == 0)
		var = make_local_variable (name);
	    }
	  else if (flags_off & att_nameref)
	    {
	      var = (SHELL_VAR *)NULL;
	      /* See if we are trying to modify an existing nameref variable */
	      refvar = find_variable_last_nameref (name);
	      if (refvar && nameref_p (refvar) == 0)
		refvar = 0;
	      if (refvar)
		var = make_local_variable (nameref_cell (refvar));
	      if (var == 0)
		var = make_local_variable (name);
	    }
	  else
#endif
	    var = make_local_variable (name);
	  if (var == 0)
	    {
	      any_failed++;
	      NEXT_VARIABLE ();
	    }
	}
      else
	var = (SHELL_VAR *)NULL;

      /* If we are declaring a function, then complain about it in some way.
	 We don't let people make functions by saying `typeset -f foo=bar'. */

      /* There should be a way, however, to let people look at a particular
	 function definition by saying `typeset -f foo'. */

      if (flags_on & att_function)
	{
	  if (offset)	/* declare -f [-rix] foo=bar */
	    {
	      builtin_error (_("cannot use `-f' to make functions"));
	      free (name);
	      return (EXECUTION_FAILURE);
	    }
	  else		/* declare -f [-rx] name [name...] */
	    {
	      var = find_function (name);

	      if (var)
		{
		  if (readonly_p (var) && (flags_off & att_readonly))
		    {
		      builtin_error (_("%s: readonly function"), name);
		      any_failed++;
		      NEXT_VARIABLE ();
		    }

		  /* declare -[Ff] name [name...] */
		  if (flags_on == att_function && flags_off == 0)
		    {
#if defined (DEBUGGER)
		      if (nodefs && debugging_mode)
			{
			  shell_fn = find_function_def (var->name);
			  if (shell_fn)
			    printf ("%s %d %s\n", var->name, shell_fn->line, shell_fn->source_file);
			  else
			    printf ("%s\n", var->name);
			}
		      else
#endif /* DEBUGGER */
			{	
			  t = nodefs ? var->name
				     : named_function_string (name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL);
			  printf ("%s\n", t);
			  any_failed = sh_chkwrite (any_failed);
			}
		    }
		  else		/* declare -[fF] -[rx] name [name...] */
		    {
		      VSETATTR (var, flags_on);
		      VUNSETATTR (var, flags_off);
		    }
		}
	      else
		any_failed++;
	      NEXT_VARIABLE ();
	    }
	}
      else		/* declare -[aAirx] name [name...] */
	{
	  /* Non-null if we just created or fetched a local variable. */
	  /* Here's what ksh93 seems to do.  If we are modifying an existing
	     nameref variable, we don't follow the nameref chain past the last
	     nameref, and we set the nameref variable's value so future
	     references to that variable will return the value of the variable
	     we're assigning right now. */
	  if (var == 0 && (flags_on & att_nameref))
	    {
	      /* See if we are trying to modify an existing nameref variable */
	      var = mkglobal ? find_global_variable_last_nameref (name) : find_variable_last_nameref (name);
	      if (var && nameref_p (var) == 0)
		var = 0;
	    }
	  /* However, if we're turning off the nameref attribute on an existing
	     nameref variable, we first follow the nameref chain to the end,
	     modify the value of the variable this nameref variable references,
	     *CHANGING ITS VALUE AS A SIDE EFFECT* then turn off the nameref
	     flag *LEAVING THE NAMEREF VARIABLE'S VALUE UNCHANGED* */
	  else if (var == 0 && (flags_off & att_nameref))
	    {
	      /* See if we are trying to modify an existing nameref variable */
	      refvar = mkglobal ? find_global_variable_last_nameref (name) : find_variable_last_nameref (name);
	      if (refvar && nameref_p (refvar) == 0)
		refvar = 0;
	      if (refvar)
		var = mkglobal ? find_global_variable (nameref_cell (refvar)) : find_variable (nameref_cell (refvar));
	    }
	
	  if (var == 0)
	    var = mkglobal ? find_global_variable (name) : find_variable (name);

	  if (var == 0)
	    {
#if defined (ARRAY_VARS)
	      if (flags_on & att_assoc)
		{
		  var = make_new_assoc_variable (name);
		  if (offset == 0)
		    VSETATTR (var, att_invisible);
		}
	      else if ((flags_on & att_array) || making_array_special)
		{
		  var = make_new_array_variable (name);
		  if (offset == 0)
		    VSETATTR (var, att_invisible);
		}
	      else
#endif

	      if (offset)
		var = mkglobal ? bind_global_variable (name, "", 0) : bind_variable (name, "", 0);
	      else
		{
		  var = mkglobal ? bind_global_variable (name, (char *)NULL, 0) : bind_variable (name, (char *)NULL, 0);
		  VSETATTR (var, att_invisible);
		}
	    }
	  /* Can't take an existing array variable and make it a nameref */
	  else if ((array_p (var) || assoc_p (var)) && (flags_on & att_nameref))
	    {
	      builtin_error (_("%s: reference variable cannot be an array"), name);
	      assign_error++;
	      NEXT_VARIABLE ();
	    }

	  /* Cannot use declare +r to turn off readonly attribute. */ 
	  if (readonly_p (var) && (flags_off & att_readonly))
	    {
	      sh_readonly (name);
	      any_failed++;
	      NEXT_VARIABLE ();
	    }

	  /* Cannot use declare to assign value to readonly or noassign
	     variable. */
	  if ((readonly_p (var) || noassign_p (var)) && offset)
	    {
	      if (readonly_p (var))
		sh_readonly (name);
	      assign_error++;
	      NEXT_VARIABLE ();
	    }

#if defined (ARRAY_VARS)
	  if ((making_array_special || (flags_on & (att_array|att_assoc)) || array_p (var) || assoc_p (var)) && offset)
	    {
	      int vlen;
	      vlen = STRLEN (value);

	      if (value[0] == '(' && value[vlen-1] == ')')
		compound_array_assign = 1;
	      else
		simple_array_assign = 1;
	    }

	  /* Cannot use declare +a name or declare +A name to remove an
	     array variable. */
	  if (((flags_off & att_array) && array_p (var)) || ((flags_off & att_assoc) && assoc_p (var)))
	    {
	      builtin_error (_("%s: cannot destroy array variables in this way"), name);
	      any_failed++;
	      NEXT_VARIABLE ();
	    }

	  if ((flags_on & att_array) && assoc_p (var))
	    {
	      builtin_error (_("%s: cannot convert associative to indexed array"), name);
	      any_failed++;
	      NEXT_VARIABLE ();
	    }
	  if ((flags_on & att_assoc) && array_p (var))
	    {
	      builtin_error (_("%s: cannot convert indexed to associative array"), name);
	      any_failed++;
	      NEXT_VARIABLE ();
	    }

	  /* declare -A name[[n]] makes name an associative array variable. */
	  if (flags_on & att_assoc)
	    {
	      if (assoc_p (var) == 0)
		var = convert_var_to_assoc (var);
	    }
	  /* declare -a name[[n]] or declare name[n] makes name an indexed
	     array variable. */
	  else if ((making_array_special || (flags_on & att_array)) && array_p (var) == 0 && assoc_p (var) == 0)
	    var = convert_var_to_array (var);
#endif /* ARRAY_VARS */

	  /* XXX - we note that we are turning on nameref attribute and defer
	     setting it until the assignment has been made so we don't do an
	     inadvertent nameref lookup.  Might have to do the same thing for
	     flags_off&att_nameref. */
	  /* XXX - ksh93 makes it an error to set a readonly nameref variable
	     using a single typeset command. */
	  onref = (flags_on & att_nameref);
	  flags_on &= ~att_nameref;
	  if (array_p (var) || assoc_p (var)
		|| (offset && compound_array_assign)
		|| simple_array_assign)
	    onref = 0;		/* array variables may not be namerefs */

	  /* ksh93 seems to do this */
	  offref = (flags_off & att_nameref);
	  flags_off &= ~att_nameref;

	  VSETATTR (var, flags_on);
	  VUNSETATTR (var, flags_off);

#if defined (ARRAY_VARS)
	  if (offset && compound_array_assign)
	    assign_array_var_from_string (var, value, aflags);
	  else if (simple_array_assign && subscript_start)
	    {
	      /* declare [-aA] name[N]=value */
	      *subscript_start = '[';	/* ] */
	      var = assign_array_element (name, value, 0);	/* XXX - not aflags */
	      *subscript_start = '\0';
	      if (var == 0)	/* some kind of assignment error */
		{
		  assign_error++;
		  flags_on |= onref;
		  flags_off |= offref;
		  NEXT_VARIABLE ();
		}
	    }
	  else if (simple_array_assign)
	    {
	      /* let bind_{array,assoc}_variable take care of this. */
	      if (assoc_p (var))
		bind_assoc_variable (var, name, savestring ("0"), value, aflags);
	      else
		bind_array_variable (name, 0, value, aflags);
	    }
	  else
#endif
	  /* bind_variable_value duplicates the essential internals of
	     bind_variable() */
	  if (offset)
	    bind_variable_value (var, value, aflags);

	  /* If we found this variable in the temporary environment, as with
	     `var=value declare -x var', make sure it is treated identically
	     to `var=value export var'.  Do the same for `declare -r' and
	     `readonly'.  Preserve the attributes, except for att_tempvar. */
	  /* XXX -- should this create a variable in the global scope, or
	     modify the local variable flags?  ksh93 has it modify the
	     global scope.
	     Need to handle case like in set_var_attribute where a temporary
	     variable is in the same table as the function local vars. */
	  if ((flags_on & (att_exported|att_readonly)) && tempvar_p (var))
	    {
	      SHELL_VAR *tv;
	      char *tvalue;

	      tv = find_tempenv_variable (var->name);
	      if (tv)
		{
		  tvalue = var_isset (var) ? savestring (value_cell (var)) : savestring ("");
	          tv = bind_variable (var->name, tvalue, 0);
	          tv->attributes |= var->attributes & ~att_tempvar;
	          if (tv->context > 0)
		    VSETATTR (tv, att_propagate);
	          free (tvalue);
		}
	      VSETATTR (var, att_propagate);
	    }
	}

      /* Turn on nameref attribute we deferred above. */
      /* XXX - should we turn on the noassign attribute for consistency with
	 ksh93 when we turn on the nameref attribute? */
      VSETATTR (var, onref);
      flags_on |= onref;
      VUNSETATTR (var, offref);
      flags_off |= offref;
      /* Yuck.  ksh93 compatibility */
      if (refvar)
 	VUNSETATTR (refvar, flags_off);

      stupidly_hack_special_variables (name);

      NEXT_VARIABLE ();
    }

  return (assign_error ? EX_BADASSIGN
		       : ((any_failed == 0) ? EXECUTION_SUCCESS
  					    : EXECUTION_FAILURE));
}
