// -*- C++ -*-
/* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
   Written by James Clark (jjc@jclark.com) */

/* Comment by rsm: shortly after label got_dot, there is a line that looks
   bogus:

	    if (!c == EOF || !csdigit(c)) break;

*/

#include "pic.h"
#include "object.h"
#include "gram.h"

// Character representing $1.  Must be illegal input character.
#define ARG1 14

declare_ptable(char)
implement_ptable(char)

PTABLE(char) macro_table;

//////////////////////////////////////////////////////////////////////
// Forward declarations for lexer
//////////////////////////////////////////////////////////////////////

// forward references to internal lexer functions
static char * process_body(const char *body);
static char * get_thru_arg(void);
static int docmp(const char *s1, int n1, const char *s2, int n2);
static int get_delimited(void);
static int get_token(int lookup_flag);
static int get_token_after_dot(int c);
static int lookup_keyword(const char *str, int len);
static void do_define(void);
static void do_undef(void);
static void interpolate_macro_with_args(const char *body);

// lexer global variables
static double token_double;
static int lookahead_token = -1;
static int token_int;
static string context_buffer;
static string old_context_buffer;
static string token_buffer;

//////////////////////////////////////////////////////////////////////
// default function members for input classes
//////////////////////////////////////////////////////////////////////

input::input() : next(0)
{
}

input::~input()
{
}

int
input::get_location (const char **, int *)
{
  return 0;
}

//////////////////////////////////////////////////////////////////////
// MACRO_INPUT class
//////////////////////////////////////////////////////////////////////

class macro_input : public input 
{
public:
  // ctor, dtor
  macro_input(const char *);
  ~macro_input();
  // public functions
  int get (void);
  int peek (void);
private:
  char *s;
  char *p;
};

macro_input::macro_input(const char *str)
{
  p = s = strsave(str);
}

macro_input::~macro_input()
{
  a_delete s;
}

int
macro_input::get(void)
{
  if (p == 0 || *p == '\0')
    return EOF;
  else
    return (unsigned char)*p++;
}

int
macro_input::peek(void)
{
  if (p == 0 || *p == '\0')
    return EOF;
  else
    return (unsigned char)*p;
}

//////////////////////////////////////////////////////////////////////
// FILE_INPUT class
//////////////////////////////////////////////////////////////////////

file_input::file_input(FILE *f, const char *fn)
: filename(fn), lineno(0), ptr("")
{
  fp = f;
}

file_input::~file_input()
{
  fclose(fp);
}

int
file_input::get(void)
{
  if (*ptr != '\0' || read_line())
    return (unsigned char)*ptr++;
  else
    return EOF;
}

int
file_input::peek(void)
{
  if (*ptr != '\0' || read_line())
    return (unsigned char)*ptr;
  else
    return EOF;
}

int
file_input::get_location (const char **fnp, int *lnp)
{
  *fnp = filename;
  *lnp = lineno;
  return 1;
}

int 
file_input::read_line(void)
{
  for (;;) 
    {
      line.clear();
      lineno++;
      for (;;) 
	{
	  int c = getc(fp);
	  if (c == EOF)
	    break;
	  else if (illegal_input_char(c))
	    lex_error("illegal input character code %1", c);
	  else 
	    {
	      line += char(c);
	      if (c == '\n') 
		break;
	    }
	}
      if (line.length() == 0)
	return 0;
      if (!(line.length() >= 3 && line[0] == '.' && line[1] == 'P'
	    && (line[2] == 'S' || line[2] == 'E' || line[2] == 'F')
	    && (line.length() == 3 || line[3] == ' ' || line[3] == '\n'
		|| compatible_flag))) 
	{
	  line += '\0';
	  ptr = line.contents();
	  return 1;
	}
    }
}

//////////////////////////////////////////////////////////////////////
// ARGUMENT_MACRO_INPUT class
//////////////////////////////////////////////////////////////////////

class argument_macro_input : public input 
{
public:
  // ctor, dtor
  argument_macro_input(const char *, int, char **);
  ~argument_macro_input();
  // public functions
  int get (void);
  int peek (void);
private:
  char *s;
  char *p;
  char *ap;
  int argc;
  char *argv[9];
};

argument_macro_input::argument_macro_input(const char *body, int ac, char **av)
: ap(0), argc(ac)
{
  for (int i = 0; i < argc; i++)
    argv[i] = av[i];
  p = s = process_body(body);
}

argument_macro_input::~argument_macro_input()
{
  for (int i = 0; i < argc; i++)
    a_delete argv[i];
  a_delete s;
}

int
argument_macro_input::get(void)
{
  if (ap) 
    {
      if (*ap != '\0')
	return (unsigned char)*ap++;
      ap = 0;
    }
  if (p == 0)
    return EOF;
  while (*p >= ARG1 && *p <= ARG1 + 8) 
    {
      int i = *p++ - ARG1;
      if (i < argc && argv[i] != 0 && argv[i][0] != '\0') 
	{
	  ap = argv[i];
	  return (unsigned char)*ap++;
	}
    }
  if (*p == '\0')
    return EOF;
  return (unsigned char)*p++;
}

int
argument_macro_input::peek(void)
{
  if (ap) 
    {
      if (*ap != '\0')
	return (unsigned char)*ap;
      ap = 0;
    }
  if (p == 0)
    return EOF;
  while (*p >= ARG1 && *p <= ARG1 + 8) 
    {
      int i = *p++ - ARG1;
      if (i < argc && argv[i] != 0 && argv[i][0] != '\0') 
	{
	  ap = argv[i];
	  return (unsigned char)*ap;
	}
    }
  if (*p == '\0')
    return EOF;
  return (unsigned char)*p;
}

//////////////////////////////////////////////////////////////////////
// CHAR_INPUT class
//////////////////////////////////////////////////////////////////////

class char_input : public input 
{
public:
  // ctor
  char_input (int n);
  // public functions
  int get (void);
  int peek (void);
private:
  int c;
};

char_input::char_input(int n) : c((unsigned char)n)
{
}

int
char_input::get (void)
{
  int n = c;
  c = EOF;
  return n;
}

int
char_input::peek (void)
{
  return c;
}

//////////////////////////////////////////////////////////////////////
// FOR_INPUT class
//////////////////////////////////////////////////////////////////////

