/*
 * GNU m4 -- A simple macro processor
 * Copyright (C) 1989, 1990 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 1, 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.
 */

/*
 * MS-DOS port (c) 1990 by Thorsten Ohl, ohl@gnu.ai.mit.edu
 * This port is also distributed under the terms of the
 * GNU General Public License as published by the
 * Free Software Foundation.
 *
 * Please note that this file is not identical to the
 * original GNU release, you should have received this
 * code as patch to the official release.
 *
 * $Header: e:/gnu/m4/RCS/macro.c 0.5.1.0 90/09/28 18:35:07 tho Exp $
 */

/* 
 * This file contains the functions, that performs the basic argument
 * parsing and macro expansion.
 */

#include "m4.h"

#ifdef MSDOS
static enum boolean expand_argument (struct obstack *obs,\
				     struct token_data *argp);
static void expand_macro (struct symbol *sym);
static void expand_token (struct obstack *obs, enum token_type t,\
			  struct token_data *td);
#else /* not MSDOS */
static void expand_token();
static void expand_macro();
#endif /* not MSDOS */

/* 
 * This function read all input, and expands each token, one at a time.
 */
void 
expand_input()
{
    token_type t;
    token_data td;
    
    while ((t = next_token(&td)) != TOKEN_EOF)
	expand_token((struct obstack *)nil, 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 macroes,
 * otherwise the text are just opcied to the output.
 */
static void 
expand_token(obs, t, td)
    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));
	break;
    case TOKEN_WORD:
	sym = lookup_symbol(TOKEN_DATA_TEXT(td), SYMBOL_LOOKUP);
	if (sym == nil || SYMBOL_TYPE(sym) == TOKEN_VOID)
	    shipout_text(obs, TOKEN_DATA_TEXT(td));
	else
	    expand_macro(sym);
	break;
    default:
	internal_error("Bad token type in expand_token()");
	break;
    }
}

/* 
 * 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(obs, argp)
    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++;
	    if (*text == ')')
		paren_level--;
	    expand_token(obs, t, &td);
	    break;
	case TOKEN_EOF:
	    fatal("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);
	    }
	    break;
	default:
	    internal_error("Bad token type in expand_argument()");
	    break;
	}
	
	t = next_token(&td);
    }
}

/* 
 * The actual macro expansion are handled by expand_macro().  It parses
 * the arguments, using expand_argument(), and builds a table of
 * pointers to the arguments.  The arguments themselves are stored on a
 * local obstack.  Expand_macro() are passed a symbol SYM, whose type
 * are used to call either a builtin function, or the user macro
 * expansion function expand_user_macro() (lives in builtin.c).  Macro
 * tracing are also handled here.
 *
 * Expand_macro() are potentially recursive, since it calls
 * expand_argument(), which might call expand_token(), which might call
 * expand_macro(). 
 */

static void 
expand_macro(sym)
    symbol *sym;
{
    token_data td;
    int ch;				/* lookahead for ( */
    
    boolean more_args;
    struct obstack arguments;
    struct obstack argptr;
    token_data **argv;
    int argc = 0;
    
    struct obstack *expansion;
    char *expanded;
    boolean traced;
    boolean groks_macro_args = SYMBOL_MACRO_ARGS(sym);

    static int expansion_level;

    expansion_level++;
    
#ifdef DEBUG_MACRO
    fprintf(stderr,  "expand_macro(%s)\n", sym->name);
#endif
    
    obstack_init(&argptr);
    obstack_init(&arguments);
    
    argv = (token_data **)obstack_base(&argptr);
	
    obstack_blank(&argptr, sizeof(token_data *));
    argv[argc] = (token_data *)obstack_alloc(&arguments, sizeof(token_data));
    TOKEN_DATA_TYPE(argv[argc]) = TOKEN_TEXT;
    TOKEN_DATA_TEXT(argv[argc]) = SYMBOL_NAME(sym);
    argc++;
    
    ch = peek_input();
    if (ch == '(') {
	next_token(&td);
	do {
	    obstack_blank(&argptr, sizeof(token_data *));
	    argv = (token_data **)obstack_base(&argptr);

	    argv[argc] = (token_data *)obstack_alloc(&arguments, sizeof(token_data));
	    more_args = expand_argument(&arguments, argv[argc]);

	    if (!groks_macro_args && TOKEN_DATA_TYPE(argv[argc]) == TOKEN_FUNC) {
		TOKEN_DATA_TYPE(argv[argc]) = TOKEN_TEXT;
		TOKEN_DATA_TEXT(argv[argc]) = "";
	    }
	    argc++;
	} while (more_args);
    }
    
    traced = SYMBOL_TRACED(sym);
    expansion = push_string_init();
    
    if (traced) {
	int i;
	
	fprintf(stderr, "m4 trace (%d): %s", expansion_level, sym->name);
	if (argc > 1)
	    fprintf(stderr, "( %c", lquote);
	for (i = 1; i < argc; i++) {
	    if (i != 1)
		fprintf(stderr, "%c, %c", rquote, lquote);
	    fprintf(stderr, "%s", TOKEN_DATA_TEXT(argv[i]));
	}
	if (argc > 1)
	    fprintf(stderr, "%c )", rquote);
    }
    
    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:
	internal_error("Bad symbol type in expand_macro()");
	break;
    }
    
    expanded = push_string_finish();
    
    if (traced) {
	if (expanded)
	    fprintf(stderr, " -> %c%s%c", lquote, expanded, rquote);
	fprintf(stderr, "\n");
    }
    
    obstack_free(&arguments, 0);
    obstack_free(&argptr, 0);

    --expansion_level;
    
#ifdef DEBUG_MACRO
    fprintf(stderr,  "expand_macro(%s) --- return\n", sym->name);
#endif
}
