/* GNU m4 -- A simple macro processor
   Copyright (C) 1989, 90, 91, 92, 93, 94 Free Software Foundation, Inc.
  
   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 2, 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, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/* This file contains the functions, that performs the basic argument
   parsing and macro expansion.  */

#include "m4.h"

static void expand_macro _((symbol *));
static void expand_token _((struct obstack *, token_type, token_data *));

/* Current recursion level in expand_macro ().  */
int expansion_level = 0;

/* The number of the current call of expand_macro ().  */
static int macro_call_id = 0;

/*----------------------------------------------------------------------.
| This function read all input, and expands each token, one at a time.  |
`----------------------------------------------------------------------*/

void
expand_input (void)
{
  token_type t;
  token_data td;

  while ((t = next_token (&td)) != TOKEN_EOF)
    expand_token ((struct obstack *) NULL, t, &td);
}


/*------------------------------------------------------------------------.
| Expand one token, according to its type.  Potential macro names	  |
| (TOKEN_WORD) are looked up in the symbol table, to see if they have a	  |
| macro definition.  If they have, they are expanded as macros, otherwise |
| the text are just copied to the output.				  |
`------------------------------------------------------------------------*/

static void
expand_token (struct obstack *obs, token_type t, token_data *td)
{
  symbol *sym;

  switch (t)
    {				/* TOKSW */
    case TOKEN_EOF:
    case TOKEN_MACDEF:
      break;

    case TOKEN_SIMPLE:
    case TOKEN_STRING:
      shipout_text (obs, TOKEN_DATA_TEXT (td), strlen (TOKEN_DATA_TEXT (td)));
      break;

    case TOKEN_WORD:
      sym = lookup_symbol (TOKEN_DATA_TEXT (td), SYMBOL_LOOKUP);
      if (sym == NULL || SYMBOL_TYPE (sym) == TOKEN_VOID
	  || (SYMBOL_TYPE (sym) == TOKEN_FUNC
	      && SYMBOL_BLIND_NO_ARGS (sym)
	      && peek_input () != '('))
	{
#ifdef ENABLE_CHANGEWORD
	  shipout_text (obs, TOKEN_DATA_ORIG_TEXT (td),
			strlen (TOKEN_DATA_ORIG_TEXT (td)));
#else
	  shipout_text (obs, TOKEN_DATA_TEXT (td),
			strlen (TOKEN_DATA_TEXT (td)));
#endif
	}
      else
	expand_macro (sym);
      break;

    default:
      M4ERROR ((warning_status, 0,
		"INTERNAL ERROR: Bad token type in expand_token ()"));
      abort ();
    }
}


/*-------------------------------------------------------------------------.
| This function parses one argument to a macro call.  It expects the first |
| left parenthesis, or the separating comma to have been read by the	   |
| caller.  It skips leading whitespace, and reads and expands tokens,	   |
| until it finds a comma or an right parenthesis at the same level of	   |
| parentheses.  It returns a flag indicating whether the argument read are |
| the last for the active macro call.  The argument are build on the	   |
| obstack OBS, indirectly through expand_token ().			   |
`-------------------------------------------------------------------------*/

static boolean
expand_argument (struct obstack *obs, token_data *argp)
{
  token_type t;
  token_data td;
  char *text;
  int paren_level;

  TOKEN_DATA_TYPE (argp) = TOKEN_VOID;

  /* Skip leading white space.  */
  do
    {
      t = next_token (&td);
    }
  while (t == TOKEN_SIMPLE && isspace (*TOKEN_DATA_TEXT (&td)));

  paren_level = 0;

  while (1)
    {

      switch (t)
	{			/* TOKSW */
	case TOKEN_SIMPLE:
	  text = TOKEN_DATA_TEXT (&td);
	  if ((*text == ',' || *text == ')') && paren_level == 0)
	    {

	      /* The argument MUST be finished, whether we want it or not.  */
	      obstack_1grow (obs, '\0');
	      text = obstack_finish (obs);

	      if (TOKEN_DATA_TYPE (argp) == TOKEN_VOID)
		{
		  TOKEN_DATA_TYPE (argp) = TOKEN_TEXT;
		  TOKEN_DATA_TEXT (argp) = text;
		}
	      return (boolean) (*TOKEN_DATA_TEXT (&td) == ',');
	    }

	  if (*text == '(')
	    paren_level++;
	  else if (*text == ')')
	    paren_level--;
	  expand_token (obs, t, &td);
	  break;

	case TOKEN_EOF:
	  M4ERROR ((EXIT_FAILURE, 0,
		    "ERROR: EOF in argument list"));
	  break;

	case TOKEN_WORD:
	case TOKEN_STRING:
	  expand_token (obs, t, &td);
	  break;

	case TOKEN_MACDEF:
	  if (obstack_object_size (obs) == 0)
	    {
	      TOKEN_DATA_TYPE (argp) = TOKEN_FUNC;
	      TOKEN_DATA_FUNC (argp) = TOKEN_DATA_FUNC (&td);
	      TOKEN_DATA_FUNC_TRACED (argp) = TOKEN_DATA_FUNC_TRACED (&td);
	    }
	  break;

	default:
	  M4ERROR ((warning_status, 0,
		    "INTERNAL ERROR: Bad token type in expand_argument ()"));
	  abort ();
	}

      t = next_token (&td);
    }
}

