/*
 * 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/builtin.c 0.5.1.0 90/09/28 18:34:53 tho Exp $
 */

/* 
 * Code for all builtin macros, initialisation of symbol table, and
 * expansion of user defined macros.
 */

#include "m4.h"

#define ARG(i)	(argc > (i) ? TOKEN_DATA_TEXT(argv[i]) : "")


/* 
 * Initialisation of builtin and predefined macros.  The table
 * "builtin_tab" is both used for initialisation, and by the "builtin"
 * builtin. 
 */

#ifdef MSDOS

#include <process.h>
#include <errno.h>
#include <io.h>

#define M4_ARGS \
  struct obstack *obs, int argc, struct token_data **argv

builtin *find_builtin_by_name(char *name);
builtin *find_builtin_by_addr(builtin_func *func);
static char *ntoa (eval_t value, int radix);
static enum boolean bad_argc (char *name, int argc, int min, int max);
static int dumpdef_cmp (struct symbol **s1, struct symbol **s2);
static void define_builtin (char *name, struct builtin *bp,\
			    enum symbol_lookup mode);
static void define_macro (int argc, struct token_data **argv,\
			  enum symbol_lookup mode);
static void dump_args (struct obstack *obs, int argc,\
		       struct token_data **argv, char *sep,\
		       enum boolean quoted);
static void dump_symbol (struct symbol *sym, struct dump_symbol_data *data);
static void include (int argc, struct token_data **argv, enum boolean silent);
static void m4_builtin (M4_ARGS);
static void m4_changecom (M4_ARGS);
static void m4_changequote (M4_ARGS);
static void m4_define (M4_ARGS);
static void m4_defn (M4_ARGS);
static void m4_divert (M4_ARGS);
static void m4_divnum (M4_ARGS);
static void m4_dnl (M4_ARGS);
static void m4_dumpdef (M4_ARGS);
static void m4_errprint (M4_ARGS);
static void m4_eval (M4_ARGS);
static void m4_ifdef (M4_ARGS);
static void m4_ifelse (M4_ARGS);
static void m4_include (M4_ARGS);
static void m4_index (M4_ARGS);
static void m4_len (M4_ARGS);
static void m4_m4exit (M4_ARGS);
static void m4_m4wrap (M4_ARGS);
static void m4_maketemp (M4_ARGS);
static void m4_popdef (M4_ARGS);
static void m4_pushdef (M4_ARGS);
static void m4_shift (M4_ARGS);
static void m4_sinclude (M4_ARGS);
static void m4_substr (M4_ARGS);
static void m4_syscmd (M4_ARGS);
static void m4_sysval (M4_ARGS);
static void m4_traceoff (M4_ARGS);
static void m4_traceon (M4_ARGS);
static void m4_translit (M4_ARGS);
static void m4_undefine (M4_ARGS);
static void m4_undivert (M4_ARGS);
static void set_trace (struct symbol *sym, char *data);
static void shipout_int (struct obstack *obs, int val);

#else /* not MSDOS */

static void m4_builtin();
static void m4_changecom();
static void m4_changequote();
static void m4_define();
static void m4_defn();
static void m4_divert();
static void m4_divnum();
static void m4_dnl();
static void m4_dumpdef();
static void m4_errprint();
static void m4_eval();
static void m4_ifdef();
static void m4_ifelse();
static void m4_include();
static void m4_index();
static void m4_len();
static void m4_m4exit();
static void m4_m4wrap();
static void m4_maketemp();
static void m4_popdef();
static void m4_pushdef();
static void m4_shift();
static void m4_sinclude();
static void m4_substr();
static void m4_syscmd();
static void m4_sysval();
static void m4_traceoff();
static void m4_traceon();
static void m4_translit();
static void m4_undefine();
static void m4_undivert();

#endif /* not MSDOS */