class for_input : public input 
{
public:
  //ctor, dtor
  for_input(char *, double, int, double, char *);
  ~for_input();
  // public functions
  int get (void);
  int peek (void);
private:
  char *var;
  char *body;
  double to;
  int by_is_multiplicative;
  double by;
  const char *p;
  int done_newline;
};

for_input::for_input(char *vr, double t, int bim, double b, char *bd)
: var(vr), body(bd), to(t), by_is_multiplicative(bim), by(b), p(body),
  done_newline(0)
{
}

for_input::~for_input()
{
  a_delete var;
  a_delete body;
}

int 
for_input::get(void)
{
  if (p == 0)
    return EOF;
  for (;;) 
    {
      if (*p != '\0')
	return (unsigned char)*p++;
      if (!done_newline) 
	{
	  done_newline = 1;
	  return '\n';
	}
      double val;
      if (!lookup_variable(var, &val)) 
	{
	  lex_error("body of `for' terminated enclosing block");
	  return EOF;
	}
      if (by_is_multiplicative)
	val *= by;
      else
	val += by;
      define_variable(var, val);
      if (val > to) 
	{
	  p = 0;
	  return EOF;
	}
      p = body;
      done_newline = 0;
    }
}

int
for_input::peek(void)
{
  if (p == 0)
    return EOF;
  if (*p != '\0')
    return (unsigned char)*p;
  if (!done_newline)
    return '\n';
  double val;
  if (!lookup_variable(var, &val))
    return EOF;
  if (by_is_multiplicative) 
    {
      if (val * by > to)
	return EOF;
    }
  else 
    {
      if (val + by > to)
	return EOF;
    }
  if (*body == '\0')
    return EOF;
  return (unsigned char)*body;
}

//////////////////////////////////////////////////////////////////////
// COPY_THRU_INPUT class
//////////////////////////////////////////////////////////////////////

class copy_thru_input : public input 
{
public:
  // ctor, dtor
  copy_thru_input(const char *b, const char *u);
  ~copy_thru_input();
  // public functions
  int get (void);
  int peek (void);
private:
  int done;
  char *body;
  char *until;
  const char *p;
  const char *ap;
  int argv[9];
  int argc;
  string line;
  int get_line();
  virtual int inget() = 0;
};

copy_thru_input::copy_thru_input(const char *b, const char *u)
: done(0)
{
  ap = 0;
  body = process_body(b);
  p = 0;
  until = strsave(u);
}

copy_thru_input::~copy_thru_input()
{
  a_delete body;
  a_delete until;
}

int
copy_thru_input::get(void)
{
  if (ap) 
    {
      if (*ap != '\0')
	return (unsigned char)*ap++;
      ap = 0;
    }
  for (;;) 
    {
      if (p == 0) 
	{
	  if (!get_line())
	    break;
	  p = body;
	}
      if (*p == '\0') 
	{
	  p = 0;
	  return '\n';
	}
      while (*p >= ARG1 && *p <= ARG1 + 8) 
	{
	  int i = *p++ - ARG1;
	  if (i < argc && line[argv[i]] != '\0') 
	    {
	      ap = line.contents() + argv[i];
	      return (unsigned char)*ap++;
	    }
	}
      if (*p != '\0')
	return (unsigned char)*p++;
    }
  return EOF;
}

int
copy_thru_input::peek(void)
{
  if (ap) 
    {
      if (*ap != '\0')
	return (unsigned char)*ap;
      ap = 0;
    }
  for (;;) 
    {
      if (p == 0) 
	{
	  if (!get_line())
	    break;
	  p = body;
	}
      if (*p == '\0')
	return '\n';
      while (*p >= ARG1 && *p <= ARG1 + 8) 
	{
	  int i = *p++ - ARG1;
	  if (i < argc && line[argv[i]] != '\0') 
	    {
	      ap = line.contents() + argv[i];
	      return (unsigned char)*ap;
	    }
	}
      if (*p != '\0')
	return (unsigned char)*p;
    }
  return EOF;
}

int
copy_thru_input::get_line(void)
{
  if (done)
    return 0;
  line.clear();
  argc = 0;
  int c = inget();
  for (;;) 
    {
      while (c == ' ')
	c = inget();
      if (c == EOF || c == '\n')
	break;
      if (argc == 9) 
	{
	  do 
	    {
	      c = inget();
	    } while (c != '\n' && c != EOF);
	  break;
	}
      argv[argc++] = line.length();
      do 
	{
	  line += char(c);
	  c = inget();
	} while (c != ' ' && c != '\n');
      line += '\0';
    }
  if (until != 0 && argc > 0 && strcmp(&line[argv[0]], until) == 0) 
    {
      done = 1;
      return 0;
    }
  return argc > 0 || c == '\n';
}

//////////////////////////////////////////////////////////////////////
// COPY_FILE_THRU_INPUT class (subclassed from COPY_THRU_INPUT)
//////////////////////////////////////////////////////////////////////

class copy_file_thru_input : public copy_thru_input 
{
public:
  copy_file_thru_input(input *, const char *b, const char *u);
  ~copy_file_thru_input();
  int inget();
private:
  input *in;
};

copy_file_thru_input::copy_file_thru_input(input *i, const char *b,
					   const char *u)
: copy_thru_input(b, u), in(i)
{
}

copy_file_thru_input::~copy_file_thru_input()
{
  delete in;
}

int
copy_file_thru_input::inget(void)
{
  if (!in)
    return EOF;
  else
    return in->get();
}

//////////////////////////////////////////////////////////////////////
// COPY_REST_THRU_INPUT class (subclassed from COPY_THRU_INPUT)
//////////////////////////////////////////////////////////////////////

class copy_rest_thru_input : public copy_thru_input 
{
public:
  // ctor
  copy_rest_thru_input (const char *, const char *u);
  // public functions
  int inget (void);
};

copy_rest_thru_input::copy_rest_thru_input(const char *b, const char *u)
: copy_thru_input(b, u)
{
}

int
copy_rest_thru_input::inget(void)
{
  while (next != 0) 
    {
      int c = next->get();
      if (c != EOF)
	return c;
      if (next->next == 0)
	return EOF;
      input *tem = next;
      next = next->next;
      delete tem;
    }
  return EOF;
}

