/* -----------------------------------------------------------------------------
 * This file is part of SWIG, which is licensed as a whole under version 3 
 * (or any later version) of the GNU General Public License. Some additional
 * terms also apply to certain portions of SWIG. The full details of the SWIG
 * license and copyrights can be found in the LICENSE and COPYRIGHT files
 * included with the SWIG source code as distributed by the SWIG developers
 * and at https://www.swig.org/legal.html.
 *
 * cpp.c
 *
 * An implementation of a C preprocessor plus some support for additional
 * SWIG directives.
 *
 * - SWIG directives such as %include and %import are handled
 * - A new macro %define ... %enddef can be used for multiline macros
 * - No preprocessing is performed in %{ ... %} blocks
 * - Lines beginning with %# are stripped down to #... and passed through.
 * ----------------------------------------------------------------------------- */

#include "swig.h"
#include "preprocessor.h"
#include <ctype.h>

static Hash *cpp = 0;		/* C preprocessor data */
static int include_all = 0;	/* Follow all includes */
static int ignore_missing = 0;
static int import_all = 0;	/* Follow all includes, but as %import statements */
static int imported_depth = 0;	/* Depth of %imported files */
static int single_include = 1;	/* Only include each file once */
static Hash *included_files = 0;
static List *dependencies = 0;
static Scanner *id_scan = 0;
static int error_as_warning = 0;	/* Understand the cpp #error directive as a special #warning */
static int expand_defined_operator = 0;
static int macro_level = 0;
static int macro_start_line = 0;
static const String * macro_start_file = 0;

/* Test a character to see if it starts an identifier */
#define isidentifier(c) ((isalpha(c)) || (c == '_') || (c == '$'))

/* Test a character to see if it valid in an identifier (after the first letter) */
#define isidchar(c) ((isalnum(c)) || (c == '_') || (c == '$'))

static DOH *Preprocessor_replace(DOH *, DOH *);

/* Skip whitespace */
static void skip_whitespace(String *s, String *out) {
  int c;
  while ((c = Getc(s)) != EOF) {
    if (!isspace(c)) {
      Ungetc(c, s);
      break;
    } else if (out)
      Putc(c, out);
  }
}

/* Skip to a specified character taking line breaks into account */
static int skip_tochar(String *s, int ch, String *out) {
  int c;
  while ((c = Getc(s)) != EOF) {
    if (out)
      Putc(c, out);
    if (c == ch)
      break;
    if (c == '\\') {
      c = Getc(s);
      if ((c != EOF) && (out))
	Putc(c, out);
    }
  }
  if (c == EOF)
    return -1;
  return 0;
}

static void copy_location(const DOH *s1, DOH *s2) {
  Setfile(s2, Getfile((DOH *) s1));
  Setline(s2, Getline((DOH *) s1));
}

static String *cpp_include(const_String_or_char_ptr fn, int sysfile) {
  String *s = sysfile ? Swig_include_sys(fn) : Swig_include(fn);
  if (s && single_include) {
    String *file = Getfile(s);
    if (Getattr(included_files, file)) {
      Delete(s);
      return 0;
    }
    Setattr(included_files, file, file);
  }
  if (!s) {
    if (ignore_missing) {
      Swig_warning(WARN_PP_MISSING_FILE, Getfile(fn), Getline(fn), "Unable to find '%s'\n", fn);
    } else {
      Swig_error(Getfile(fn), Getline(fn), "Unable to find '%s'\n", fn);
    }
  } else {
    String *lf;
    Seek(s, 0, SEEK_SET);
    if (!dependencies) {
      dependencies = NewList();
    }
    lf = Copy(Swig_last_file());
    Append(dependencies, lf);
    Delete(lf);
  }
  return s;
}

static int is_digits(const String *str) {
  const char *s = Char(str);
  int isdigits = (*s != 0);
  while (*s) {
    if (!isdigit((int)*s)) {
      isdigits = 0;
      break;
    }
    s++;
  }
  return isdigits;
}

List *Preprocessor_depend(void) {
  return dependencies;
}

/* -----------------------------------------------------------------------------
 * void Preprocessor_cpp_init() - Initialize the preprocessor
 * ----------------------------------------------------------------------------- */
static String *kpp_args = 0;
static String *kpp_define = 0;
static String *kpp_defined = 0;
static String *kpp_elif = 0;
static String *kpp_else = 0;
static String *kpp_endif = 0;
static String *kpp_expanded = 0;
static String *kpp_if = 0;
static String *kpp_ifdef = 0;
static String *kpp_ifndef = 0;
static String *kpp_name = 0;
static String *kpp_swigmacro = 0;
static String *kpp_symbols = 0;
static String *kpp_undef = 0;
static String *kpp_value = 0;
static String *kpp_varargs = 0;
static String *kpp_error = 0;
static String *kpp_warning = 0;
static String *kpp_line = 0;
static String *kpp_include = 0;
static String *kpp_pragma = 0;
static String *kpp_level = 0;

static String *kpp_dline = 0;
static String *kpp_ddefine = 0;
static String *kpp_dinclude = 0;
static String *kpp_dimport = 0;
static String *kpp_dbeginfile = 0;

static String *kpp_LINE = 0;
static String *kpp_FILE = 0;

static String *kpp_hash_if = 0;
static String *kpp_hash_elif = 0;

void Preprocessor_init(void) {
  Hash *s;

  kpp_args = NewString("args");
  kpp_define = NewString("define");
  kpp_defined = NewString("defined");
  kpp_else = NewString("else");
  kpp_elif = NewString("elif");
  kpp_endif = NewString("endif");
  kpp_expanded = NewString("*expanded*");
  kpp_if = NewString("if");
  kpp_ifdef = NewString("ifdef");
  kpp_ifndef = NewString("ifndef");
  kpp_name = NewString("name");
  kpp_swigmacro = NewString("swigmacro");
  kpp_symbols = NewString("symbols");
  kpp_undef = NewString("undef");
  kpp_value = NewString("value");
  kpp_error = NewString("error");
  kpp_warning = NewString("warning");
  kpp_pragma = NewString("pragma");
  kpp_level = NewString("level");
  kpp_line = NewString("line");
  kpp_include = NewString("include");
  kpp_varargs = NewString("varargs");

  kpp_dinclude = NewString("%include");
  kpp_dimport = NewString("%import");
  kpp_dbeginfile = NewString("%beginfile");
  kpp_ddefine = NewString("%define");
  kpp_dline = NewString("%line");


  kpp_LINE = NewString("__LINE__");
  kpp_FILE = NewString("__FILE__");

  kpp_hash_if = NewString("#if");
  kpp_hash_elif = NewString("#elif");

  cpp = NewHash();
  s = NewHash();
  Setattr(cpp, kpp_symbols, s);
  Delete(s);
  Preprocessor_expr_init();	/* Initialize the expression evaluator */
  included_files = NewHash();

  id_scan = NewScanner();

}

void Preprocessor_delete(void) {
  Delete(kpp_args);
  Delete(kpp_define);
  Delete(kpp_defined);
  Delete(kpp_else);
  Delete(kpp_elif);
  Delete(kpp_endif);
  Delete(kpp_expanded);
  Delete(kpp_if);
  Delete(kpp_ifdef);
  Delete(kpp_ifndef);
  Delete(kpp_name);
  Delete(kpp_swigmacro);
  Delete(kpp_symbols);
  Delete(kpp_undef);
  Delete(kpp_value);
  Delete(kpp_error);
  Delete(kpp_warning);
  Delete(kpp_pragma);
  Delete(kpp_level);
  Delete(kpp_line);
  Delete(kpp_include);
  Delete(kpp_varargs);

  Delete(kpp_dinclude);
  Delete(kpp_dimport);
  Delete(kpp_dbeginfile);
  Delete(kpp_ddefine);
  Delete(kpp_dline);

  Delete(kpp_LINE);
  Delete(kpp_FILE);

  Delete(kpp_hash_if);
  Delete(kpp_hash_elif);

  Delete(cpp);
  Delete(included_files);
  Preprocessor_expr_delete();
  DelScanner(id_scan);

  Delete(dependencies);

  Delete(Swig_add_directory(0));
}