static builtin
builtin_tab[] = {

    /* name		gnu	macro-args	function */

    { "builtin",	true,	false,		m4_builtin },
    { "changecom",	false,	false,		m4_changecom },
    { "changequote",	false,	false,		m4_changequote },
    { "define",		false,	true,		m4_define },
    { "defn",		false,	false,		m4_defn },
    { "divert",		false,	false,		m4_divert },
    { "divnum",		false,	false,		m4_divnum },
    { "dnl",		false,	false,		m4_dnl },
    { "dumpdef",	false,	false,		m4_dumpdef },
    { "errprint",	false,	false,		m4_errprint },
    { "eval",		false,	false,		m4_eval },
    { "ifdef",		false,	false,		m4_ifdef },
    { "ifelse",		false,	false,		m4_ifelse },
    { "include",	false,	false,		m4_include },
    { "index",		false,	false,		m4_index },
    { "len",		false,	false,		m4_len },
    { "m4exit",		false,	false,		m4_m4exit },
    { "m4wrap",		false,	false,		m4_m4wrap },
    { "maketemp",	false,	false,		m4_maketemp },
    { "popdef",		false,	false,		m4_popdef },
    { "pushdef",	false,	true,		m4_pushdef },
    { "shift",		false,	false,		m4_shift },
    { "sinclude",	false,	false,		m4_sinclude },
    { "substr"	,	false,	false,		m4_substr },
    { "syscmd",		false,	false,		m4_syscmd },
    { "sysval",		false,	false,		m4_sysval },
    { "traceoff",	false,	false,		m4_traceoff },
    { "traceon",	false,	false,		m4_traceon },
    { "translit",	false,	false,		m4_translit },
    { "undefine",	false,	false,		m4_undefine },
    { "undivert",	false,	false,		m4_undivert },

    { 0, false,	false, 0 },
};

static predefined 
predefined_tab[] = {

    { "unix",	false,	"" },
    { "gnu",	true,	"" },
    { "incr",	false,	"eval(`($1)+1')" },
    { "decr",	false,	"eval(`($1)-1')" },

    { 0, false,	0 },
};

/* The number of the currently active diversion */
static int current_diversion;


/* 
 * Find the builtin, which lives on ADDR
 */

builtin *
find_builtin_by_addr(func)
    builtin_func *func;
{
    builtin *bp;

    for (bp = &builtin_tab[0]; bp->name != nil; bp++)
	if (bp->func == func)
	    return bp;
    return nil;
}

/* 
 * Find the builtin, which has NAME
 */

builtin *
find_builtin_by_name(name)
    char *name;
{
    builtin *bp;

    for (bp = &builtin_tab[0]; bp->name != nil; bp++)
	if (strcmp(bp->name, name) == 0)
	    return bp;
    return nil;
}


/* 
 * Install a builtin macro with name NAME, bound to the C function given
 * in BP.
 */
static void 
define_builtin(name, bp, mode)
    char *name;
    builtin *bp;
    symbol_lookup mode;
{
    symbol *sym;

    sym = lookup_symbol(name, mode);
    SYMBOL_TYPE(sym) = TOKEN_FUNC;
    SYMBOL_MACRO_ARGS(sym) = bp->groks_macro_args;
    SYMBOL_FUNC(sym) = bp->func;
}

/* 
 * Define a predefined or user-defined macro, with name NAME, and
 * expansion TEXT.  MODE destinguishes between the "define" and the
 * "pushdef" case.  It is also used from main().
 */
void 
define_user_macro(name, text, mode)
    char *name;
    char *text;
    symbol_lookup mode;
{
    symbol *s;
    
    s = lookup_symbol(name, mode);
    if (SYMBOL_TYPE(s) == TOKEN_TEXT)
	xfree(SYMBOL_TEXT(s));
    
    SYMBOL_TYPE(s) = TOKEN_TEXT;
    SYMBOL_TEXT(s) = xstrdup(text);
}

/* 
 * Initialise all builtin and predefined macros.
 */