//////////////////////////////////////////////////////////////////////
// SIMPLE_FILE_INPUT class
//////////////////////////////////////////////////////////////////////

class simple_file_input : public input 
{
public:
  // ctor, dtor
  simple_file_input(FILE *, const char *);
  ~simple_file_input();
  // public functions
  int get (void);
  int peek (void);
  int get_location (const char **, int *);
private:
  const char *filename;
  int lineno;
  FILE *fp;
};

simple_file_input::simple_file_input(FILE *p, const char *s)
: filename(s), lineno(1), fp(p)
{
}

simple_file_input::~simple_file_input(void)
{
  // don't delete the filename
  fclose(fp);
}

int
simple_file_input::get(void)
{
  int c = getc(fp);
  while (illegal_input_char(c)) 
    {
      error("illegal input character code %1", c);
      c = getc(fp);
    }
  if (c == '\n')
    lineno++;
  return c;
}

int
simple_file_input::peek(void)
{
  int c = getc(fp);
  while (illegal_input_char(c)) 
    {
      error("illegal input character code %1", c);
      c = getc(fp);
    }
  if (c != EOF)
    ungetc(c, fp);
  return c;
}

int
simple_file_input::get_location (const char **fnp, int *lnp)
{
  *fnp = filename;
  *lnp = lineno;
  return 1;
}

//////////////////////////////////////////////////////////////////////
// INPUT_STACK class (not subclassed from INPUT)
//////////////////////////////////////////////////////////////////////

class input_stack 
{
public:
  static void push(input *);
  static void clear();
  static int get_char();
  static int peek_char();
  static int get_location (const char **fnp, int *lnp);
  static void push_back(unsigned char c, int was_bol = 0);
  static int bol();
private:
  static input *current_input;
  static int bol_flag;
};

void 
input_stack::push(input *in)
{
  in->next = current_input;
  current_input = in;
}

void 
input_stack::clear(void)
{
  while (current_input != 0) 
    {
      input *tem = current_input;
      current_input = current_input->next;
      delete tem;
    }
  bol_flag = 1;
}

int
input_stack::get_char(void)
{
  while (current_input != 0) 
    {
      int c = current_input->get();
      if (c != EOF) 
	{
	  bol_flag = c == '\n';
	  return c;
	}
      // don't pop the top-level input off the stack
      if (current_input->next == 0)
	return EOF;
      input *tem = current_input;
      current_input = current_input->next;
      delete tem;
    }
  return EOF;
}

int
input_stack::peek_char(void)
{
  while (current_input != 0) 
    {
      int c = current_input->peek();
      if (c != EOF)
	return c;
      if (current_input->next == 0)
	return EOF;
      input *tem = current_input;
      current_input = current_input->next;
      delete tem;
    }
  return EOF;
}

int
input_stack::get_location (const char **fnp, int *lnp)
{
  for (input *p = current_input; p; p = p->next)
    if (p->get_location (fnp, lnp))
      return 1;
  return 0;
}

void
input_stack::push_back(unsigned char c, int was_bol)
{
  push(new char_input(c));
  bol_flag = was_bol;
}

inline int 
input_stack::bol(void)
{
  return bol_flag;
}

input *input_stack::current_input = 0;
int input_stack::bol_flag = 0;

//////////////////////////////////////////////////////////////////////
// PIC LEXER
//
// Public interface (declared in pic.h):
//
// lex_init(), lex_cleanup()
// yylex(), yyerror()
// lex_error(), lex_warning(),
// do_copy(), do_for(), copy_file_thru(), copy_rest_thru(), push_body()
// do_lookahead()
//
// The lexer accesses a read-only variable of the parser, `delim_flag',
// and also calls the parser's define_variable() and lookup_variable()
// functions [they're used in for loops].
//////////////////////////////////////////////////////////////////////

void 
lex_init(input *top)
{
  input_stack::clear();
  input_stack::push(top);
}

void 
lex_cleanup(void)
{
  while (input_stack::get_char() != EOF)
    ;
}

static void
interpolate_macro_with_args(const char *body)
{
  char *argv[9];
  int argc = 0;
  int i;
  for (i = 0; i < 9; i++)
    argv[i] = 0;
  int level = 0;
  int c;
  enum { NORMAL, IN_STRING, IN_STRING_QUOTED } state = NORMAL;
  do 
    {
      token_buffer.clear();
      for (;;) 
	{
	  c = input_stack::get_char();
	  if (c == EOF) 
	    {
	      lex_error("end of input while scanning macro arguments");
	      break;
	    }
	  if (state == NORMAL && level == 0 && (c == ',' || c == ')')) 
	    {
	      if (token_buffer.length() > 0) 
		{
		  token_buffer +=  '\0';
		  argv[argc] = strsave(token_buffer.contents());
		}
	      // for `foo()', argc = 0
	      if (argc > 0 || c != ')' || i > 0)
		argc++;
	      break;
	    }
	  token_buffer += char(c);
	  switch (state) 
	    {
	    case NORMAL:
	      if (c == '"')
		state = IN_STRING;
	      else if (c == '(')
		level++;
	      else if (c == ')')
		level--;
	      break;
	    case IN_STRING:
	      if (c == '"')
		state = NORMAL;
	      else if (c == '\\')
		state = IN_STRING_QUOTED;
	      break;
	    case IN_STRING_QUOTED:
	      state = IN_STRING;
	      break;
	    }
	}
    } while (c != ')' && c != EOF);
  input_stack::push(new argument_macro_input(body, argc, argv));
}

static int
docmp(const char *s1, int n1, const char *s2, int n2)
{
  if (n1 < n2) 
    {
      int r = memcmp(s1, s2, n1);
      return r ? r : -1;
    }
  else if (n1 > n2) 
    {
      int r = memcmp(s1, s2, n2);
      return r ? r : 1;
    }
  else
    return memcmp(s1, s2, n1);
}