/* -----------------------------------------------------------------------------
 * void Preprocessor_include_all() - Instruct preprocessor to include all files
 * ----------------------------------------------------------------------------- */
void Preprocessor_include_all(int a) {
  include_all = a;
}

void Preprocessor_import_all(int a) {
  import_all = a;
}

void Preprocessor_ignore_missing(int a) {
  ignore_missing = a;
}

void Preprocessor_error_as_warning(int a) {
  error_as_warning = a;
}


/* -----------------------------------------------------------------------------
 * Preprocessor_define()
 *
 * Defines a new C preprocessor symbol.   swigmacro specifies whether or not the macro has
 * SWIG macro semantics.
 * ----------------------------------------------------------------------------- */


String *Macro_vararg_name(const_String_or_char_ptr str, const_String_or_char_ptr line) {
  String *argname;
  String *varargname;
  char *s, *dots;

  argname = Copy(str);
  s = Char(argname);
  dots = strchr(s, '.');
  if (!dots) {
    Delete(argname);
    return NULL;
  }

  if (strcmp(dots, "...") != 0) {
    Swig_error(Getfile(line), Getline(line), "Illegal macro argument name '%s'\n", str);
    Delete(argname);
    return NULL;
  }
  if (dots == s) {
    varargname = NewString("__VA_ARGS__");
  } else {
    *dots = '\0';
    varargname = NewString(s);
  }
  Delete(argname);
  return varargname;
}

Hash *Preprocessor_define(const_String_or_char_ptr _str, int swigmacro) {
  String *macroname = 0, *argstr = 0, *macrovalue = 0, *file = 0, *s = 0;
  Hash *macro = 0, *symbols = 0, *m1;
  List *arglist = 0;
  int c, line;
  int varargs = 0;
  String *str;

  assert(cpp);
  assert(_str);

  /* First make sure that string is actually a string */
  if (DohCheck(_str)) {
    s = Copy(_str);
    copy_location(_str, s);
    str = s;
  } else {
    str = NewString((char *) _str);
  }
  Seek(str, 0, SEEK_SET);
  line = Getline(str);
  file = Getfile(str);

  /* Skip over any leading whitespace */
  skip_whitespace(str, 0);

  /* Now look for a macro name */
  macroname = NewStringEmpty();
  copy_location(str, macroname);
  while ((c = Getc(str)) != EOF) {
    if (c == '(') {
      argstr = NewStringEmpty();
      copy_location(str, argstr);
      /* It is a macro.  Go extract its argument string */
      while ((c = Getc(str)) != EOF) {
	if (c == ')')
	  break;
	else
	  Putc(c, argstr);
      }
      if (c != ')') {
	Swig_error(Getfile(argstr), Getline(argstr), "Missing \')\' in macro parameters\n");
	goto macro_error;
      }
      break;
    } else if (isidchar(c) || (c == '%')) {
      Putc(c, macroname);
    } else if (isspace(c)) {
      break;
    } else if (c == '\\') {
      c = Getc(str);
      if (c != '\n') {
	Ungetc(c, str);
	Ungetc('\\', str);
	break;
      }
    } else {
      Ungetc(c, str);
      break;
    }
  }
  if (!swigmacro)
    skip_whitespace(str, 0);
  macrovalue = NewStringEmpty();
  copy_location(str, macrovalue);
  while ((c = Getc(str)) != EOF) {
    Putc(c, macrovalue);
  }

  /* If there are any macro arguments, convert into a list */
  if (argstr) {
    String *argname, *varargname;
    arglist = NewList();
    Seek(argstr, 0, SEEK_SET);
    argname = NewStringEmpty();
    while ((c = Getc(argstr)) != EOF) {
      if (c == ',') {
	varargname = Macro_vararg_name(argname, argstr);
	if (varargname) {
	  Delete(varargname);
	  Swig_error(Getfile(argstr), Getline(argstr), "Variable length macro argument must be last parameter\n");
	} else {
	  Append(arglist, argname);
	}
	Delete(argname);
	argname = NewStringEmpty();
      } else if (isidchar(c) || (c == '.')) {
	Putc(c, argname);
      } else if (!(isspace(c) || (c == '\\'))) {
	Delete(argname);
	Swig_error(Getfile(argstr), Getline(argstr), "Illegal character in macro argument name\n");
	goto macro_error;
      }
    }
    if (Len(argname)) {
      /* Check for varargs */
      varargname = Macro_vararg_name(argname, argstr);
      if (varargname) {
	Append(arglist, varargname);
	Delete(varargname);
	varargs = 1;
      } else {
	Append(arglist, argname);
      }
    }
    Delete(argname);
  }

  if (!swigmacro) {
    Replace(macrovalue, "\\\n", " ", DOH_REPLACE_NOQUOTE);
  }

  /* Look for special # substitutions.   We only consider # that appears
     outside of quotes and comments */

  {
    int state = 0;
    char *cc = Char(macrovalue);
    while (*cc) {
      switch (state) {
      case 0:
	if (*cc == '#')
	  *cc = '\001';
	else if (*cc == '/')
	  state = 10;
	else if (*cc == '\'')
	  state = 20;
	else if (*cc == '\"')
	  state = 30;
	break;
      case 10:
	if (*cc == '*')
	  state = 11;
	else if (*cc == '/')
	  state = 15;
	else {
	  state = 0;
	  cc--;
	}
	break;
      case 11:
	if (*cc == '*')
	  state = 12;
	break;
      case 12:
	if (*cc == '/')
	  state = 0;
	else if (*cc != '*')
	  state = 11;
	break;
      case 15:
	if (*cc == '\n')
	  state = 0;
	break;
      case 20:
	if (*cc == '\'')
	  state = 0;
	if (*cc == '\\')
	  state = 21;
	break;
      case 21:
	state = 20;
	break;
      case 30:
	if (*cc == '\"')
	  state = 0;
	if (*cc == '\\')
	  state = 31;
	break;
      case 31:
	state = 30;
	break;
      default:
	break;
      }
      cc++;
    }
  }

  /* Get rid of whitespace surrounding # */
  /*  Replace(macrovalue,"#","\001",DOH_REPLACE_NOQUOTE); */
  while (strstr(Char(macrovalue), "\001 ")) {
    Replace(macrovalue, "\001 ", "\001", DOH_REPLACE_ANY);
  }
  while (strstr(Char(macrovalue), " \001")) {
    Replace(macrovalue, " \001", "\001", DOH_REPLACE_ANY);
  }
  /* Replace '##' with a special token */
  Replace(macrovalue, "\001\001", "\002", DOH_REPLACE_ANY);
  /* Replace '#@' with a special token */
  Replace(macrovalue, "\001@", "\004", DOH_REPLACE_ANY);
  /* Replace '##@' with a special token */
  Replace(macrovalue, "\002@", "\005", DOH_REPLACE_ANY);

  /* Go create the macro */
  macro = NewHash();
  Setattr(macro, kpp_name, macroname);

  if (arglist) {
    Setattr(macro, kpp_args, arglist);
    Delete(arglist);
    if (varargs) {
      Setattr(macro, kpp_varargs, "1");
    }
  }
  Setattr(macro, kpp_value, macrovalue);
  Setline(macro, line);
  Setfile(macro, file);
  if (swigmacro) {
    Setattr(macro, kpp_swigmacro, "1");
  }
  symbols = Getattr(cpp, kpp_symbols);
  if ((m1 = Getattr(symbols, macroname))) {
    if (!Checkattr(m1, kpp_value, macrovalue)) {
      Swig_error(Getfile(macroname), Getline(macroname), "Macro '%s' redefined,\n", macroname);
      Swig_error(Getfile(m1), Getline(m1), "previous definition of '%s'.\n", macroname);
      goto macro_error;
    }
  } else {
    Setattr(symbols, macroname, macro);
    Delete(macro);
  }

  Delete(macroname);
  Delete(macrovalue);

  Delete(str);
  Delete(argstr);
  return macro;

macro_error:
  Delete(str);
  Delete(argstr);
  Delete(arglist);
  Delete(macroname);
  Delete(macrovalue);
  return 0;
}