void 
builtin_init()
{
    builtin *bp;
    predefined *pp;

    for (bp = &builtin_tab[0]; bp->name != nil; bp++) {
	if (!(no_gnu_extensions && bp->gnu_extension))
	    define_builtin(bp->name, bp, SYMBOL_INSERT);
    }

    for (pp = &predefined_tab[0]; pp->name != nil; pp++) {
	if (!(no_gnu_extensions && pp->gnu_extension))
	    define_user_macro(pp->name, pp->func, SYMBOL_INSERT);
    }

    current_diversion = 0;
}

/* 
 * Give friendly warnings, if a builtin macro is passed an inappropriate
 * number of arguments.  NAME is macro name for messages, ARGC is actual
 * number of arguments, MIN is minimum number of acceptable arguments,
 * negative if inapplicable, MAX is maximum number, negative if
 * inapplicable.
 */
static boolean 
bad_argc(name, argc, min, max)
    char *name;
    int argc;
    int min;
    int max;
{
    if (min > 0 && argc < min) {
	warning("too few arguments to %s", name);
	return true;
    } else if (max > 0 && argc > max)
	warning("excess arguments to %s ignored", name);
    return false;
}

/* 
 * Format an int VAL, and stuff it into an obstack OBS.
 */
static void 
shipout_int(obs, val)
    struct obstack *obs;
    int val;
{
    char buf[512];
    sprintf(buf, "%d", val);
    obstack_grow(obs, buf, strlen(buf));
}
    

/* 
 * Print all arguments to a macro to obstack OBS, separated by SEP, and
 * quoted by the current quotes, if QUOTED is true.
 */
static void 
dump_args(obs, argc, argv, sep, quoted)
    struct obstack *obs;
    int argc;
    token_data **argv;
    char *sep;
    boolean quoted;
{
    int i;
    int len = strlen(sep);

    for (i = 1; i < argc; i++) {
	if (i > 1)
	    obstack_grow(obs, sep, len);
	if (quoted)
	    obstack_1grow(obs, lquote);
	obstack_grow(obs, TOKEN_DATA_TEXT(argv[i]), strlen(TOKEN_DATA_TEXT(argv[i])));
	if (quoted)
	    obstack_1grow(obs, rquote);
    }
}


/* 
 * The rest of this file is code for builtins and expansion of user
 * defined macros.  All the functions for builtins have a prototype as:
 * 
 * 	void m4_MACRONAME(struct obstack *obs, int argc, char *argv[]);
 *
 * The function are expected to leave their expansion on the obstack
 * OBS, as an unfinished object.  ARGV is a table of ARGC pointers to
 * the individual arguments to the macro.  Please note that in general
 * argv[argc] != nil.
 */

/* 
 * The first section are macros for definining, undefining, examining,
 * changing, ... other macros.
 */
static void 
define_macro(argc, argv, mode)
    int argc;
    token_data **argv;
    symbol_lookup mode;
{
    builtin *bp;

    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 3, 3))
	return;

    if (TOKEN_DATA_TYPE(argv[1]) != TOKEN_TEXT)
	return;

    switch (TOKEN_DATA_TYPE(argv[2])) {
    case TOKEN_TEXT:
	define_user_macro(ARG(1), ARG(2), mode);
	break;
    case TOKEN_FUNC:
	bp = find_builtin_by_addr(TOKEN_DATA_FUNC(argv[2]));
	if (bp == nil)
	    return;
	else
	    define_builtin(ARG(1), bp, mode);
	break;
    default:
	internal_error("Bad token data type in define_macro()");
	break;
    }
    return;
}

static void 
m4_define(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    define_macro(argc, argv, SYMBOL_INSERT);
}


static void 
m4_undefine(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 2, 2))
	return;
    lookup_symbol(ARG(1), SYMBOL_DELETE);
}

static void 
m4_pushdef(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    define_macro(argc, argv,  SYMBOL_PUSHDEF);
}


static void 
m4_popdef(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 2, 2))
	return;
    lookup_symbol(ARG(1), SYMBOL_POPDEF);
}