static int
lookup_keyword(const char *str, int len)
{
  static struct keyword 
    {
      const char *name;
      int token;
    } table[] = 
      {
	{ "Here", HERE },
	{ "above", ABOVE },
	{ "aligned", ALIGNED },
	{ "and", AND },
	{ "arc", ARC },
	{ "arrow", ARROW },
	{ "at", AT },
	{ "atan2", ATAN2 },
	{ "below", BELOW },
	{ "between", BETWEEN },
	{ "bottom", BOTTOM },
	{ "box", BOX },
	{ "by", BY },
	{ "ccw", CCW },
	{ "center", CENTER },
	{ "chop", CHOP },
	{ "circle", CIRCLE },
	{ "command", COMMAND },
	{ "copy", COPY },
	{ "cos", COS },
	{ "cw", CW },
	{ "dashed", DASHED },
	{ "define", DEFINE },
	{ "diam", DIAMETER },
	{ "diameter", DIAMETER },
	{ "do", DO },
	{ "dotted", DOTTED },
	{ "down", DOWN },
	{ "ellipse", ELLIPSE },
	{ "else", ELSE },
	{ "end", END },
	{ "exp", EXP },
	{ "fill", FILL },
	{ "filled", FILL },
	{ "for", FOR },
	{ "from", FROM },
	{ "height", HEIGHT },
	{ "ht", HEIGHT },
	{ "if", IF },
	{ "int", INT },
	{ "invis", INVISIBLE },
	{ "invisible", INVISIBLE },
	{ "last", LAST },
	{ "left", LEFT },
	{ "line", LINE },
	{ "ljust", LJUST },
	{ "log", LOG },
	{ "lower", LOWER },
	{ "max", K_MAX },
	{ "min", K_MIN },
	{ "move", MOVE },
	{ "of", OF },
	{ "plot", PLOT },
	{ "print", PRINT },
	{ "rad", RADIUS },
	{ "radius", RADIUS },
	{ "rand", RAND },
	{ "reset", RESET },
	{ "right", RIGHT },
	{ "rjust", RJUST },
	{ "same", SAME },
	{ "sh", SH },
	{ "sin", SIN },
	{ "spline", SPLINE },
	{ "sprintf", SPRINTF },
	{ "sqrt", SQRT },
	{ "start", START },
	{ "the", THE },
	{ "then", THEN },
	{ "thick", THICKNESS },
	{ "thickness", THICKNESS },
	{ "thru", THRU },
	{ "to", TO },
	{ "top", TOP },
	{ "undef", UNDEF },
	{ "until", UNTIL },
	{ "up", UP },
	{ "upper", UPPER },
	{ "way", WAY },
	{ "wid", WIDTH },
	{ "width", WIDTH },
	{ "with", WITH },
      };
  
  const keyword *start = table;
  const keyword *end = table + sizeof(table)/sizeof(table[0]);
  while (start < end) 
    {
      // start <= target < end
      const keyword *mid = start + (end - start)/2;
      
      int cmp = docmp(str, len, mid->name, strlen(mid->name));
      if (cmp == 0)
	return mid->token;
      if (cmp < 0)
	end = mid;
      else
	start = mid + 1;
    }
  return 0;
}