/* -----------------------------------------------------------------------------
 * Preprocessor_undef()
 *
 * Undefines a macro.
 * ----------------------------------------------------------------------------- */
void Preprocessor_undef(const_String_or_char_ptr str) {
  Hash *symbols;
  assert(cpp);
  symbols = Getattr(cpp, kpp_symbols);
  Delattr(symbols, str);
}

/* -----------------------------------------------------------------------------
 * Preprocessor_defined()
 *
 * Check if a macro is defined.
 * ----------------------------------------------------------------------------- */
int Preprocessor_defined(const_String_or_char_ptr str) {
  Hash *symbols;
  assert(cpp);
  symbols = Getattr(cpp, kpp_symbols);
  return Getattr(symbols, str) != NULL;
}

/* -----------------------------------------------------------------------------
 * find_args()
 *
 * Isolates macro arguments and returns them in a list.   For each argument,
 * leading and trailing whitespace is stripped (ala K&R, pg. 230).
 * ----------------------------------------------------------------------------- */
static List *find_args(String *s, int ismacro, String *macro_name) {
  List *args;
  String *str;
  int c, level;
  long pos;

  /* Create a new list */
  args = NewList();
  copy_location(s, args);

  /* First look for a '(' */
  pos = Tell(s);
  skip_whitespace(s, 0);

  /* Now see if the next character is a '(' */
  c = Getc(s);
  if (c != '(') {
    /* Not a macro, bail out now! */
    assert(pos != -1);
    (void)Seek(s, pos, SEEK_SET);
    Delete(args);
    return 0;
  }
  c = Getc(s);
  /* Okay.  This appears to be a macro so we will start isolating arguments */
  while (c != EOF) {
    if (isspace(c)) {
      skip_whitespace(s, 0);	/* Skip leading whitespace */
      c = Getc(s);
    }
    str = NewStringEmpty();
    copy_location(s, str);
    level = 0;
    while (c != EOF) {
      if (c == '\"') {
	Putc(c, str);
	skip_tochar(s, '\"', str);
	c = Getc(s);
	continue;
      } else if (c == '\'') {
	Putc(c, str);
	skip_tochar(s, '\'', str);
	c = Getc(s);
	continue;
      } else if (c == '/') {
        /* Ensure comments are ignored by eating up the characters */
        c = Getc(s);
        /* Handle / * ... * / type comments (multi-line) */
        if (c == '*') {
          while ((c = Getc(s)) != EOF) {
            if (c == '*') {
              c = Getc(s);
              if (c == '/' || c == EOF)
                break;
            }
          }
          c = Getc(s);
          continue;
        }
        /* Handle // ... type comments (single-line) */
        if (c == '/') {
          while ((c = Getc(s)) != EOF) {
            if (c == '\n') {
                break;
            }
          }
          c = Getc(s);
          continue;
        }
        /* ensure char is available in the stream as this was not a comment*/
        Ungetc(c, s);
        c = '/';
      }
      if ((c == ',') && (level == 0))
	break;
      if ((c == ')') && (level == 0))
	break;
      Putc(c, str);
      if (c == '(')
	level++;
      if (c == ')')
	level--;
      c = Getc(s);
    }
    if (level > 0) {
      goto unterm;
    }
    Chop(str);
    Append(args, str);
    Delete(str);
    if (c == ')')
      return args;
    c = Getc(s);
  }
unterm:
  if (ismacro)
    Swig_error(Getfile(args), Getline(args), "Unterminated call invoking macro '%s'\n", macro_name);
  else
    Swig_error(Getfile(args), Getline(args), "Unterminated call to '%s'\n", macro_name);
  return args;
}

/* -----------------------------------------------------------------------------
 * DOH *get_filename()
 *
 * Read a filename from str.   A filename can be enclosed in quotes, angle brackets,
 * or bare.
 * ----------------------------------------------------------------------------- */

static String *get_filename(String *str, int *sysfile) {
  String *fn;
  int c;

  fn = NewStringEmpty();
  copy_location(str, fn);
  c = Getc(str);
  *sysfile = 0;
  if (c == '\"') {
    while (((c = Getc(str)) != EOF) && (c != '\"'))
      Putc(c, fn);
  } else if (c == '<') {
    *sysfile = 1;
    while (((c = Getc(str)) != EOF) && (c != '>'))
      Putc(c, fn);
  } else {
    String *preprocessed_str;
    Putc(c, fn);
    while (((c = Getc(str)) != EOF) && (!isspace(c)))
      Putc(c, fn);
    if (isspace(c))
      Ungetc(c, str);
    preprocessed_str = Preprocessor_replace(fn, NULL);
    Seek(preprocessed_str, 0, SEEK_SET);
    Delete(fn);

    fn = NewStringEmpty();
    copy_location(preprocessed_str, fn);
    c = Getc(preprocessed_str);
    if (c == '\"') {
      while (((c = Getc(preprocessed_str)) != EOF) && (c != '\"'))
	Putc(c, fn);
    } else if (c == '<') {
      *sysfile = 1;
      while (((c = Getc(preprocessed_str)) != EOF) && (c != '>'))
	Putc(c, fn);
    } else {
      fn = Copy(preprocessed_str);
    }
    Delete(preprocessed_str);
  }
  Swig_filename_unescape(fn);
  Swig_filename_correct(fn);
  Seek(fn, 0, SEEK_SET);
  return fn;
}

static String *get_options(String *str) {
  int c;
  c = Getc(str);
  if (c == '(') {
    String *opt;
    int level = 1;
    opt = NewString("(");
    while (((c = Getc(str)) != EOF)) {
      Putc(c, opt);
      switch (c) {
	case ')':
	  level--;
	  if (!level)
	    return opt;
	  break;
	case '(':
	  level++;
	  break;
	case '"':
	  /* Skip over quoted strings */
	  while (1) {
	    c = Getc(str);
	    if (c == EOF)
	      goto bad;
	    Putc(c, opt);
	    if (c == '"')
	      break;
	    if (c == '\\') {
	      c = Getc(str);
	      if (c == EOF)
		goto bad;
	      Putc(c, opt);
	    }
	  }
	  break;
      }
    }
bad:
    Delete(opt);
    return 0;
  } else {
    Ungetc(c, str);
    return 0;
  }
}

/* -----------------------------------------------------------------------------
 * expand_macro()
 *
 * Perform macro expansion and return a new string.  Returns NULL if some sort
 * of error occurred.
 * name - name of the macro
 * args - arguments passed to the macro
 * line_file - global context, used for line/file name when reporting errors
 * ----------------------------------------------------------------------------- */