static void 
m4_ifdef(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    symbol *s;
    char *result;
    
    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 3, 4))
	return;
    s = lookup_symbol(ARG(1), SYMBOL_LOOKUP);

    if (s != nil)
	result = ARG(2);
    else if (argc == 4)
	result = ARG(3);
    else
	result = nil;

    if (result != nil)
	obstack_grow(obs, result, strlen(result));
}

static void 
m4_ifelse(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    char *result;
    char *name = TOKEN_DATA_TEXT(argv[0]);

    --argc, ++argv;

    result = nil;
    while (result == nil) {
	bad_argc(name, argc, 3, -1);

	if (strcmp(ARG(0), ARG(1)) == 0) {
	    result = ARG(2);
	} else {
	    switch (argc) {
	    case 2:
		result = "";
		break;
	    case 3:
	    case 4:
		result = ARG(3);
		break;
	    default:
		argc -= 3;
		argv += 3;
		break;
	    }
	}
    }
    obstack_grow(obs, result, strlen(result));
}

/* 
 * The function dump_symbol() is for use by "dumpdef".  It builds up a
 * table of all defined, un-shadowed, symbols.  The structure
 * dump_symbol_data is used to pass the information needed from call to
 * call to dump_symbol.
 */

struct dump_symbol_data {
    struct obstack *obs;		/* obstack for table */
    symbol **base;			/* base of table */
    int size;				/* size of table */
};

static void 
dump_symbol(sym, data)
    symbol *sym;
    struct dump_symbol_data *data;
{
    if (!sym->shadowed) {
	obstack_blank(data->obs, sizeof(symbol*));
	data->base = (symbol **)obstack_base(data->obs);
#if defined(_MSC_VER) && (_MSC_VER == 600)
	/* Work around an optimizer bug.  */
	{ int tmp = data->size++; data->base[tmp] = sym; }
#else
	data->base[data->size++] = sym;
#endif
    }
}

/* 
 * qsort comparison routine, for sorting the table made in m4_dumpdef().
 */
static int 
dumpdef_cmp(s1, s2)
    symbol **s1, **s2;
{
    return strcmp(SYMBOL_NAME(*s1), SYMBOL_NAME(*s2));
}

/* 
 * Implementation of "dumpdef" itself.  It builds up a table of pointers
 * to symbols, sorts it and prints the sorted table.
 */
static void 
m4_dumpdef(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    symbol *s;
    int i;
    struct dump_symbol_data data;
    builtin *bp;

    data.obs = obs;
    data.base = (symbol **)obstack_base(obs);
    data.size = 0;
    
    if (argc == 1) {
	hack_all_symbols(dump_symbol, (char *)&data);
    } else {
	for (i = 1; i < argc; i++) {
	    s = lookup_symbol(TOKEN_DATA_TEXT(argv[i]), SYMBOL_LOOKUP);
	    if (s != nil)
#ifdef MSDOS
		dump_symbol(s, &data);
#else /* not MSDOS */
		dump_symbol(s, (char *)&data);
#endif /* not MSDOS */
	    else
		error("Undefined name %s", TOKEN_DATA_TEXT(argv[i]));
	}
    }

    qsort((char*)data.base, data.size, sizeof(symbol*), dumpdef_cmp);

    for ( ; data.size > 0; --data.size, data.base++) {
	fprintf(stderr, "%s:\t", SYMBOL_NAME(data.base[0]));
	switch(SYMBOL_TYPE(data.base[0])) {
	case TOKEN_TEXT:
	    fprintf(stderr, "`%s'\n", SYMBOL_TEXT(data.base[0]));
	    break;
	case TOKEN_FUNC:
	    bp = find_builtin_by_addr(SYMBOL_FUNC(data.base[0]));
	    if (bp == nil)
		internal_error("builtin not found in builtin table!");
	    fprintf(stderr, "<%s>\n", bp->name);
	    break;
	default:
	    internal_error("Bad token data type in m4_dumpdef()");
	    break;
	}
    }

#ifdef MSDOS			/* but it's a genuine bug.  */
  /* Or is the intention to finalize the object?
     (cf. m4_errprint (), which could also free the object)  */
  obstack_free (obs, obstack_base (obs));
#endif
}