static int
get_token_after_dot(int c)
{
  // get_token deals with the case where c is a digit
  switch (c) 
    {
    case 'h':
      input_stack::get_char();
      c = input_stack::peek_char();
      if (c == 't') 
	{
	  input_stack::get_char();
	  context_buffer = ".ht";
	  return DOT_HT;
	}
      else if (c == 'e') 
	{
	  input_stack::get_char();
	  c = input_stack::peek_char();
	  if (c == 'i') 
	    {
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == 'g') 
		{
		  input_stack::get_char();
		  c = input_stack::peek_char();
		  if (c == 'h') 
		    {
		      input_stack::get_char();
		      c = input_stack::peek_char();
		      if (c == 't') 
			{
			  input_stack::get_char();
			  context_buffer = ".height";
			  return DOT_HT;
			}
		      input_stack::push_back('h');
		    }
		  input_stack::push_back('g');
		}
	      input_stack::push_back('i');
	    }
	  input_stack::push_back('e');
	}
      input_stack::push_back('h');
      return '.';
    case 'x':
      input_stack::get_char();
      context_buffer = ".x";
      return DOT_X;
    case 'y':
      input_stack::get_char();
      context_buffer = ".y";
      return DOT_Y;
    case 'c':
      input_stack::get_char();
      c = input_stack::peek_char();
      if (c == 'e') 
	{
	  input_stack::get_char();
	  c = input_stack::peek_char();
	  if (c == 'n') 
	    {
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == 't') 
		{
		  input_stack::get_char();
		  c = input_stack::peek_char();
		  if (c == 'e') 
		    {
		      input_stack::get_char();
		      c = input_stack::peek_char();
		      if (c == 'r') 
			{
			  input_stack::get_char();
			  context_buffer = ".center";
			  return DOT_C;
			}
		      input_stack::push_back('e');
		    }
		  input_stack::push_back('t');
		}
	      input_stack::push_back('n');
	    }
	  input_stack::push_back('e');
	}
      context_buffer = ".c";
      return DOT_C;
    case 'n':
      input_stack::get_char();
      c = input_stack::peek_char();
      if (c == 'e') 
	{
	  input_stack::get_char();
	  context_buffer = ".ne";
	  return DOT_NE;
	}
      else if (c == 'w') 
	{
	  input_stack::get_char();
	  context_buffer = ".nw";
	  return DOT_NW;
	}
      else 
	{
	  context_buffer = ".n";
	  return DOT_N;
	}
      break;
    case 'e':
      input_stack::get_char();
      c = input_stack::peek_char();
      if (c == 'n') 
	{
	  input_stack::get_char();
	  c = input_stack::peek_char();
	  if (c == 'd') 
	    {
	      input_stack::get_char();
	      context_buffer = ".end";
	      return DOT_END;
	    }
	  input_stack::push_back('n');
	  context_buffer = ".e";
	  return DOT_E;
	}
      context_buffer = ".e";
      return DOT_E;
    case 'w':
      input_stack::get_char();
      c = input_stack::peek_char();
      if (c == 'i') 
	{
	  input_stack::get_char();
	  c = input_stack::peek_char();
	  if (c == 'd') 
	    {
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == 't') 
		{
		  input_stack::get_char();
		  c = input_stack::peek_char();
		  if (c == 'h') 
		    {
		      input_stack::get_char();
		      context_buffer = ".width";
		      return DOT_WID;
		    }
		  input_stack::push_back('t');
		}
	      context_buffer = ".wid";
	      return DOT_WID;
	    }
	  input_stack::push_back('i');
	}
      context_buffer = ".w";
      return DOT_W;
    case 's':
      input_stack::get_char();
      c = input_stack::peek_char();
      if (c == 'e') 
	{
	  input_stack::get_char();
	  context_buffer = ".se";
	  return DOT_SE;
	}
      else if (c == 'w') 
	{
	  input_stack::get_char();
	  context_buffer = ".sw";
	  return DOT_SW;
	}
      else 
	{
	  if (c == 't') 
	    {
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == 'a') 
		{
		  input_stack::get_char();
		  c = input_stack::peek_char();
		  if (c == 'r') 
		    {
		      input_stack::get_char();
		      c = input_stack::peek_char();
		      if (c == 't') 
			{
			  input_stack::get_char();
			  context_buffer = ".start";
			  return DOT_START;
			}
		      input_stack::push_back('r');
		    }
		  input_stack::push_back('a');
		}
	      input_stack::push_back('t');
	    }
	  context_buffer = ".s";
	  return DOT_S;
	}
      break;
    case 't':
      input_stack::get_char();
      c = input_stack::peek_char();
      if (c == 'o') 
	{
	  input_stack::get_char();
	  c = input_stack::peek_char();
	  if (c == 'p') 
	    {
	      input_stack::get_char();
	      context_buffer = ".top";
	      return DOT_N;
	    }
	  input_stack::push_back('o');
	}
      context_buffer = ".t";
      return DOT_N;
    case 'l':
      input_stack::get_char();
      c = input_stack::peek_char();
      if (c == 'e') 
	{
	  input_stack::get_char();
	  c = input_stack::peek_char();
	  if (c == 'f') 
	    {
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == 't') 
		{
		  input_stack::get_char();
		  context_buffer = ".left";
		  return DOT_W;
		}
	      input_stack::push_back('f');
	    }
	  input_stack::push_back('e');
	}
      context_buffer = ".l";
      return DOT_W;
    case 'r':
      input_stack::get_char();
      c = input_stack::peek_char();
      if (c == 'a') 
	{
	  input_stack::get_char();
	  c = input_stack::peek_char();
	  if (c == 'd') 
	    {
	      input_stack::get_char();
	      context_buffer = ".rad";
	      return DOT_RAD;
	    }
	  input_stack::push_back('a');
	}
      else if (c == 'i') 
	{
	  input_stack::get_char();
	  c = input_stack::peek_char();
	  if (c == 'g') 
	    {
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == 'h') 
		{
		  input_stack::get_char();
		  c = input_stack::peek_char();
		  if (c == 't') 
		    {
		      input_stack::get_char();
		      context_buffer = ".right";
		      return DOT_E;
		    }
		  input_stack::push_back('h');
		}
	      input_stack::push_back('g');
	    }
	  input_stack::push_back('i');
	}
      context_buffer = ".r";
      return DOT_E;
    case 'b':
      input_stack::get_char();
      c = input_stack::peek_char();
      if (c == 'o') 
	{
	  input_stack::get_char();
	  c = input_stack::peek_char();
	  if (c == 't') 
	    {
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == 't') 
		{
		  input_stack::get_char();
		  c = input_stack::peek_char();
		  if (c == 'o') 
		    {
		      input_stack::get_char();
		      c = input_stack::peek_char();
		      if (c == 'm') 
			{
			  input_stack::get_char();
			  context_buffer = ".bottom";
			  return DOT_S;
			}
		      input_stack::push_back('o');
		    }
		  input_stack::push_back('t');
		}
	      context_buffer = ".bot";
	      return DOT_S;
	    }
	  input_stack::push_back('o');
	}
      context_buffer = ".b";
      return DOT_S;
    default:
      context_buffer = '.';
      return '.';
    }
}