static String *expand_macro(String *name, List *args, String *line_file) {
  String *ns;
  DOH *symbols, *macro, *margs, *mvalue, *temp, *tempa, *e;
  int i, l;
  int isvarargs = 0;

  symbols = Getattr(cpp, kpp_symbols);
  if (!symbols)
    return 0;

  /* See if the name is actually defined */
  macro = Getattr(symbols, name);
  if (!macro)
    return 0;

  if (macro_level == 0) {
    /* Store the start of the macro should the macro contain __LINE__ and __FILE__ for expansion */
    macro_start_line = Getline(args ? args : line_file);
    macro_start_file = Getfile(args ? args : line_file);
  }
  macro_level++;

  if (Getattr(macro, kpp_expanded)) {
    ns = NewStringEmpty();
    Append(ns, name);
    if (args) {
      int lenargs = Len(args);
      if (lenargs)
	Putc('(', ns);
      for (i = 0; i < lenargs; i++) {
	Append(ns, Getitem(args, i));
	if (i < (lenargs - 1))
	  Putc(',', ns);
      }
      if (i)
	Putc(')', ns);
    }
    macro_level--;
    return ns;
  }

  /* Get macro arguments and value */
  mvalue = Getattr(macro, kpp_value);
  assert(mvalue);
  margs = Getattr(macro, kpp_args);

  if (args && Getattr(macro, kpp_varargs)) {
    isvarargs = 1;
    /* Variable length argument macro.  We need to collect all of the extra arguments into a single argument */
    if (Len(args) >= (Len(margs) - 1)) {
      int i;
      int vi, na;
      String *vararg = NewStringEmpty();
      vi = Len(margs) - 1;
      na = Len(args);
      for (i = vi; i < na; i++) {
	Append(vararg, Getitem(args, i));
	if ((i + 1) < na) {
	  Append(vararg, ",");
	}
      }
      /* Remove arguments */
      for (i = vi; i < na; i++) {
	Delitem(args, vi);
      }
      Append(args, vararg);
      Delete(vararg);
    }
  }

  if (args && margs && Len(margs) == 0 && Len(args) == 1 && Len(Getitem(args, 0)) == 0) {
    /* FOO() can invoke a macro defined as FOO(X) as well as one defined FOO().
     *
     * Handle this by removing the only argument if it's empty and the macro
     * expects no arguments.
     *
     * We don't need to worry about varargs here - a varargs macro will always have
     * Len(margs) >= 1, since the varargs are put in the final macro argument.
     */
    Delitem(args, 0);
  }

  /* If there are arguments, see if they match what we were given */
  if (args && (!margs || Len(margs) != Len(args))) {
    if (margs && Len(margs) > (1 + isvarargs))
      Swig_error(macro_start_file, macro_start_line, "Macro '%s' expects %d arguments\n", name, Len(margs) - isvarargs);
    else if (margs && Len(margs) == (1 + isvarargs))
      Swig_error(macro_start_file, macro_start_line, "Macro '%s' expects 1 argument\n", name);
    else
      Swig_error(macro_start_file, macro_start_line, "Macro '%s' expects no arguments\n", name);
    macro_level--;
    return 0;
  }

  /* If the macro expects arguments, but none were supplied, we leave it in place */
  if (!args && margs) {
    macro_level--;
    return NewString(name);
  }

  /* Copy the macro value */
  ns = Copy(mvalue);
  copy_location(mvalue, ns);

  /* Tag the macro as being expanded.   This is to avoid recursion in
     macro expansion */

  temp = NewStringEmpty();
  tempa = NewStringEmpty();
  if (args && margs) {
    l = Len(margs);
    for (i = 0; i < l; i++) {
      DOH *arg, *aname;
      String *reparg;
      arg = Getitem(args, i);	/* Get an argument value */
      reparg = Preprocessor_replace(arg, NULL);
      aname = Getitem(margs, i);	/* Get macro argument name */
      if (strchr(Char(ns), '\001')) {
	/* Try to replace a quoted version of the argument */
	Clear(temp);
	Clear(tempa);
	Printf(temp, "\001%s", aname);
	Printf(tempa, "\"%s\"", arg);
	Replace(ns, temp, tempa, DOH_REPLACE_ID_END);
      }
      if (strchr(Char(ns), '\002')) {
	/* Look for concatenation tokens */
	Clear(temp);
	Clear(tempa);
	Printf(temp, "\002%s", aname);
	Append(tempa, "\002\003");
	Replace(ns, temp, tempa, DOH_REPLACE_ID_END);
	Clear(temp);
	Clear(tempa);
	Printf(temp, "%s\002", aname);
	Append(tempa, "\003\002");
	Replace(ns, temp, tempa, DOH_REPLACE_ID_BEGIN);
      }

      /* Non-standard macro expansion.   The value `x` is replaced by a quoted
         version of the argument except that if the argument is already quoted
         nothing happens */

      if (strchr(Char(ns), '`')) {
	String *rep;
	char *c;
	Clear(temp);
	Printf(temp, "`%s`", aname);
	c = Char(arg);
	if (*c == '"') {
	  rep = arg;
	} else {
	  Clear(tempa);
	  Printf(tempa, "\"%s\"", arg);
	  rep = tempa;
	}
	Replace(ns, temp, rep, DOH_REPLACE_ANY);
      }

      /* Non-standard mangle expansions.  
         The #@Name is replaced by mangle_arg(Name). */
      if (strchr(Char(ns), '\004')) {
	String *marg = Swig_name_mangle_string(arg);
	Clear(temp);
	Printf(temp, "\004%s", aname);
	Replace(ns, temp, marg, DOH_REPLACE_ID_END);
	Delete(marg);
      }
      if (strchr(Char(ns), '\005')) {
	String *marg = Swig_name_mangle_string(arg);
	Clear(temp);
	Clear(tempa);
	Printf(temp, "\005%s", aname);
	Printf(tempa, "\"%s\"", marg);
	Replace(ns, temp, tempa, DOH_REPLACE_ID_END);
	Delete(marg);
      }

      if (isvarargs && i == l - 1 && Len(arg) == 0) {
	/* Zero length varargs macro argument.   We search for commas that might appear before and nuke them */
	char *a, *s, *t, *name;
	int namelen;
	s = Char(ns);
	name = Char(aname);
	namelen = Len(aname);
	a = strstr(s, name);
	while (a) {
	  char ca = a[namelen];
	  if (!isidchar((int) ca)) {
	    /* Matched the entire vararg name, not just a prefix */
	    if (a > s) {
	      t = a - 1;
	      if (*t == '\002') {
		t--;
		while (t >= s) {
		  if (isspace((int) *t))
		    t--;
		  else if (*t == ',') {
		    *t = ' ';
		  } else
		    break;
		}
	      }
	    }
	  }
	  a = strstr(a + namelen, name);
	}
      }
      /*      Replace(ns, aname, arg, DOH_REPLACE_ID); */
      Replace(ns, aname, reparg, DOH_REPLACE_ID);	/* Replace expanded args */
      Replace(ns, "\003", arg, DOH_REPLACE_ANY);	/* Replace unexpanded arg */
      Delete(reparg);
    }
  }
  Replace(ns, "\002", "", DOH_REPLACE_ANY);	/* Get rid of concatenation tokens */
  Replace(ns, "\001", "#", DOH_REPLACE_ANY);	/* Put # back (non-standard C) */
  Replace(ns, "\004", "#@", DOH_REPLACE_ANY);	/* Put # back (non-standard C) */

  /* Expand this macro even further */
  Setattr(macro, kpp_expanded, "1");

  e = Preprocessor_replace(ns, line_file);

  Delattr(macro, kpp_expanded);
  Delete(ns);

  if (Getattr(macro, kpp_swigmacro)) {
    String *g;
    String *f = NewStringEmpty();
    Seek(e, 0, SEEK_SET);
    copy_location(macro, e);
    g = Preprocessor_parse(e);

#if 0
    /* Drop the macro in place, but with a marker around it */
    Printf(f, "/*@%s,%d,%s@*/%s/*@@*/", Getfile(macro), Getline(macro), name, g);
#else
    /* Use simplified around markers to properly count lines in cscanner.c */
    if (strchr(Char(g), '\n')) {
      Printf(f, "/*@SWIG:%s,%d,%s@*/%s/*@SWIG@*/", Getfile(macro), Getline(macro), name, g);
#if 0
      Printf(f, "/*@SWIG:%s@*/%s/*@SWIG@*/", name, g);
#endif
    } else {
      Append(f, g);
    }
#endif

    Delete(g);
    Delete(e);
    e = f;
  }
  macro_level--;
  Delete(temp);
  Delete(tempa);
  return e;
}

/* -----------------------------------------------------------------------------
 * DOH *Preprocessor_replace(DOH *s, DOH *line_file)
 *
 * Performs a macro substitution on a string s.  Returns a new string with
 * substitutions applied.   This function works by walking down s and looking
 * for identifiers.   When found, a check is made to see if they are macros
 * which are then expanded.
 * ----------------------------------------------------------------------------- */

/* #define SWIG_PUT_BUFF  */