/* 
 * This is non-standard.  "Builtin" allows calls to builtin macros, even
 * if thier definition has been overridden or shadowed.  It is thus
 * possible to redefine builtins, and still access their original
 * definition.
 *
 * This macro is not available in compatibility mode.
 */
static void 
m4_builtin(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    struct builtin *bp;
    char *name = ARG(1);

    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 2, -1))
	return;

    bp = find_builtin_by_name(name);
    if (bp == nil)
	error("Undefined name %s", name);
    else
	(*bp->func)(obs, argc-1, argv+1);
}

/* 
 * The macro "defn" returns the quoted definition of its first argument.
 */
static void 
m4_defn(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    symbol *s;
    
    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 2, 2))
	return;

    s = lookup_symbol(ARG(1), SYMBOL_LOOKUP);
    if (s == nil)
	return;

    switch (SYMBOL_TYPE(s)) {
    case TOKEN_TEXT:
	obstack_1grow(obs, lquote);
	obstack_grow(obs, SYMBOL_TEXT(s), strlen(SYMBOL_TEXT(s)));
	obstack_1grow(obs, rquote);
	break;
    case TOKEN_FUNC:
	push_macro(SYMBOL_FUNC(s));
	break;
    default:
	internal_error("Bad symbol type in m4_defn()");
	break;
    }
}


/* 
 * This section contains macros to handle the builtins "syscmd" and
 * "sysval".
 */

/* Exit code from last "syscmd" command. */
static int sysval;

static void 
m4_syscmd(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
#ifdef MSDOS

  /* The system function from the MSC runtime lib doesn't return
     the exit code, so we have to hack one ourselves.  */

  char *sysargv[4];
  char *p = NULL;

  if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 2, 2))
    return;

  /* Find $SHELL or $COMSPEC, assuming the former is a UNIX style
     shell (preferred) and the latter is a DOS style command
     processor.  */

  sysargv[0] = getenv ("SHELL");
  if (sysargv[0])
    sysargv[1] = "-c";
  else
    {
      sysargv[0] = getenv ("COMSPEC");
      if (sysargv[0])
	sysargv[1] = "/c";
      else
	{
	  errno = ENOENT;
	  sysval = (-1) << 8;
	  return;
	}
    }

  /* Let the shell execute COMMAND.  */

  sysargv[2] = ARG(1);
  sysargv[3] = NULL;

  /* Precompensate the ">> 8" from m4_sysval ()  */
  sysval = spawnvpe (P_WAIT, sysargv[0], sysargv, NULL) << 8;

#else /* not MSDOS */

    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 2, 2))
	return;
    sysval = system(ARG(1));

#endif /* not MSDOS */
}

static void 
m4_sysval(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    shipout_int(obs, sysval>>8);
}
    


/* 
 * This section contains the top level code for the "eval" builtin.  The
 * actual work is done in the function evaluate(), which lives in eval.c.
 */

/* digits for number to ascii conversions. */
static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";

static char *
ntoa(value, radix)
    register eval_t value;
    int radix;
{
#ifdef MSDOS
    unsigned long uvalue;
#else
    unsigned int uvalue;
#endif
    static char str[256];
    register char *s = &str[sizeof str];
    boolean negative = false;

    *--s = '\0';
    
    if (radix == 10 && value < 0) {
	int tmp;

	negative = true;
	value = -(value+1);

#ifdef MSDOS			/* shut up the compiler.  */
	tmp = (int) (value%radix);
#else
	tmp = value%radix;
#endif
	if (tmp == radix - 1) {
	    *--s = '0';
	    value = value/radix + 1;
	} else {
	    *--s = digits[tmp+1];
	    value /= radix;
	}
	if (value == 0) {
	    *--s = '-';
	    return s;
	}
    }


#ifdef MSDOS
    uvalue = (unsigned long)value;
#else
    uvalue = (unsigned int)value;
#endif
    do {
	*--s = digits[uvalue%radix];
	uvalue /= radix;
    } while (uvalue > 0);

    if (negative)
	*--s = '-';
    return s;
}