static int
get_token(int lookup_flag)
{
  context_buffer.clear();
  for (;;) 
    {
      int n = 0;
      int bol = input_stack::bol();
      int c = input_stack::get_char();
      if (bol && c == command_char) 
	{
	  token_buffer.clear();
	  token_buffer += c;
	  // the newline is not part of the token
	  for (;;) 
	    {
	      c = input_stack::peek_char();
	      if (c == EOF || c == '\n')
		break;
	      input_stack::get_char();
	      token_buffer += char(c);
	    }
	  context_buffer = token_buffer;
	  return COMMAND_LINE;
	}
      switch (c) 
	{
	case EOF:
	  return EOF;
	case ' ':
	case '\t':
	  break;
	case '\\':
	  {
	    int d = input_stack::peek_char();
	    if (d != '\n') 
	      {
		context_buffer = '\\';
		return '\\';
	      }
	    input_stack::get_char();
	    break;
	  }
	case '#':
	  do 
	    {
	      c = input_stack::get_char();
	    } while (c != '\n' && c != EOF);
	  if (c == '\n')
	    context_buffer = '\n';
	  return c;
	case '"':
	  context_buffer = '"';
	  token_buffer.clear();
	  for (;;) 
	    {
	      c = input_stack::get_char();
	      if (c == '\\') 
		{
		  context_buffer += '\\';
		  c = input_stack::peek_char();
		  if (c == '"') 
		    {
		      input_stack::get_char();
		      token_buffer += '"';
		      context_buffer += '"';
		    }
		  else
		    token_buffer += '\\';
		}
	      else if (c == '\n') 
		{
		  error("newline in string");
		  break;
		}
	      else if (c == EOF) 
		{
		  error("missing `\"'");
		  break;
		}
	      else if (c == '"') 
		{
		  context_buffer += '"';
		  break;
		}
	      else 
		{
		  context_buffer += char(c);
		  token_buffer += char(c);
		}
	    }
	  return TEXT;
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	  {   
	    int overflow = 0;
	    n = 0;
	    for (;;) 
	      {
		if (n > (INT_MAX - 9)/10) 
		  {
		    overflow = 1;
		    break;
		  }
		n *= 10;
		n += c - '0';
		context_buffer += char(c);
		c = input_stack::peek_char();
		if (c == EOF || !csdigit(c))
		  break;
		c = input_stack::get_char();
	      }
	    token_double = n;
	    if (overflow) 
	      {
		for (;;) 
		  {
		    token_double *= 10.0;
		    token_double += c - '0';
		    context_buffer += char(c);
		    c = input_stack::peek_char();
		    if (c == EOF || !csdigit(c))
		      break;
		    c = input_stack::get_char();
		  }
		// if somebody asks for 1000000000000th, we will silently
		// give them INT_MAXth
		double temp = token_double; // work around gas 1.34/sparc bug
		if (token_double > INT_MAX)
		  n = INT_MAX;
		else
		  n = int(temp);
	      }
	  }
	  switch (c) 
	    {
	    case 'i':
	    case 'I':
	      context_buffer += char(c);
	      input_stack::get_char();
	      return NUMBER;
	    case '.':
	      {
		context_buffer += '.';
		input_stack::get_char();
	      got_dot:
		double factor = 1.0;
		for (;;) 
		  {
		    c = input_stack::peek_char();
		    if (!c == EOF || !csdigit(c))
		      break;
		    input_stack::get_char();
		    context_buffer += char(c);
		    factor /= 10.0;
		    if (c != '0')
		      token_double += factor*(c - '0');
		  }
		if (c != 'e' && c != 'E') 
		  {
		    if (c == 'i' || c == 'I') 
		      {
			context_buffer += char(c);
			input_stack::get_char();
		      }
		    return NUMBER;
		  }
	      }
	      // fall through
	    case 'e':
	    case 'E':
	      {
		int echar = c;
		input_stack::get_char();
		c = input_stack::peek_char();
		int sign = '+';
		if (c == '+' || c == '-') 
		  {
		    sign = c;
		    input_stack::get_char();
		    c = input_stack::peek_char();
		    if (c == EOF || !csdigit(c)) 
		      {
			input_stack::push_back(sign);
			input_stack::push_back(echar);
			return NUMBER;
		      }
		    context_buffer += char(echar);
		    context_buffer += char(sign);
		  }
		else 
		  {
		    if (c == EOF || !csdigit(c)) 
		      {
			input_stack::push_back(echar);
			return NUMBER;
		      }
		    context_buffer += char(echar);
		  }
		input_stack::get_char();
		context_buffer += char(c);
		n = c - '0';
		for (;;) 
		  {
		    c = input_stack::peek_char();
		    if (c == EOF || !csdigit(c))
		      break;
		    input_stack::get_char();
		    context_buffer += char(c);
		    n = n*10 + (c - '0');
		  }
		if (sign == '-')
		  n = -n;
		if (c == 'i' || c == 'I') 
		  {
		    context_buffer += char(c);
		    input_stack::get_char();
		  }
		token_double *= pow(10.0, n);
		return NUMBER;
	      }
	    case 'n':
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == 'd') 
		{
		  input_stack::get_char();
		  token_int = n;
		  context_buffer += "nd";
		  return ORDINAL;
		}
	      input_stack::push_back('n');
	      return NUMBER;
	    case 'r':
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == 'd') 
		{
		  input_stack::get_char();
		  token_int = n;
		  context_buffer += "rd";
		  return ORDINAL;
		}
	      input_stack::push_back('r');
	      return NUMBER;
	    case 't':
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == 'h') 
		{
		  input_stack::get_char();
		  token_int = n;
		  context_buffer += "th";
		  return ORDINAL;
		}
	      input_stack::push_back('t');
	      return NUMBER;
	    case 's':
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == 't') 
		{
		  input_stack::get_char();
		  token_int = n;
		  context_buffer += "st";
		  return ORDINAL;
		}
	      input_stack::push_back('s');
	      return NUMBER;
	    default:
	      return NUMBER;
	    }
	  break;
	case '\'':
	  {
	    c = input_stack::peek_char();
	    if (c == 't') 
	      {
		input_stack::get_char();
		c = input_stack::peek_char();
		if (c == 'h') 
		  {
		    input_stack::get_char();
		    context_buffer = "'th";
		    return TH;
		  }
		else
		  input_stack::push_back('t');
	      }
	    context_buffer = "'";
	    return '\'';
	  }
	case '.':
	  {
	    c = input_stack::peek_char();
	    if (c != EOF && csdigit(c)) 
	      {
		n = 0;
		token_double = 0.0;
		context_buffer = '.';
		goto got_dot;
	      }
	    return get_token_after_dot(c);
	  }
	case '<':
	  c = input_stack::peek_char();
	  if (c == '-') 
	    {
	      input_stack::get_char();
	      c = input_stack::peek_char();
	      if (c == '>') 
		{
		  input_stack::get_char();
		  context_buffer = "<->";
		  return DOUBLE_ARROW_HEAD;
		}
	      context_buffer = "<-";
	      return LEFT_ARROW_HEAD;
	    }
	  else if (c == '=') 
	    {
	      input_stack::get_char();
	      context_buffer = "<=";
	      return LESSEQUAL;
	    }
	  context_buffer = "<";
	  return '<';
	case '-':
	  c = input_stack::peek_char();
	  if (c == '>') 
	    {
	      input_stack::get_char();
	      context_buffer = "->";
	      return RIGHT_ARROW_HEAD;
	    }
	  context_buffer = "-";
	  return '-';
	case '!':
	  c = input_stack::peek_char();
	  if (c == '=') 
	    {
	      input_stack::get_char();
	      context_buffer = "!=";
	      return NOTEQUAL;
	    }
	  context_buffer = "!";
	  return '!';
	case '>':
	  c = input_stack::peek_char();
	  if (c == '=') 
	    {
	      input_stack::get_char();
	      context_buffer = ">=";
	      return GREATEREQUAL;
	    }
	  context_buffer = ">";
	  return '>';
	case '=':
	  c = input_stack::peek_char();
	  if (c == '=') 
	    {
	      input_stack::get_char();
	      context_buffer = "==";
	      return EQUALEQUAL;
	    }
	  context_buffer = "=";
	  return '=';
	case '&':
	  c = input_stack::peek_char();
	  if (c == '&') 
	    {
	      input_stack::get_char();
	      context_buffer = "&&";
	      return ANDAND;
	    }
	  context_buffer = "&";
	  return '&';
	case '|':
	  c = input_stack::peek_char();
	  if (c == '|') 
	    {
	      input_stack::get_char();
	      context_buffer = "||";
	      return OROR;
	    }
	  context_buffer = "|";
	  return '|';
	default:
	  if (c != EOF && csalpha(c)) 
	    {
	      token_buffer.clear();
	      token_buffer = c;
	      for (;;) 
		{
		  c = input_stack::peek_char();
		  if (c == EOF || (!csalnum(c) && c != '_'))
		    break;
		  input_stack::get_char();
		  token_buffer += char(c);
		}
	      int tok = lookup_keyword(token_buffer.contents(),
				       token_buffer.length());
	      if (tok != 0) 
		{
		  context_buffer = token_buffer;
		  return tok;
		}
	      char *def = 0;
	      if (lookup_flag) 
		{
		  token_buffer += '\0';
		  def = macro_table.lookup(token_buffer.contents());
		  token_buffer.set_length(token_buffer.length() - 1);
		  if (def) 
		    {
		      if (c == '(') 
			{
			  input_stack::get_char();
			  interpolate_macro_with_args(def);
			}
		      else
			input_stack::push(new macro_input(def));
		    }
		}
	      if (!def) 
		{
		  context_buffer = token_buffer;
		  if (csupper(token_buffer[0]))
		    return LABEL;
		  else
		    return VARIABLE;
		}
	    }
	  else 
	    {
	      context_buffer = char(c);
	      return (unsigned char)c;
	    }
	  break;
	}
    }
}