static DOH *Preprocessor_replace(DOH *s, DOH *line_file) {
  DOH *ns, *symbols, *m;
  int c, i, state = 0;
  String *id = NewStringEmpty();

  assert(cpp);
  symbols = Getattr(cpp, kpp_symbols);

  ns = NewStringEmpty();
  copy_location(s, ns);
  Seek(s, 0, SEEK_SET);

  /* Try to locate identifiers in s and replace them with macro replacements */
  while ((c = Getc(s)) != EOF) {
    switch (state) {
    case 0:
      if (isidentifier(c)) {
	Clear(id);
	Putc(c, id);
	state = 4;
      } else if (c == '%') {
	Clear(id);
	Putc(c, id);
	state = 2;
      } else if (c == '#') {
	Clear(id);
	Putc(c, id);
	state = 4;
      } else if (c == '\"') {
	Putc(c, ns);
	skip_tochar(s, '\"', ns);
      } else if (c == '\'') {
	Putc(c, ns);
	skip_tochar(s, '\'', ns);
      } else if (c == '/') {
	Putc(c, ns);
	state = 10;
      } else if (c == '\\') {
	Putc(c, ns);
	c = Getc(s);
	if (c == '\n') {
	  Putc(c, ns);
	} else {
	  Ungetc(c, s);
	}
      } else if (c == '\n') {
	Putc(c, ns);
	expand_defined_operator = 0;
      } else {
	Putc(c, ns);
      }
      break;
    case 2:
      /* Found '%#' */
      if (c == '#') {
	Putc(c, id);
	state = 4;
      } else {
	Ungetc(c, s);
	state = 4;
      }
      break;
    case 4:			/* An identifier */
      if (isidchar(c)) {
	Putc(c, id);
	state = 4;
      } else {
	/* We found the end of a valid identifier */
	Ungetc(c, s);
	/* See if this is the special "defined" operator */
       	if (Equal(kpp_defined, id)) {
	  if (expand_defined_operator) {
	    int lenargs = 0;
	    DOH *args = 0;
	    /* See whether or not a parenthesis has been used */
	    skip_whitespace(s, 0);
	    c = Getc(s);
	    if (c == '(') {
	      Ungetc(c, s);
	      args = find_args(s, 0, kpp_defined);
	    } else if (isidchar(c)) {
	      DOH *arg = NewStringEmpty();
	      args = NewList();
	      Putc(c, arg);
	      while (((c = Getc(s)) != EOF)) {
		if (!isidchar(c)) {
		  Ungetc(c, s);
		  break;
		}
		Putc(c, arg);
	      }
	      if (Len(arg))
		Append(args, arg);
	      Delete(arg);
	    } else {
	      Seek(s, -1, SEEK_CUR);
	    }
	    lenargs = Len(args);
	    if ((!args) || (!lenargs)) {
	      /* This is not a defined() operator. */
	      Append(ns, id);
	      state = 0;
	      break;
	    }
	    for (i = 0; i < lenargs; i++) {
	      DOH *o = Getitem(args, i);
	      if (!Getattr(symbols, o)) {
		break;
	      }
	    }
	    if (i < lenargs)
	      Putc('0', ns);
	    else
	      Putc('1', ns);
	    Delete(args);
	  } else {
	    Append(ns, id);
	  }
	  state = 0;
	  break;
	} else if (Equal(kpp_LINE, id)) {
	  Printf(ns, "%d", macro_level > 0 ? macro_start_line : Getline(s));
	  state = 0;
	  break;
	} else if (Equal(kpp_FILE, id)) {
	  String *fn = Copy(macro_level > 0 ? macro_start_file : Getfile(s));
	  Replaceall(fn, "\\", "\\\\");
	  Printf(ns, "\"%s\"", fn);
	  Delete(fn);
	  state = 0;
	  break;
	} else if (Equal(kpp_hash_if, id) || Equal(kpp_hash_elif, id)) {
	  expand_defined_operator = 1;
	  Append(ns, id);
	} else if (Equal("#ifdef", id) || Equal("#ifndef", id)) {
	  /* Evaluate the defined-ness check and substitute `#if 0` or `#if 1`. */
	  int allow = 0;
	  skip_whitespace(s, 0);
	  c = Getc(s);
	  if (isidchar(c)) {
	    DOH *arg = NewStringEmpty();
	    Putc(c, arg);
	    while (((c = Getc(s)) != EOF)) {
	      if (!isidchar(c)) {
		Ungetc(c, s);
		break;
	      }
	      Putc(c, arg);
	    }
	    if (Equal("#ifdef", id)) {
	      if (Getattr(symbols, arg)) allow = 1;
	    } else {
	      if (!Getattr(symbols, arg)) allow = 1;
	    }
	    Delete(arg);
	  } else {
	    Ungetc(c, s);
	    Swig_error(Getfile(s), Getline(s), "Missing identifier for %s.\n", id);
	  }
	  if (allow) {
	    Append(ns, "#if 1");
	  } else {
	    Append(ns, "#if 0");
	  }
	} else if ((m = Getattr(symbols, id))) {
	  /* See if the macro is defined in the preprocessor symbol table */
	  DOH *args = 0;
	  DOH *e;
	  int macro_additional_lines = 0;
	  /* See if the macro expects arguments */
	  if (Getattr(m, kpp_args)) {
	    /* Yep.  We need to go find the arguments and do a substitution */
	    int line = Getline(s);
	    args = find_args(s, 1, id);
	    macro_additional_lines = Getline(s) - line;
	    assert(macro_additional_lines >= 0);
	  } else {
	    args = 0;
	  }
	  e = expand_macro(id, args, s);
	  if (e) {
	    Append(ns, e);
	  }
	  while (macro_additional_lines--) {
	    Putc('\n', ns);
	  }
	  Delete(e);
	  Delete(args);
	} else {
	  Append(ns, id);
	}
	state = 0;
      }
      break;
    case 10:
      if (c == '/')
	state = 11;
      else if (c == '*')
	state = 12;
      else {
	Ungetc(c, s);
	state = 0;
	break;
      }
      Putc(c, ns);
      break;
    case 11:
      /* in C++ comment */
      Putc(c, ns);
      if (c == '\n') {
	expand_defined_operator = 0;
	state = 0;
      }
      break;
    case 12:
      /* in C comment */
      Putc(c, ns);
      if (c == '*')
	state = 13;
      break;
    case 13:
      Putc(c, ns);
      if (c == '/')
	state = 0;
      else if (c != '*')
	state = 12;
      break;
    default:
      state = 0;
      break;
    }
  }

  /* Identifier at the end */
  if (state == 2 || state == 4) {
    /* See if this is the special "defined" operator */
    if (Equal(kpp_defined, id)) {
      Swig_error(Getfile(s), Getline(s), "No arguments given to defined()\n");
    } else if (Equal(kpp_LINE, id)) {
      Printf(ns, "%d", macro_level > 0 ? macro_start_line : Getline(s));
    } else if (Equal(kpp_FILE, id)) {
      String *fn = Copy(macro_level > 0 ? macro_start_file : Getfile(s));
      Replaceall(fn, "\\", "\\\\");
      Printf(ns, "\"%s\"", fn);
      Delete(fn);
    } else if ((m = Getattr(symbols, id))) {
      /* Yes.  There is a macro here */
      /* If it expects arguments, they must come from `line_file` */
      DOH *args = 0;
      DOH *e;
      int macro_additional_lines = 0;
      /* See if the macro expects arguments */
      if (Getattr(m, kpp_args) && line_file) {
        /* Yep.  We need to go find the arguments and do a substitution */
        int line = Getline(line_file);
        args = find_args(line_file, 1, id);
        macro_additional_lines = Getline(line_file) - line;
        assert(macro_additional_lines >= 0);
      } else {
        args = 0;
      }
      e = expand_macro(id, args, s);
      if (e) {
	Append(ns, e);
      }
      while (macro_additional_lines--) {
	Putc('\n', ns);
      }
      Delete(e);
      Delete(args);
    } else {
      Append(ns, id);
    }
  }
  Delete(id);
  return ns;
}


/* -----------------------------------------------------------------------------
 * int checkpp_id(DOH *s)
 *
 * Checks the string s to see if it contains any unresolved identifiers.  This
 * function contains the heuristic that determines whether or not a macro
 * definition passes through the preprocessor as a constant declaration.
 * ----------------------------------------------------------------------------- */