static void 
m4_eval(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    eval_t value;
    int radix = 10;
    int min = 1;
    char *s;

    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 2, 4))
	return;

    if (argc >= 3 && (sscanf(ARG(2), "%d", &radix) != 1)) {
	error("non-numeric second argument to eval");
	return;
    }
    if (radix <= 1 || radix > strlen(digits)) {
	error("radix in eval out of range (radix = %d)", radix);
	return;
    }
    if (argc >= 4 && (sscanf(ARG(3), "%d", &min) != 1 || min <= 0)) {
	error("non-numeric third argument to eval");
	return;
    }
	
    if (evaluate(ARG(1), &value))
	return;

    s = ntoa(value, radix);

    if (*s == '-') {
	obstack_1grow(obs, '-');
	min--;
	s++;
    }
    for (min -= strlen(s); --min >= 0; )
	obstack_1grow(obs, '0');

    obstack_grow(obs, s, strlen(s));
}



/* 
 * This section contains the macros for handling diversion.
 */

/* 
 * Divert further output to the diversion given by ARGV[ARGC].  Out of
 * range means discard further output.
 */
static void 
m4_divert(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    int i = 0;

    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 1, 2))
	return;
    
    if (argc == 2 && sscanf(ARG(1), "%d", &i) != 1) {
	error("non-numeric argument to divert");
	return;
    }
    make_divertion(i);
    current_diversion = i;
}

/* 
 * Expand to the current diversion number, -1 if none.
 */
static void 
m4_divnum(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 1, 1))
	return;
    shipout_int(obs, current_diversion);
}
    
/* 
 * Bring back the diversion given by the argument list.  If none is
 * specified, bring back all diversions.
 */
static void 
m4_undivert(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    int i, div;

    if (argc == 1) {
	undivert_all();
    } else {
	for (i = 1; i < argc; i++) {
	    if (sscanf(ARG(1), "%d", &div) != 1) {
		error("non-numeric argument to divert");
		continue;
	    }
	    insert_divertion(div);
	}
    }
}


/* 
 * This section contains various macros, which does not fall into any
 * specific group.
 */

/* 
 * Delete all subsequent whitespace from input.  skip_whitespace() lives
 * in input.c.
 */
static void 
m4_dnl(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    skip_line();
}

/* 
 * Shift all argument one to the left, discarding the first argument.
 * Each output argument is quoted iwth the current quotes.
 */
static void 
m4_shift(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    dump_args(obs, argc-1, argv+1, ", ", true);
}

/* 
 * Change the current quotes.  Currently, only single character quotes
 * are supported.
 */
static void 
m4_changequote(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 1, 3))
	return;

    lquote = (argc >= 2) ? TOKEN_DATA_TEXT(argv[1])[0] : DEF_LQUOTE;
    rquote = (argc >= 3) ? TOKEN_DATA_TEXT(argv[2])[0] : DEF_RQUOTE;
}

/* 
 * Change the current comment delimiters.  Currently, only single
 * comment delimiters are supported.
 */
static void 
m4_changecom(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 1, 3))
	return;

    bcomm = (argc >= 2) ? TOKEN_DATA_TEXT(argv[1])[0] : DEF_BCOMM;
    ecomm = (argc >= 3) ? TOKEN_DATA_TEXT(argv[2])[0] : DEF_ECOMM;
}


/* 
 * This section contains macros for inclusion of other files.  This
 * differs from diversion, in that the input is scanned before being
 * copied to the output.
 */
/* 
 * Generic include function.  Include the file given by the first
 * argument, if it exists.  Complain about inaccesible files, only if
 * SILENT is false.
 */