static int
get_delimited(void)
{
  token_buffer.clear();
  int c = input_stack::get_char();
  while (c == ' ' || c == '\t' || c == '\n')
    c = input_stack::get_char();
  if (c == EOF) 
    {
      lex_error("missing delimiter");
      return 0;
    }
  context_buffer = char(c);
  int had_newline = 0;
  int start = c;
  int level = 0;
  enum { NORMAL, IN_STRING, IN_STRING_QUOTED, DELIM_END } state = NORMAL;
  for (;;) 
    {
      c = input_stack::get_char();
      if (c == EOF) 
	{
	  lex_error("missing closing delimiter");
	  return 0;
	}
      if (c == '\n')
	had_newline = 1;
      else if (!had_newline)
	context_buffer += char(c);
      switch (state) 
	{
	case NORMAL:
	  if (start == '{') 
	    {
	      if (c == '{') 
		{
		  level++;
		  break;
		}
	      if (c == '}') 
		{
		  if (--level < 0)
		    state = DELIM_END;
		  break;
		}
	    }
	  else 
	    {
	      if (c == start) 
		{
		  state = DELIM_END;
		  break;
		}
	    }
	  if (c == '"')
	    state = IN_STRING;
	  break;
	case IN_STRING_QUOTED:
	  if (c == '\n')
	    state = NORMAL;
	  else
	    state = IN_STRING;
	  break;
	case IN_STRING:
	  if (c == '"' || c == '\n')
	    state = NORMAL;
	  else if (c == '\\')
	    state = IN_STRING_QUOTED;
	  break;
	case DELIM_END:
	  // This case is just to shut cfront 2.0 up.
	default:
	  assert(0);
	}
      if (state == DELIM_END)
	break;
      token_buffer += c;
    }
  return 1;
}

static void
do_define(void)
{
  int t = get_token(0);		// do not expand what we are defining
  if (t != VARIABLE && t != LABEL) 
    {
      lex_error("can only define variable or placename");
      return;
    }
  token_buffer += '\0';
  string nm = token_buffer;
  const char *name = nm.contents();
  if (!get_delimited())
    return;
  token_buffer += '\0';
  macro_table.define(name, strsave(token_buffer.contents()));
}

static void
do_undef(void)
{
  int t = get_token(0);		// do not expand what we are undefining
  if (t != VARIABLE && t != LABEL) 
    {
      lex_error("can only define variable or placename");
      return;
    }
  token_buffer += '\0';
  macro_table.define(token_buffer.contents(), 0);
}

void
do_for(char *var, double from, double to, int by_is_multiplicative,
       double by, char *body)
{
  define_variable(var, from);
  if (from <= to)
    input_stack::push(new for_input(var, to, by_is_multiplicative, by, body));
}

void 
do_copy(const char *filename)
{
  errno = 0;
  FILE *fp = fopen(filename, "r");
  if (fp == 0) 
    {
      lex_error("can't open `%1': %2", filename, strerror(errno));
      return;
    }
  input_stack::push(new file_input(fp, filename));
}

void
copy_file_thru(const char *filename, const char *body, const char *until)
{
  errno = 0;
  FILE *fp = fopen(filename, "r");
  if (fp == 0) 
    {
      lex_error("can't open `%1': %2", filename, strerror(errno));
      return;
    }
  input *in = new copy_file_thru_input(new simple_file_input(fp, filename),
				       body, until);
  input_stack::push(in);
}

void
copy_rest_thru(const char *body, const char *until)
{
  input_stack::push(new copy_rest_thru_input(body, until));
}

void
push_body(const char *s)
{
  input_stack::push(new char_input('\n'));
  input_stack::push(new macro_input(s));
}

static char *
get_thru_arg(void)
{
  int c = input_stack::peek_char();
  while (c == ' ') 
    {
      input_stack::get_char();
      c = input_stack::peek_char();
    }
  if (c != EOF && csalpha(c)) 
    {
      // looks like a macro
      input_stack::get_char();
      token_buffer = c;
      for (;;) 
	{
	  c = input_stack::peek_char();
	  if (c == EOF || (!csalnum(c) && c != '_'))
	    break;
	  input_stack::get_char();
	  token_buffer += char(c);
	}
      context_buffer = token_buffer;
      token_buffer += '\0';
      char *def = macro_table.lookup(token_buffer.contents());
      if (def)
	return strsave(def);
      // I guess it wasn't a macro after all; so push the macro name back.
      // -2 because we added a '\0'
      for (int i = token_buffer.length() - 2; i >= 0; i--)
	input_stack::push_back(token_buffer[i]);
    }
  if (get_delimited()) 
    {
      token_buffer += '\0';
      return strsave(token_buffer.contents());
    }
  else
    return 0;
}

static char *
process_body(const char *body)
{
  char *s = strsave(body);
  int j = 0;
  for (int i = 0; s[i] != '\0'; i++)
    if (s[i] == '$' && s[i+1] >= '0' && s[i+1] <= '9') 
      {
	if (s[i+1] != '0')
	  s[j++] = ARG1 + s[++i] - '1';
      }
    else
      s[j++] = s[i];
  s[j] = '\0';
  return s;
}