static int checkpp_id(DOH *s) {
  int c;
  int hastok = 0;
  Scanner *scan = id_scan;

  Seek(s, 0, SEEK_SET);

  Scanner_clear(scan);
  s = Copy(s);
  Seek(s, SEEK_SET, 0);
  Scanner_push(scan, s);
  while ((c = Scanner_token(scan))) {
    hastok = 1;
    if ((c == SWIG_TOKEN_ID) || (c == SWIG_TOKEN_LBRACE) || (c == SWIG_TOKEN_RBRACE))
      return 1;
  }
  if (!hastok)
    return 1;
  return 0;
}

/* addline().  Utility function for adding lines to a chunk */
static void addline(DOH *s1, DOH *s2, int allow) {
  if (allow) {
    Append(s1, s2);
  } else {
    char *c = Char(s2);
    while (*c) {
      if (*c == '\n')
	Putc('\n', s1);
      c++;
    }
  }
}

static void add_chunk(DOH *ns, DOH *chunk, int allow) {
  DOH *echunk;
  Seek(chunk, 0, SEEK_SET);
  if (allow) {
    echunk = Preprocessor_replace(chunk, NULL);
    addline(ns, echunk, allow);
    Delete(echunk);
  } else {
    addline(ns, chunk, 0);
  }
  Clear(chunk);
}

/*
  push/pop_imported(): helper functions for defining and undefining
  SWIGIMPORTED (when %importing a file).
 */
static void push_imported(void) {
  if (imported_depth == 0) {
    Preprocessor_define("SWIGIMPORTED 1", 0);
  }
  ++imported_depth;
}

static void pop_imported(void) {
  --imported_depth;
  if (imported_depth == 0) {
    Preprocessor_undef("SWIGIMPORTED");
  }
}


/* -----------------------------------------------------------------------------
 * Preprocessor_parse()
 *
 * Parses the string s.  Returns a new string containing the preprocessed version.
 *
 * Parsing rules :
 *       1.  Lines starting with # are C preprocessor directives
 *       2.  Macro expansion inside strings is not allowed
 *       3.  All code inside false conditionals is changed to blank lines
 *       4.  Code in %{, %} is not parsed because it may need to be
 *           included inline (with all preprocessor directives included).
 * ----------------------------------------------------------------------------- */