static void 
include(argc, argv, silent)
    int argc;
    token_data **argv;
    boolean silent;
{
    FILE *fp;

    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 2, 2))
	return;

    fp = fopen(ARG(1), "r");
    if (fp == nil) {
	if (!silent)
	    error("can't open %s: %s", ARG(1), syserr());
	return;
    }

    push_file(fp, ARG(1));
}

/* 
 * Include a file, complaining in case of errors.
 */
static void 
m4_include(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    include(argc, argv, false);
}

/* 
 * Include a file, ignoring errors.
 */
static void 
m4_sinclude(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    include(argc, argv, true);
}


/* 
 * Use the first argument as at template for a temporary file name.
 */
static void 
m4_maketemp(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 2, 2))
	return;
    mktemp(ARG(1));
    obstack_grow(obs, ARG(1), strlen(ARG(1)));
}

/* 
 * Print all arguments on standard error.
 */
static void 
m4_errprint(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    dump_args(obs, argc, argv, " ", false);
#ifdef MSDOS			/* but it's a genuine bug.  */
    obstack_1grow(obs, '\0');
#endif
    fprintf(stderr, "%s", obstack_finish(obs));
    fflush(stderr);
}


/* 
 * This section contains various macros for exiting, saving input until
 * EOF is seen, and tracing macro calls.
 */
/* 
 * Exit immediately, with exitcode specified by the first argument, 0 if
 * no arguments are present.
 */
static void 
m4_m4exit(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    int exit_code = 0;

    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 1, 2))
	return;
    if (argc == 2  && sscanf(ARG(1), "%d", &exit_code) != 1) {
	error("non-numeric argument to m4exit");
	exit_code = 0;
    }
    exit(exit_code);
}

/* 
 * Save the argument text until EOF has been seen, allowing for user
 * specified cleanup action.  GNU version saves all arguments, the
 * standard version only the first.
 */
static void 
m4_m4wrap(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    if (no_gnu_extensions) {
	obstack_grow(obs, ARG(1), strlen(ARG(1)));
    } else
	dump_args(obs, argc, argv, " ", false);
#ifdef MSDOS			/* but it's a genuine bug.  */
    obstack_1grow(obs, '\0');
#endif
    push_wrapup(obstack_finish(obs));
}

/* 
 * Enable tracing of all specified macros, or all, if none is specified.
 * Tracing is disabled by default, when a macro is defined.
 */

/* 
 * Set_trace() is used by "traceon" and "traceoff" to enable and disable
 * tracing of a macro.  It disables tracing if DATA is nil, otherwise it
 * enable tracing.
 */
static void 
set_trace(sym, data)
    symbol *sym;
    char *data;
{
    SYMBOL_TRACED(sym) = (boolean)(data != nil);
}

static void 
m4_traceon(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    symbol *s;
    int i;

    if (argc == 1)
	hack_all_symbols(set_trace, (char *)obs);
    else
	for (i = 1; i < argc; i++) {
	    s = lookup_symbol(TOKEN_DATA_TEXT(argv[i]), SYMBOL_LOOKUP);
	    if (s != nil)
		set_trace(s, (char *)obs);
	    else
		error("Undefined name %s", TOKEN_DATA_TEXT(argv[i]));
	}
}

/* 
 * Disable tracing of all specified macros, or all, if none is
 * specified.
 */
static void 
m4_traceoff(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    symbol *s;
    int i;

    if (argc == 1)
	hack_all_symbols(set_trace, nil);
    else
	for (i = 1; i < argc; i++) {
	    s = lookup_symbol(TOKEN_DATA_TEXT(argv[i]), SYMBOL_LOOKUP);
	    if (s != nil)
		set_trace(s, nil);
	    else
		error("Undefined name %s", TOKEN_DATA_TEXT(argv[i]));
	}
}


/* 
 * This section contains some text processing macros.
 */
/* 
 * Expand to the length of the first argument.
 */