void
do_lookahead(void)
{
  if (lookahead_token == -1) 
    {
      old_context_buffer = context_buffer;
      lookahead_token = get_token(1);
    }
}

int
yylex(void)
{
  if (delim_flag) 
    {
      assert(lookahead_token == -1);
      if (delim_flag == 2) 
	{
	  if ((yylval.str = get_thru_arg()) != 0)
	    return DELIMITED;
	  else
	    return 0;
	}
      else 
	{
	  if (get_delimited()) 
	    {
	      token_buffer += '\0';
	      yylval.str = strsave(token_buffer.contents());
	      return DELIMITED;
	    }
	  else
	    return 0;
	}
    }
  for (;;) 
    {
      int t;
      if (lookahead_token >= 0) 
	{
	  t = lookahead_token;
	  lookahead_token = -1;
	}
      else
	t = get_token(1);
      switch (t) 
	{
	case '\n':
	  return ';';
	case EOF:
	  return 0;
	case DEFINE:
	  do_define();
	  break;
	case UNDEF:
	  do_undef();
	  break;
	case ORDINAL:
	  yylval.n = token_int;
	  return t;
	case NUMBER:
	  yylval.x = token_double;
	  return t;
	case COMMAND_LINE:
	case TEXT:
	  token_buffer += '\0';
	  if (!input_stack::get_location (&yylval.lstr.filename,
					 &yylval.lstr.lineno)) 
	    {
	      yylval.lstr.filename = 0;
	      yylval.lstr.lineno = -1;
	    }
	  yylval.lstr.str = strsave(token_buffer.contents());
	  return t;
	case LABEL:
	case VARIABLE:
	  token_buffer += '\0';
	  yylval.str = strsave(token_buffer.contents());
	  return t;
	case LEFT:
	  // change LEFT to LEFT_CORNER when followed by OF
	  old_context_buffer = context_buffer;
	  lookahead_token = get_token(1);
	  if (lookahead_token == OF)
	    return LEFT_CORNER;
	  else
	    return t;
	case RIGHT:
	  // change RIGHT to RIGHT_CORNER when followed by OF
	  old_context_buffer = context_buffer;
	  lookahead_token = get_token(1);
	  if (lookahead_token == OF)
	    return RIGHT_CORNER;
	  else
	    return t;
	case UPPER:
	  // recognise UPPER only before LEFT or RIGHT
	  old_context_buffer = context_buffer;
	  lookahead_token = get_token(1);
	  if (lookahead_token != LEFT && lookahead_token != RIGHT) 
	    {
	      yylval.str = strsave("upper");
	      return VARIABLE;
	    }
	  else
	    return t;
	case LOWER:
	  // recognise LOWER only before LEFT or RIGHT
	  old_context_buffer = context_buffer;
	  lookahead_token = get_token(1);
	  if (lookahead_token != LEFT && lookahead_token != RIGHT) 
	    {
	      yylval.str = strsave("lower");
	      return VARIABLE;
	    }
	  else
	    return t;
	case TOP:
	  // recognise TOP only before OF
	  old_context_buffer = context_buffer;
	  lookahead_token = get_token(1);
	  if (lookahead_token != OF) 
	    {
	      yylval.str = strsave("top");
	      return VARIABLE;
	    }
	  else
	    return t;
	case BOTTOM:
	  // recognise BOTTOM only before OF
	  old_context_buffer = context_buffer;
	  lookahead_token = get_token(1);
	  if (lookahead_token != OF) 
	    {
	      yylval.str = strsave("bottom");
	      return VARIABLE;
	    }
	  else
	    return t;
	case CENTER:
	  // recognise CENTER only before OF
	  old_context_buffer = context_buffer;
	  lookahead_token = get_token(1);
	  if (lookahead_token != OF) 
	    {
	      yylval.str = strsave("center");
	      return VARIABLE;
	    }
	  else
	    return t;
	case START:
	  // recognise START only before OF
	  old_context_buffer = context_buffer;
	  lookahead_token = get_token(1);
	  if (lookahead_token != OF) 
	    {
	      yylval.str = strsave("start");
	      return VARIABLE;
	    }
	  else
	    return t;
	case END:
	  // recognise END only before OF
	  old_context_buffer = context_buffer;
	  lookahead_token = get_token(1);
	  if (lookahead_token != OF) 
	    {
	      yylval.str = strsave("end");
	      return VARIABLE;
	    }
	  else
	    return t;
	default:
	  return t;
	}
    }
}

void
lex_error(const char *message, const errarg &arg1,
	  const errarg &arg2, const errarg &arg3)
{
  const char *filename;
  int lineno;
  if (!input_stack::get_location (&filename, &lineno))
    error(message, arg1, arg2, arg3);
  else
    error_with_file_and_line(filename, lineno, message, arg1, arg2, arg3);
}

void 
lex_warning(const char *message, const errarg &arg1,
	    const errarg &arg2, const errarg &arg3)
{
  const char *filename;
  int lineno;
  if (!input_stack::get_location (&filename, &lineno))
    warning(message, arg1, arg2, arg3);
  else
    warning_with_file_and_line(filename, lineno, message, arg1, arg2, arg3);
}

void
yyerror(const char *s)
{
  const char *filename;
  int lineno;
  const char *context = 0;
  if (lookahead_token == -1) 
    {
      if (context_buffer.length() > 0) 
	{
	  context_buffer += '\0';
	  context = context_buffer.contents();
	}
    }
  else 
    {
      if (old_context_buffer.length() > 0) 
	{
	  old_context_buffer += '\0';
	  context = old_context_buffer.contents();
	}
    }
  if (!input_stack::get_location (&filename, &lineno)) 
    {
      if (context) 
	{
	  if (context[0] == '\n' && context[1] == '\0')
	    error("%1 before newline", s);
	  else
	    error("%1 before `%2'", s, context);
	}
      else
	error("%1 at end of picture", s);
    }
  else 
    {
      if (context) 
	{
	  if (context[0] == '\n' && context[1] == '\0')
	    error_with_file_and_line(filename, lineno, "%1 before newline", s);
	  else
	    error_with_file_and_line(filename, lineno, "%1 before `%2'",
				     s, context);
	}
      else
	error_with_file_and_line(filename, lineno, "%1 at end of picture", s);
    }
}