String *Preprocessor_parse(String *s) {
  String *ns;			/* New string containing the preprocessed text */
  String *chunk, *decl;
  Hash *symbols;
  String *id = 0, *value = 0, *comment = 0;
  int i, state, e, c;
  int start_line = 0;
  int allow = 1;
  int level = 0;
  int dlevel = 0;
  int filelevel = 0;
  int mask = 0;
  int start_level = 0;
  int cpp_lines = 0;
  int cond_lines[256];

  /* Blow away all carriage returns */
  Replace(s, "\015", "", DOH_REPLACE_ANY);

  ns = NewStringEmpty();	/* Return result */

  decl = NewStringEmpty();
  id = NewStringEmpty();
  value = NewStringEmpty();
  comment = NewStringEmpty();
  chunk = NewStringEmpty();
  copy_location(s, chunk);
  copy_location(s, ns);
  symbols = Getattr(cpp, kpp_symbols);

  state = 0;
  while ((c = Getc(s)) != EOF) {
    switch (state) {
    case 0:			/* Initial state - in first column */
      /* Look for C preprocessor directives.   Otherwise, go directly to state 1 */
      if (c == '#') {
	copy_location(s, chunk);
	add_chunk(ns, chunk, allow);
	cpp_lines = 1;
	state = 40;
      } else if (isspace(c)) {
	Putc(c, chunk);
	skip_whitespace(s, chunk);
      } else {
	state = 1;
	Ungetc(c, s);
      }
      break;
    case 1: {			/* Non-preprocessor directive */
      /* Look for SWIG directives */
state1:
      if (c == '%') {
	state = 100;
	break;
      }
      Putc(c, chunk);
      if (c == '\n')
	state = 0;
      else if (c == '\"') {
	start_line = Getline(s);
	if (skip_tochar(s, '\"', chunk) < 0) {
	  Swig_error(Getfile(s), start_line, "Unterminated string constant\n");
	}
      } else if (c == '\'') {
	start_line = Getline(s);
	if (skip_tochar(s, '\'', chunk) < 0) {
	  Swig_error(Getfile(s), start_line, "Unterminated character constant\n");
	}
      } else if (c == '/')
	state = 30;		/* Comment */
      break;
    }

    case 30:			/* Possibly a comment string of some sort */
      start_line = Getline(s);
      if (c == '/')
	state = 31;
      else if (c == '*')
	state = 32;
      else {
	state = 1;
	goto state1; /* Process this character the same as if it wasn't preceded by a `/`. */
      }
      Putc(c, chunk);
      break;
    case 31:
      Putc(c, chunk);
      if (c == '\n')
	state = 0;
      break;
    case 32:
      Putc(c, chunk);
      if (c == '*')
	state = 33;
      break;
    case 33:
      Putc(c, chunk);
      if (c == '/')
	state = 1;
      else if (c != '*')
	state = 32;
      break;

    case 40:			/* Start of a C preprocessor directive */
      if (c == '\n') {
	Putc('\n', chunk);
	state = 0;
      } else if (isspace(c)) {
	state = 40;
      } else {
	/* Got the start of a preprocessor directive */
	Ungetc(c, s);
	Clear(id);
	copy_location(s, id);
	state = 41;
      }
      break;

    case 41:			/* Build up the name of the preprocessor directive */
      if ((isspace(c) || (!isidchar(c)))) {
	Clear(value);
	Clear(comment);
	if (c == '\n') {
	  Ungetc(c, s);
	  state = 50;
	} else {
	  state = 42;
	  if (!isspace(c)) {
	    Ungetc(c, s);
	  }
	}

	copy_location(s, value);
	break;
      }
      Putc(c, id);
      break;

    case 42:			/* Strip any leading space after the preprocessor directive (before preprocessor value) */
      if (isspace(c)) {
	if (c == '\n') {
	  Ungetc(c, s);
	  state = 50;
	}
	break;
      }
      state = 43;
      /* FALL THRU */

    case 43:
      /* Get preprocessor value */
      if (c == '\n') {
	Ungetc(c, s);
	state = 50;
      } else if (c == '/') {
	state = 45;
      } else if (c == '\"') {
	Putc(c, value);
	skip_tochar(s, '\"', value);
      } else if (c == '\'') {
	Putc(c, value);
	skip_tochar(s, '\'', value);
      } else {
	Putc(c, value);
	if (c == '\\')
	  state = 44;
      }
      break;

    case 44:
      if (c == '\n') {
	Putc(c, value);
	cpp_lines++;
      } else {
	Ungetc(c, s);
      }
      state = 43;
      break;

      /* States 45-48 are used to remove, but retain comments from macro values.  The comments
         will be placed in the output in an alternative form */

    case 45:
      if (c == '/')
	state = 46;
      else if (c == '*')
	state = 47;
      else if (c == '\n') {
	Putc('/', value);
	Ungetc(c, s);
	state = 50;
      } else {
	Putc('/', value);
	Putc(c, value);
	state = 43;
      }
      break;
    case 46: /* in C++ comment */
      if (c == '\n') {
	Ungetc(c, s);
	state = 50;
      } else
	Putc(c, comment);
      break;
    case 47: /* in C comment */
      if (c == '*')
	state = 48;
      else
	Putc(c, comment);
      break;
    case 48:
      if (c == '/')
	state = 43;
      else if (c == '*')
	Putc(c, comment);
      else {
	Putc('*', comment);
	Putc(c, comment);
	state = 47;
      }
      break;
    case 50:
      /* Check for various preprocessor directives */
      Chop(value);
      if (Equal(id, kpp_define)) {
	if (allow) {
	  DOH *m, *v, *v1;
	  Seek(value, 0, SEEK_SET);
	  m = Preprocessor_define(value, 0);
	  if ((m) && !(Getattr(m, kpp_args))) {
	    v = Copy(Getattr(m, kpp_value));
	    copy_location(m, v);
	    if (Len(v)) {
	      Swig_error_silent(1);
	      v1 = Preprocessor_replace(v, NULL);
	      Swig_error_silent(0);
	      /*              Printf(stdout,"checking '%s'\n", v1); */
	      if (!checkpp_id(v1)) {
		if (Len(comment) == 0)
		  Printf(ns, "%%constant %s = %s;\n", Getattr(m, kpp_name), v1);
		else
		  Printf(ns, "%%constant %s = %s; /*%s*/\n", Getattr(m, kpp_name), v1, comment);
		cpp_lines--;
	      }
	      Delete(v1);
	    }
	    Delete(v);
	  }
	}
      } else if (Equal(id, kpp_undef)) {
	if (allow)
	  Preprocessor_undef(value);
      } else if (Equal(id, kpp_ifdef)) {
	cond_lines[level] = Getline(id);
	level++;
	if (allow) {
	  start_level = level;
	  if (Len(value) > 0) {
	    /* See if the identifier is in the hash table */
	    if (!Getattr(symbols, value))
	      allow = 0;
	  } else {
	    Swig_error(Getfile(s), Getline(id), "Missing identifier for #ifdef.\n");
	    allow = 0;
	  }
	  mask = 1;
	}
      } else if (Equal(id, kpp_ifndef)) {
	cond_lines[level] = Getline(id);
	level++;
	if (allow) {
	  start_level = level;
	  if (Len(value) > 0) {
	    /* See if the identifier is in the hash table */
	    if (Getattr(symbols, value))
	      allow = 0;
	  } else {
	    Swig_error(Getfile(s), Getline(id), "Missing identifier for #ifndef.\n");
	    allow = 0;
	  }
	  mask = 1;
	}
      } else if (Equal(id, kpp_else)) {
	if (level <= 0) {
	  Swig_error(Getfile(s), Getline(id), "Misplaced #else.\n");
	} else {
	  cond_lines[level - 1] = Getline(id);
	  if (Len(value) != 0)
	    Swig_warning(WARN_PP_UNEXPECTED_TOKENS, Getfile(s), Getline(id), "Unexpected tokens after #else directive.\n");
	  if (allow) {
	    allow = 0;
	    mask = 0;
	  } else if (level == start_level) {
	    allow = 1 * mask;
	  }
	}
      } else if (Equal(id, kpp_endif)) {
	level--;
	if (level < 0) {
	  Swig_error(Getfile(id), Getline(id), "Extraneous #endif.\n");
	  level = 0;
	} else {
	  if (level < start_level) {
	    if (Len(value) != 0)
	      Swig_warning(WARN_PP_UNEXPECTED_TOKENS, Getfile(s), Getline(id), "Unexpected tokens after #endif directive.\n");
	    allow = 1;
	    start_level--;
	  }
	}
      } else if (Equal(id, kpp_if)) {
	cond_lines[level] = Getline(id);
	level++;
	if (allow) {
	  int val;
	  String *sval;
	  expand_defined_operator = 1;
	  sval = Preprocessor_replace(value, NULL);
	  start_level = level;
	  Seek(sval, 0, SEEK_SET);
	  /*      Printf(stdout,"Evaluating '%s'\n", sval); */
	  if (Len(sval) > 0) {
	    val = Preprocessor_expr(sval, &e);
	    if (e) {
	      const char *msg = Preprocessor_expr_error();
	      Seek(value, 0, SEEK_SET);
	      Swig_warning(WARN_PP_EVALUATION, Getfile(value), Getline(value), "Could not evaluate expression '%s'\n", value);
	      if (msg)
		Swig_warning(WARN_PP_EVALUATION, Getfile(value), Getline(value), "%s\n", msg);
	      allow = 0;
	    } else {
	      if (val == 0)
		allow = 0;
	    }
	  } else {
	    Swig_error(Getfile(s), Getline(id), "Missing expression for #if.\n");
	    allow = 0;
	  }
	  expand_defined_operator = 0;
	  mask = 1;
	}
      } else if (Equal(id, kpp_elif)) {
	if (level == 0) {
	  Swig_error(Getfile(s), Getline(id), "Misplaced #elif.\n");
	} else {
	  cond_lines[level - 1] = Getline(id);
	  if (allow) {
	    allow = 0;
	    mask = 0;
	  } else if (level == start_level) {
	    int val;
	    String *sval;
	    expand_defined_operator = 1;
	    sval = Preprocessor_replace(value, NULL);
	    Seek(sval, 0, SEEK_SET);
	    if (Len(sval) > 0) {
	      val = Preprocessor_expr(sval, &e);
	      if (e) {
		const char *msg = Preprocessor_expr_error();
		Seek(value, 0, SEEK_SET);
		Swig_warning(WARN_PP_EVALUATION, Getfile(value), Getline(value), "Could not evaluate expression '%s'\n", value);
		if (msg)
		  Swig_warning(WARN_PP_EVALUATION, Getfile(value), Getline(value), "%s\n", msg);
		allow = 0;
	      } else {
		if (val)
		  allow = 1 * mask;
		else
		  allow = 0;
	      }
	    } else {
	      Swig_error(Getfile(s), Getline(id), "Missing expression for #elif.\n");
	      allow = 0;
	    }
	    expand_defined_operator = 0;
	  }
	}
      } else if (Equal(id, kpp_warning)) {
	if (allow) {
	  Swig_warning(WARN_PP_CPP_WARNING, Getfile(s), Getline(id), "CPP #warning, \"%s\".\n", value);
	}
      } else if (Equal(id, kpp_error)) {
	if (allow) {
	  if (error_as_warning) {
	    Swig_warning(WARN_PP_CPP_ERROR, Getfile(s), Getline(id), "CPP #error \"%s\".\n", value);
	  } else {
	    Swig_error(Getfile(s), Getline(id), "CPP #error \"%s\". Use the -cpperraswarn option to continue swig processing.\n", value);
	  }
	}
      } else if (Equal(id, kpp_line)) {
      } else if (Equal(id, kpp_include)) {
	if (((include_all) || (import_all)) && (allow)) {
	  String *s1, *s2, *fn;
	  String *dirname;
	  int sysfile = 0;
	  if (include_all && import_all) {
	    Swig_warning(WARN_PP_INCLUDEALL_IMPORTALL, Getfile(s), Getline(id), "Both includeall and importall are defined: using includeall.\n");
	    import_all = 0;
	  }
	  Seek(value, 0, SEEK_SET);
	  fn = get_filename(value, &sysfile);
	  s1 = cpp_include(fn, sysfile);
	  if (s1) {
	    if (include_all)
	      Printf(ns, "%%includefile \"%s\" %%beginfile\n", Swig_filename_escape(Swig_last_file()));
	    else if (import_all) {
	      Printf(ns, "%%importfile \"%s\" %%beginfile\n", Swig_filename_escape(Swig_last_file()));
	      push_imported();
	    }

	    /* See if the filename has a directory component */
	    dirname = Swig_file_dirname(Swig_last_file());
	    if (sysfile || !Len(dirname)) {
	      Delete(dirname);
	      dirname = 0;
	    }
	    if (dirname) {
	      int len = Len(dirname);
	      Delslice(dirname, len - 1, len); /* Kill trailing directory delimiter */
	      Swig_push_directory(dirname);
	    }
	    s2 = Preprocessor_parse(s1);
	    addline(ns, s2, allow);
	    Append(ns, "%endoffile");
	    if (dirname) {
	      Swig_pop_directory();
	    }
	    if (import_all) {
	      pop_imported();
	    }
	    Delete(s2);
	    Delete(dirname);
	    Delete(s1);
	  }
	  Delete(fn);
	}
      } else if (Equal(id, kpp_pragma)) {
	if (Strncmp(value, "SWIG ", 5) == 0) {
	  char *c = Char(value) + 5;
	  while (*c && (isspace((int) *c)))
	    c++;
	  if (*c) {
	    if (strncmp(c, "nowarn=", 7) == 0) {
	      String *val = NewString(c + 7);
	      String *nowarn = Preprocessor_replace(val, NULL);
	      Swig_warnfilter(nowarn, 1);
	      Delete(nowarn);
	      Delete(val);
	    } else if (strncmp(c, "cpperraswarn=", 13) == 0) {
	      error_as_warning = atoi(c + 13);
	    } else {
              Swig_error(Getfile(s), Getline(id), "Unknown SWIG pragma: %s\n", c);
            }
	  }
	}
      } else if (Equal(id, kpp_level)) {
	Swig_error(Getfile(s), Getline(id), "cpp debug: level = %d, startlevel = %d\n", level, start_level);
      } else if (Equal(id, "")) {
	/* Null directive */
      } else if (is_digits(id)) {
	/* A gcc linemarker of the form '# linenum filename flags' (resulting from running gcc -E) */
      } else {
	/* Ignore unknown preprocessor directives which are inside an inactive
	 * conditional (github issue #394). */
	if (allow)
	  Swig_error(Getfile(s), Getline(id), "Unknown SWIG preprocessor directive: %s (if this is a block of target language code, delimit it with %%{ and %%})\n", id);
      }
      for (i = 0; i < cpp_lines; i++)
	Putc('\n', ns);
      state = 0;
      break;

      /* SWIG directives  */
    case 100:
      /* %{,%} block  */
      if (c == '{') {
	start_line = Getline(s);
	copy_location(s, chunk);
	add_chunk(ns, chunk, allow);
	Putc('%', chunk);
	Putc(c, chunk);
	state = 105;
      }
      /* %#cpp -  an embedded C preprocessor directive (we strip off the %)  */
      else if (c == '#') {
	add_chunk(ns, chunk, allow);
	Putc(c, chunk);
	state = 107;
      } else if (isidentifier(c)) {
	Clear(decl);
	Putc('%', decl);
	Putc(c, decl);
	state = 110;
      } else {
	Putc('%', chunk);
	Putc(c, chunk);
	state = 1;
      }
      break;

    case 105:
      Putc(c, chunk);
      if (c == '%')
	state = 106;
      break;

    case 106:
      Putc(c, chunk);
      if (c == '}') {
	state = 1;
	addline(ns, chunk, allow);
	Clear(chunk);
	copy_location(s, chunk);
      } else {
	state = 105;
      }
      break;

    case 107:
      Putc(c, chunk);
      if (c == '\n') {
	addline(ns, chunk, allow);
	Clear(chunk);
	state = 0;
      } else if (c == '\\') {
	state = 108;
      }
      break;

    case 108:
      Putc(c, chunk);
      state = 107;
      break;

    case 110:
      if (!isidchar(c)) {
	Ungetc(c, s);
	/* Look for common SWIG directives  */
	if (Equal(decl, kpp_dinclude) || Equal(decl, kpp_dimport)) {
	  /* Got some kind of file inclusion directive, eg: %import(option1="value1") "filename" */
	  if (allow) {
	    DOH *s1, *s2, *fn, *opt;
	    String *options_whitespace = NewStringEmpty();
	    String *filename_whitespace = NewStringEmpty();
	    int sysfile = 0;

	    skip_whitespace(s, options_whitespace);
	    opt = get_options(s);

	    skip_whitespace(s, filename_whitespace);
	    fn = get_filename(s, &sysfile);
	    s1 = cpp_include(fn, sysfile);
	    if (s1) {
	      String *dirname;
	      copy_location(s, chunk);
	      add_chunk(ns, chunk, allow);
	      Printf(ns, "%sfile%s%s%s\"%s\" %%beginfile\n", decl, options_whitespace, opt, filename_whitespace, Swig_filename_escape(Swig_last_file()));
	      if (Equal(decl, kpp_dimport)) {
		push_imported();
	      }
	      dirname = Swig_file_dirname(Swig_last_file());
	      if (sysfile || !Len(dirname)) {
		Delete(dirname);
		dirname = 0;
	      }
	      if (dirname) {
		int len = Len(dirname);
		Delslice(dirname, len - 1, len); /* Kill trailing directory delimiter */
		Swig_push_directory(dirname);
	      }
	      s2 = Preprocessor_parse(s1);
	      if (dirname) {
		Swig_pop_directory();
	      }
	      if (Equal(decl, kpp_dimport)) {
		pop_imported();
	      }
	      addline(ns, s2, allow);
	      Append(ns, "%endoffile");
	      Delete(s2);
	      Delete(dirname);
	      Delete(s1);
	    }
	    Delete(fn);
	    Delete(filename_whitespace);
	    Delete(options_whitespace);
	  }
	  state = 1;
	} else if (Equal(decl, kpp_dbeginfile)) {
          /* Got an internal directive marking the beginning of an included file: %beginfile ... %endoffile */
          filelevel++;
          start_line = Getline(s);
          copy_location(s, chunk);
          add_chunk(ns, chunk, allow);
          Append(chunk, decl);
          state = 120;
	} else if (Equal(decl, kpp_dline)) {
	  /* Got a line directive  */
	  state = 1;
	} else if (Equal(decl, kpp_ddefine)) {
	  /* Got a define directive  */
	  dlevel++;
	  copy_location(s, chunk);
	  add_chunk(ns, chunk, allow);
	  Clear(value);
	  copy_location(s, value);
	  state = 150;
	} else {
	  Append(chunk, decl);
	  state = 1;
	}
      } else {
	Putc(c, decl);
      }
      break;

      /* Searching for the end of a %beginfile block */
    case 120:
      Putc(c, chunk);
      if (c == '%') {
        const char *bf = "beginfile";
        const char *ef = "endoffile";
        char statement[10];
        int i = 0;
        for (i = 0; i < 9;) {
          c = Getc(s);
          Putc(c, chunk);
          statement[i++] = (char)c;
	  if (strncmp(statement, bf, i) && strncmp(statement, ef, i))
	    break;
	}
	c = Getc(s);
	Ungetc(c, s);
        if ((i == 9) && (isspace(c))) {
	  if (strncmp(statement, bf, i) == 0) {
	    ++filelevel;
	  } else if (strncmp(statement, ef, i) == 0) {
            --filelevel;
            if (!filelevel) {
              /* Reached end of included file */
              addline(ns, chunk, allow);
              Clear(chunk);
              copy_location(s, chunk);
              state = 1;
            }
          }
        }
      }
      break;

      /* Searching for the end of a %define statement  */
    case 150:
      Putc(c, value);
      if (c == '%') {
	const char *ed = "enddef";
	const char *df = "define";
	char statement[7];
	int i = 0;
	for (i = 0; i < 6;) {
	  c = Getc(s);
	  Putc(c, value);
	  statement[i++] = (char)c;
	  if (strncmp(statement, ed, i) && strncmp(statement, df, i))
	    break;
	}
	c = Getc(s);
	Ungetc(c, s);
	if ((i == 6) && (isspace(c))) {
	  if (strncmp(statement, df, i) == 0) {
	    ++dlevel;
	  } else {
	    if (strncmp(statement, ed, i) == 0) {
	      --dlevel;
	      if (!dlevel) {
		/* Got the macro  */
		for (i = 0; i < 7; i++) {
		  Delitem(value, DOH_END);
		}
		if (allow) {
		  Seek(value, 0, SEEK_SET);
		  Preprocessor_define(value, 1);
		}
		addline(ns, value, 0);
		state = 0;
	      }
	    }
	  }
	}
      }
      break;
    default:
      Printf(stderr, "cpp: Invalid parser state %d\n", state);
      Exit(EXIT_FAILURE);
    }
  }
  while (level > 0) {
    Swig_error(Getfile(s), cond_lines[level - 1], "Missing #endif for conditional starting here\n");
    level--;
  }
  if (state == 120) {
    Swig_error(Getfile(s), start_line, "Missing %%endoffile for file inclusion block starting here\n");
  }
  if (state == 150) {
    Seek(value, 0, SEEK_SET);
    Swig_error(Getfile(s), Getline(value), "Missing %%enddef for macro starting here\n", Getline(value));
  }
  if ((state >= 105) && (state < 107)) {
    Swig_error(Getfile(s), start_line, "Unterminated %%{ ... %%} block\n");
  }
  if ((state >= 30) && (state < 40)) {
    Swig_error(Getfile(s), start_line, "Unterminated comment\n");
  }

  copy_location(s, chunk);
  add_chunk(ns, chunk, allow);

  /*  DelScope(scp); */
  Delete(decl);
  Delete(id);
  Delete(value);
  Delete(comment);
  Delete(chunk);

  return ns;
}