static void 
m4_len(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 2, 2))
	return;
    shipout_int(obs, strlen(ARG(1)));
}

/* 
 * The macro expands to the first index of the second argument in the
 * first argument.
 */
static void 
m4_index(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    char *cp, *last;
    int l1, l2, retval;

    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 3, 3))
	return;

    l1 = strlen(ARG(1));
    l2 = strlen(ARG(2));
    
    last = ARG(1) + l1 - l2;

    for (cp = ARG(1); cp <= last; cp++) {
	if (strncmp(cp, ARG(2), l2) == 0)
	    break;
    }
    retval = (cp <= last) ? cp - ARG(1) : -1;
    
    shipout_int(obs, retval);
}

/* 
 * The macro "substr" extracts substrings from the first argument,
 * starting from the index given by the second argument, extending for a
 * length given by the third argument.  If the third argument is
 * missing, the substring extends to the end of the first argument.
 */
static void 
m4_substr(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    int start, length, avail;

    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 3, 4))
	return;
    
    length = avail = strlen(ARG(1));
    if (sscanf(ARG(2), "%d", &start) != 1) {
	error("non-numeric second argument to substr");
	return;
    }
    if (argc == 4 && sscanf(ARG(3), "%d", &length) != 1) {
	error("non-numeric third argument to substr");
	return;
    }
    if (start < 0 || length <= 0 || start >= avail)
	return;
    if (start + length > avail)
	length = avail - start;
    obstack_grow(obs, ARG(1) + start, length);
}

/* 
 * The macro "translit" transliterates all characters in the first
 * argument, which are present in the second argument, into the
 * corresponding character from the third argument.  If the third
 * argument is shorter than the second, the extra characters in the
 * second argument, are delete from the first. (pueh)
 */
static void 
m4_translit(obs, argc, argv)
    struct obstack *obs;
    int argc;
    token_data **argv;
{
    register char *data, *tmp;
    char *from, *to;
    int tolen;

    if (bad_argc(TOKEN_DATA_TEXT(argv[0]), argc, 3, 4))
	return;

    from = ARG(2);
    to = argc == 4 ? ARG(3) : "";
    tolen = strlen(to);

    for (data = ARG(1); *data; data++) {
	tmp = (char*)index(from, *data);
	if (tmp == nil){
	    obstack_1grow(obs, *data);
	    continue;
	}
	if (tmp - from >= tolen)
	    continue;
	obstack_1grow(obs, *(to + (tmp - from)));
    }
}

/* 
 * This function handles all expansion of user defined and predefined
 * macros.  It is called with an obstack OBS, where the macros expansion
 * will be placed, as an unfinished object.  SYM points to the macro
 * definition, giving the expansin text.  ARGC and ARGV are the
 * arguments, as usual.
 */
void 
expand_user_macro(obs, sym, argc, argv)
    struct obstack *obs;
    symbol *sym;
    int argc;
    token_data **argv;
{
    register char *text;
    int i;

    for  (text = SYMBOL_TEXT(sym); *text != '\0'; ) {
	if (*text != '$') {
	    obstack_1grow(obs, *text);
	    text++;
	    continue;
	}
	text++;
	switch (*text) {
	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':
	    if (no_gnu_extensions) {
		i = *text++ - '0';
	    } else {
		for (i = 0; isdigit(*text); text++)
		    i = i*10 + (*text - '0');
	    }
	    if (i < argc)
		obstack_grow(obs, TOKEN_DATA_TEXT(argv[i]), strlen(TOKEN_DATA_TEXT(argv[i])));
	    break;

	case '#':			/* number of arguments */
	    shipout_int(obs, argc-1);
	    text++;
	    break;

	case '*':			/* all arguments */
	case '@':			/* ... same, but quoted */
	    dump_args(obs, argc, argv, ", ", *text == '@');
	    text++;
	    break;

	default:
	    obstack_1grow(obs, '$');
	    break;
	}
    }
}