/*-------------------------------------------------------------------------.
| Collect all the arguments to a call of the macro SYM.  The arguments are |
| stored on the obstack ARGUMENTS and a table of pointers to the arguments |
| on the obstack ARGPTR.						   |
`-------------------------------------------------------------------------*/

static void
collect_arguments (symbol *sym, struct obstack *argptr,
		   struct obstack *arguments)
{
  int ch;			/* lookahead for ( */
  token_data td;
  token_data *tdp;
  boolean more_args;
  boolean groks_macro_args = SYMBOL_MACRO_ARGS (sym);

  TOKEN_DATA_TYPE (&td) = TOKEN_TEXT;
  TOKEN_DATA_TEXT (&td) = SYMBOL_NAME (sym);
  tdp = (token_data *) obstack_copy (arguments, (voidstar) &td, sizeof (td));
  obstack_grow (argptr, (voidstar) &tdp, sizeof (tdp));

  ch = peek_input ();
  if (ch == '(')
    {
      next_token (&td);		/* gobble parenthesis */
      do
	{
	  more_args = expand_argument (arguments, &td);

	  if (!groks_macro_args && TOKEN_DATA_TYPE (&td) == TOKEN_FUNC)
	    {
	      TOKEN_DATA_TYPE (&td) = TOKEN_TEXT;
	      TOKEN_DATA_TEXT (&td) = "";
	    }
	  tdp = (token_data *)
	    obstack_copy (arguments, (voidstar) &td, sizeof (td));
	  obstack_grow (argptr, (voidstar) &tdp, sizeof (tdp));
	}
      while (more_args);
    }
}


/*------------------------------------------------------------------------.
| The actual call of a macro is handled by call_macro ().  call_macro ()  |
| is passed a symbol SYM, whose type is used to call either a builtin	  |
| function, or the user macro expansion function expand_user_macro ()	  |
| (lives in builtin.c).  There are ARGC arguments to the call, stored in  |
| the ARGV table.  The expansion is left on the obstack EXPANSION.  Macro |
| tracing is also handled here.						  |
`------------------------------------------------------------------------*/

void
call_macro (symbol *sym, int argc, token_data **argv,
		 struct obstack *expansion)
{
  switch (SYMBOL_TYPE (sym))
    {
    case TOKEN_FUNC:
      (*SYMBOL_FUNC (sym)) (expansion, argc, argv);
      break;

    case TOKEN_TEXT:
      expand_user_macro (expansion, sym, argc, argv);
      break;

    default:
      M4ERROR ((warning_status, 0,
		"INTERNAL ERROR: Bad symbol type in call_macro ()"));
      abort ();
    }
}

/*-------------------------------------------------------------------------.
| The macro expansion is handled by expand_macro ().  It parses the	   |
| arguments, using collect_arguments (), and builds a table of pointers to |
| the arguments.  The arguments themselves are stored on a local obstack.  |
| Expand_macro () uses call_macro () to do the call of the macro.	   |
| 									   |
| Expand_macro () is potentially recursive, since it calls expand_argument |
| (), which might call expand_token (), which might call expand_macro ().  |
`-------------------------------------------------------------------------*/

static void
expand_macro (symbol *sym)
{
  struct obstack arguments;
  struct obstack argptr;
  token_data **argv;
  int argc;
  struct obstack *expansion;
  const char *expanded;
  boolean traced;
  int my_call_id;

  expansion_level++;
  if (expansion_level > nesting_limit)
    M4ERROR ((EXIT_FAILURE, 0,
	      "ERROR: Recursion limit of %d exceeded, use -L<N> to change it",
	      nesting_limit));

  macro_call_id++;
  my_call_id = macro_call_id;

  traced = (boolean) ((debug_level & DEBUG_TRACE_ALL) || SYMBOL_TRACED (sym));

  obstack_init (&argptr);
  obstack_init (&arguments);

  if (traced && (debug_level & DEBUG_TRACE_CALL))
    trace_prepre (SYMBOL_NAME (sym), my_call_id);

  collect_arguments (sym, &argptr, &arguments);

  argc = obstack_object_size (&argptr) / sizeof (token_data *);
  argv = (token_data **) obstack_finish (&argptr);

  if (traced)
    trace_pre (SYMBOL_NAME (sym), my_call_id, argc, argv);

  expansion = push_string_init ();
  call_macro (sym, argc, argv, expansion);
  expanded = push_string_finish ();

  if (traced)
    trace_post (SYMBOL_NAME (sym), my_call_id, argc, argv, expanded);

  --expansion_level;

  obstack_free (&arguments, NULL);
  obstack_free (&argptr, NULL);
}
