/* This file is part of `mogul'
 * Copyright (c) 1995 David Deaven
 *
 * In the following, "you" and "You" refer to any entity other than 
 * the Microsoft corporation and its subsidiaries.  All rights are
 * reserved by the author in connection with Microsoft and its
 * subsidiaries.  The Microsoft Network is explicity denied the right
 * to copy, reproduce, or transmit this software.
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Author: deaven $
 * $Id: parse.y,v 2.0 1996/07/09 20:59:55 deaven Exp deaven $
 */

%{
#include "dmd/rgb.h"
#include "dmd/memory.h"
#include "dmd/tools.h"
#include "mogul.h"

#define MIN_PLOT_SIZE 0.01
#define MAX_PLOT_SIZE 1.e10

char *MogulParserMsg = NULL;

static char *helpString =
  "commands:\n"
  "\tadd atom <select>\n"
  "\t    bond <select>\n"
  "\t    box\n"
  "\t    label <string> <position> <height>\n"
  "\t    line <position> <position>\n"
  "\tcamera position = <position>\n"
  "\t       view = <direction>\n"
  "\t       up = <direction>\n"
  "\tattributes(atom <string>) = {attr1 = value1 [, attr2 = value2 [...]]}\n"
  "\tattributes(bond(<string>,<string>)) = {attr1 = value1 [, attr2 = value2 [...]]}\n"
  "\tattributes(plot) = {attr1 = value1 [, attr2 = value2 [...]]}\n"
  "\thelp\n"
  "\t<identifier> = <number>\n"
  "\n"
  "attributes = values:\n"
  "\tbond radius = <number>\n"
  "\tcolor = <string>\n"
  "\tmode = filled | outlined | solid | translucent | minimal | moon | stick\n"
  "\tperspective = <number>\n"
  "\tradius = <number>\n"
  "\tsize = <number> | <number>,<number>\n"
  "\n"
  "other basic data types:\n"
  "\t<direction> == [dx,dy,dz] | <<number> * <direction>\n"
  "\t<position> == (x,y,z) | <atom n> | <geometry center> | <camera center> |\n"
  "\t              <plot center> | [point] + [direction]\n"
  "\t<string> == \"in double quotes\"\n"
  ;

static char *copyrightString = 
  "mogul version 2.0, Copyright (C) 1996 David Deaven.\n"
  "\tmogul comes with ABSOLUTELY NO WARRANTY.\n"
  "\tThis is free software, and you are welcome to redistribute\n"
  "\tit under the conditions of the Free Software Foundation\n"
  "\tversion 2 or (at your option) later.\n"
  ;

struct MogulParserRec {
	MogulRules rules;
	struct symtab_s *symtab;
	StringPool pool;
};

struct symtab_s {
	char *name;
	int type;
	union {
	  double r;
	  double (*r_fun)(void);
	  double (*r_fun_r)(double);
	} value;
	struct symtab_s *next;
};

struct lex_s {
	enum {SRC_FILE, SRC_STRING} type;
	union {
	  FILE *f;
	  char *s;
	} source;
	int ch;		/* chars read */
	int line;	/* lines read */
	int c, lastc;	/* this character, last read character */
	int eof;
};

enum context_type {INITIAL_CONTEXT, IF_CONTEXT, FOR_CONTEXT, WHILE_CONTEXT};
struct context_s {
	int flameon;	/* executing? */
	enum context_type type;
};

struct select_s {
        unsigned int n;		/* how many selctions */
        int *which;		/* list of selections */
};

static struct {
	/* initialized */
	struct lex_s lex;
	char *prompt;
	int autoRender;
	MogulPlot plot;
	MogulDevice device;
	MogulParser parser;
	int ncontext;
	int nerror;
	struct context_s *context;
	Geometry *g;
	NeighborList *nl;

	/* scratch */
	struct {
	  double p[3];
	  struct ruleAttributes *lrule;
	  struct plotAttributes *lplot;
	} tmp;
} global;

int yylex(void);
int yyerror(char *s);
int yyparse(void);

static void prompt()
{
	if(global.prompt)
	{
	  printf("%s", global.prompt);
	  fflush(stdout);
	}
}

static struct symtab_s *new_symtab(MogulParser parser, char *name, int type)
{
	struct symtab_s *s = NewPointer(s);
	s->type = type;
	s->name = name;
	s->next = parser->symtab;
	parser->symtab = s;
	return s;
}

static struct symtab_s *find_symtab(MogulParser parser, char *name)
{
	struct symtab_s *s;
	for(s = parser->symtab; s; s = s->next) {
	  if(!strcmp(name, s->name)) return s;
	}
	return NULL;
}

static void push_context(int flameon, enum context_type type)
{
	global.context =
	  ResizePointerArray(global.context, global.ncontext + 1);
	global.context[global.ncontext].flameon = flameon;
	global.context[global.ncontext].type = type;
	global.ncontext++;
}

#if 0
static int pop_context(void)
{
	if(global.ncontext <= 1) return -1;
	global.ncontext--;
	return 0;
}
#endif

static int flameon(void)
{
	int i;
	for(i=0; i<global.ncontext; i++)
	  if(!global.context[i].flameon) return 0;
	return 1;
}

static void delete_context(void)
{
	myfree(global.context); global.context = NULL;
	global.ncontext = 0;
}

static double check_range(double v, double vmin, double vmax)
{
	if(v < vmin) v = vmin;
	if(v > vmax) v = vmax;
	return v;
}

/* Return a newly allocated number specification.
 */
static struct select_s *select_range(int i, int j)
{
	int n;
	struct select_s *s = NewPointer(s);
	if(i < 0 || j < 0 || j < i) {
	  s->n = 0;
	  s->which = NULL;
	}
	else {
	  s->n = j - i;
	  s->which = imalloc(s->n > 0 ? s->n : 1);
	  for(n=i; n<j; n++)
	    s->which[n - i] = n;
	}
	return s;
}

/* Free a specification.
 */
static void delete_select(struct select_s *s)
{
	myfree(s->which);
	myfree(s);
}

static int is_selected(int i, struct select_s *s)
{
	int j;
	for(j=0; j<s->n; j++) if(s->which[j] == i) return 1;
	return 0;
}

/* Concatenate two specifications, freeing them in the process.
 */
static struct select_s *select_union(struct select_s *s1, struct select_s *s2)
{
	int i, m, n = s1->n + s2->n;
	s1->which = ResizePointerArray(s1->which, n);
	for(i=m=s1->n; i<n; i++) {
	  if(!is_selected(i, s1)) s1->which[m++] = s2->which[i - s1->n];
	}
	s1->n = m;
	delete_select(s2);
	return s1;
}

static double *set3(double dst[3], double src[3])
{
	int i;
	for(i=0; i<3; i++) dst[i] = src[i];
	return dst;
}

static double *add3(double dst[3], double src[3])
{
	int i;
	for(i=0; i<3; i++) dst[i] += src[i];
	return dst;
}

static double *mul31(double dst[3], double a)
{
	int i;
	for(i=0; i<3; i++) dst[i] *= a;
	return dst;
}

%}

%start program

%union {
  double number;
  double point[3];
  unsigned long mode;
  char *string;
  Color color;
  struct select_s *select;
  struct symtab_s *sym;
  struct ruleAttributes *lrule;
  struct plotAttributes *lplot;
}

/* Basic data types that can be returned by the lexical scanner.
 */
%token NUM, STRING, IDENTIFIER, R_VAR, R_FUN, R_FUN_R

/* Tightly-bound operators
 */
%left <code> '|'
%left <code> '-' '+'
%left <code> '/' '*'

/* Reserved keywords
 */
%token ADD, ATTRIBUTES, OUTLINED, MOON, STICK, MINIMAL
%token ALL, NONE, ATOM, CENTER, BOND, TRUE, FALSE
%token CAMERA, VIEW, NAME, PERSPECTIVE, SIZE
%token COLOR, RADIUS, PLOT, GEOMETRY, BOX
%token POSITION, UP
%token SOLID, TRANSLUCENT, FOREGROUND, BACKGROUND, LINE
%token HELP, LABEL, FILLED
%token MODE

/* Type assignments for parser nodes.
 */
%type <number> number NUM TRUE FALSE
%type <point> point direction
%type <mode> mode mode_item
%type <color> color
%type <string> string STRING
%type <select> select select_item
%type <lrule> latom lbond
%type <lplot> lplot
%type <sym> IDENTIFIER R_VAR, R_FUN, R_FUN_R

%%

program: /* nil */ | program statement | program error ;

statement:	'\n'
		  { prompt(); }
	|	command '\n'
		  { if(global.autoRender && global.device)
		      MogulRender(global.plot, global.device);
		    prompt(); }
	;

command:  	ADD ATOM select
		  { int i = 0, ii;
		    double x[3];
		    struct ruleAttributes *rule;

		    for(ii=0; $3->n > 0 && ii<$3->n; ii++) {
		      i = $3->which[ii];
		      rule = MogulGetAtomRule(global.parser->rules, global.g->aname[i]);
		      mmul3(global.g->box, global.g->x + 3*i, x);
		      MogulAddAtom(global.plot, &(rule->attr), x, rule->u.atom.radius);
		    }
		  }
	|	ADD BOND select
		  { int ii, i = 0, j = 0, jnl, m;
		    double x1[3], x2[3], dx[3], r1, r2;
		    struct ruleAttributes *rule;

		    for(ii=0; $3->n > 0 && ii<($3->n - 1); ii++)
		    {
		      i = $3->which[ii];
		      mmul3(global.g->box, global.g->x + 3*i, x1);
		      r1 = MogulGetAtomRule(global.parser->rules, global.g->aname[i])->u.atom.radius;
		      for(jnl=global.nl->mrkr[i]; jnl<(global.nl->mrkr[i+1]); jnl++)
		      {
			j = getnlindex(global.nl->list[jnl]);
			if(is_selected(j, $3)) {
			  r2 = MogulGetAtomRule(global.parser->rules, global.g->aname[j])->u.atom.radius;
			  rule = MogulGetBondRule(global.parser->rules,
			    global.g->aname[i], global.g->aname[j]);
			  if(Gdisp2(global.g, i, j, dx) < rule->u.bond.bondr * rule->u.bond.bondr)
			  {
			    /* mmul3(global.g->box, global.g->x + 3*j, x2); */
			    mtrn3(global.g->box, dx);
			    for(m=0; m<3; m++) x2[m] = x1[m] + dx[m];
			    MogulAddBondBetween(global.plot, &(rule->attr),
			      x1, r1, x2, r2, rule->u.bond.radius);
			  }
			}
		      }
		    }
		  }
	|	ADD BOX
		  { if(global.g) {
		      struct commonAttributes attr;
		      MogulDefaultAttributes(global.plot, &attr);
		      attr.color = global.plot->attr.foreground;
		      MogulAddBox(global.plot, &attr, global.g);
		    }
		  }
	|	ADD LABEL string point number
		  { struct commonAttributes attr;
		    MogulDefaultAttributes(global.plot, &attr);
		    attr.color = global.plot->attr.foreground;
		    MogulAddLabel(global.plot, &attr, $4, $3, $5);
		  }
	|	ADD LINE point point
		  { struct commonAttributes attr;
		    MogulDefaultAttributes(global.plot, &attr);
		    attr.color = global.plot->attr.foreground;
		    MogulAddLine(global.plot, &attr, $3, $4);
		  }
	|	CAMERA POSITION '=' point
		  { CameraSetPosition(global.plot->camera, $4); }
	|	CAMERA VIEW '=' direction 
		  { CameraSetView(global.plot->camera, $4); }
	|	CAMERA UP '=' direction 
		  { CameraSetUp(global.plot->camera, $4); }
	|	HELP
		  { printf("%s", helpString); }
	|	IDENTIFIER '=' number
		  { $1->type = R_VAR;
		    $1->value.r = $3;
		  }
	|	R_VAR '=' number
		  { $1->value.r = $3; }
	|	R_FUN '(' ')'
		  { $1->value.r_fun(); }
	|	R_FUN_R '(' number ')'
		  { $1->value.r_fun_r($3); }
	|	latom '='
		  { global.tmp.lrule = $1; }
		'{' attr_list '}'
	|	lbond '='
		  { global.tmp.lrule = $1; }
		'{' attr_list '}'
	|	lplot '='
		  { global.tmp.lplot = $1; }
		'{' plot_attr_list '}'
	;

/* Left-hand sides of attributes simply return by reference.
 */
latom:		ATTRIBUTES '(' ATOM string ')'
		  { $$ = MogulGetAtomRule(global.parser->rules, $4); }
	;

lbond:		ATTRIBUTES '(' BOND '(' string ',' string ')' ')'
		  { $$ = MogulGetBondRule(global.parser->rules, $5, $7); }
	;

lplot:		ATTRIBUTES '(' PLOT ')'
		  { $$ = &(global.plot->attr); }
	;

/* Right-hand sides of attribute assignments modify whatever
 * is pointed to by the global pointers. XXX
 */
attr_list:	attr
	|	attr_list ',' attr
	;

attr:
	|	RADIUS '=' number
		  { switch(global.tmp.lrule->type) {
		      case RULE_ATOM:
			global.tmp.lrule->u.atom.radius = $3; break;
		      case RULE_BOND:
			global.tmp.lrule->u.bond.radius = $3; break;
		      default: break;
		    }
		  }
	|	BOND RADIUS '=' number
		  { switch(global.tmp.lrule->type) {
		      case RULE_BOND:
			global.tmp.lrule->u.bond.bondr = $4; break;
		      default: break;
		    }
		  }
	|	MODE '=' mode
		  { global.tmp.lrule->attr.mode = $3; }
	|	MODE '|' '=' mode
		  { global.tmp.lrule->attr.mode |= $4; }
	|     	COLOR '=' color
		  { global.tmp.lrule->attr.color = $3; }
	;

plot_attr_list:	plot_attr
	|	plot_attr_list ',' plot_attr
	;

plot_attr:	SIZE '=' '(' number ',' number ')'
		  { global.tmp.lplot->width = check_range($4, MIN_PLOT_SIZE, MAX_PLOT_SIZE);
		    global.tmp.lplot->height = check_range($6, MIN_PLOT_SIZE, MAX_PLOT_SIZE);
		  }
	|	SIZE '=' number 
		  { global.tmp.lplot->width = check_range($3, MIN_PLOT_SIZE, MAX_PLOT_SIZE);
		    global.tmp.lplot->height = check_range($3, MIN_PLOT_SIZE, MAX_PLOT_SIZE);
		  }
	|	PERSPECTIVE '=' number
		  { global.tmp.lplot->vdist = $3; }
	|	FOREGROUND '=' color
		  { global.tmp.lplot->foreground = $3; }
	|	BACKGROUND '=' color
		  { global.tmp.lplot->background = $3; }
	;

/* Basic data types derived from the lexical atomic types.
 */
point:		'(' number ',' number ',' number ')'
		  { $$[0] = $2;
		    $$[1] = $4;
		    $$[2] = $6; }
	|	'<' ATOM number '>'
		  { if(global.g && $3 >= 0 && $3 < global.g->n)
		      set3($$, mmul3(global.g->box, global.g->x + 3 * ((int) $3), global.tmp.p));
		    else {
		      yyerror("invalid atom number");
		      YYERROR;
		    }
		  }
	|	'<' GEOMETRY CENTER '>'
		  { int i;
		    for(i=0; i<3; i++) $$[i] = 0.0;
		    if(global.g) {
		      for(i=0; i<global.g->n; i++) {
			$$[0] += global.g->x[3*i];
			$$[1] += global.g->x[3*i + 1];
			$$[2] += global.g->x[3*i + 2];
		      }
		      for(i=0; i<3; i++) $$[i] /= global.g->n;
		    }
		  }
	|	'<' CAMERA CENTER '>'
		  { set3($$, CameraGetPosition(global.plot->camera, global.tmp.p)); }
	|	'<' PLOT CENTER '>'
		{
		  double r;
		  MogulObjectExtent(global.plot, $$, &r);
		}
	|	point '+' direction
		  { set3($$, $1); add3($$, $3); }
	|	'(' point ')'
		  { set3($$, $2); }
	;

direction:	'[' number ',' number ',' number ']'
		  { $$[0] = $2;
		    $$[1] = $4;
		    $$[2] = $6; }
	|	CAMERA VIEW
		  { set3($$, CameraGetView(global.plot->camera, global.tmp.p)); }
	|	CAMERA UP
		  { set3($$, CameraGetUp(global.plot->camera, global.tmp.p)); }
	|	number '*' direction
		  { set3($$, $3); mul31($$, $1); }
	|	direction '*' number
		  { set3($$, $1); mul31($$, $3); }
	|	'(' direction ')'
		  { set3($$, $2); }
	;


/* Selections refer to atom numbers within the geometry.
 */
select_item:	'<' ATOM number '>'
		  { if(global.g && ((int) $3) >= 0 && ((int) $3) < global.g->n)
		      $$ = select_range((int) $3, (int) $3 + 1);
		    else {
		      yyerror("invalid atom number");
		      YYERROR;
		    }
		  }
	|	'<' ALL '>'
		  { $$ = select_range(0, global.g ? global.g->n : 0); }
	|	'<' ATOM string '>'
		  { int i;
		    $$ = select_range(0, 0);
		    if(global.g) {
		      for(i=0; i<global.g->n; i++) {
			if(!strcmp(global.g->aname[i], $3))
			  $$ = select_union($$, select_range(i, i + 1));
		      }
		    }
		  }
	;

select:		select_item
	|	select ',' select_item
		  { $$ = select_union($1, $3); }
	;

color:		string
		 { $$ = ColorFromName($1); }
	;

mode_item:	FILLED { $$ = MA_FILLED; }
	|	OUTLINED { $$ = MA_OUTLINE; }
	|	SOLID { $$ = MA_SOLID; }
	|	TRANSLUCENT { $$ = MA_TRANSLUCENT; }
	|	MINIMAL { $$ = MA_MINIMAL; }
	|	MOON { $$ = MA_MOON; }
	|	NONE { $$ = 0; }
	|	STICK { $$ = MA_STICK; }
	;

mode:		mode_item
	|	mode '|' mode_item { $$ = $1 | $3; }
	;

/* The data types retured by the lexical scanner.
 */
number:		NUM
	|	number '+' number { $$ = $1 + $3; }
	|	number '-' number { $$ = $1 - $3; }
	|	number '*' number { $$ = $1 * $3; }
	|	number '/' number { $$ = $1 / $3; }
	|	R_VAR { $$ = $1->value.r; }
	|	R_FUN '(' ')' { $$ = $1->value.r_fun(); }
	|	R_FUN_R '(' number ')' { $$ = $1->value.r_fun_r($3); }
	|	'(' number ')' { $$ = $2; }
	|	TRUE { $$ = 1.0; }
	|	FALSE { $$ = 0.0; }
	;

string:		STRING 
	|	'<' GEOMETRY NAME '>'
		  { $$ = global.g ? AddToStringPool(global.parser->pool, global.g->name) : ""; }
	|	'<' ATOM number NAME '>'
		  { if(global.g && $3 >= 0 && $3 < global.g->n)
		      $$ = global.g->aname[(int) $3];
		    else {
		      yyerror("invalid atom numer");
		      YYERROR;
		    }
		  }
	|	'<' number '>' 
		  { char buf[64];
		    sprintf(buf, "%g", $2);
		    $$ = AddToStringPool(global.parser->pool, buf);
		  }
	|	string '+' string
		  { char *buf = mymalloc(sizeof(char) * (strlen($1) + strlen($3) + 1));
		    strcpy(buf, $1);
		    strcat(buf, $3);
		    $$ = AddToStringPool(global.parser->pool, buf);
		    myfree(buf);
		  }
	;

%%

#include <stdio.h>
#include <string.h>
#include <ctype.h>

static struct {
  char *s;
  int n;
} lextable[] = {
  {"add", ADD},
  {"all", ALL},
  {"atom", ATOM},
  {"attributes", ATTRIBUTES},
  {"background", BACKGROUND},
  {"bond", BOND},
  {"box", BOX},
  {"camera", CAMERA},
  {"center", CENTER},
  {"color", COLOR},
  {"exit", 0},
  {"false", FALSE},
  {"filled", FILLED},
  {"foreground", FOREGROUND},
  {"geometry", GEOMETRY},
  {"help", HELP},
  {"label", LABEL},
  {"line", LINE},
  {"minimal", MINIMAL},
  {"moon", MOON},
  {"mode", MODE},
  {"name", NAME},
  {"none", NONE},
  {"outlined", OUTLINED},
  {"perspective", PERSPECTIVE},
  {"plot", PLOT},
  {"position", POSITION},
  {"quit", 0},
  {"radius", RADIUS},
  {"size", SIZE},
  {"solid", SOLID},
  {"stick", STICK},
  {"translucent", TRANSLUCENT},
  {"true", TRUE},
  {"up", UP},
  {"view", VIEW},
  {"?", HELP},
  {NULL, 0}
};

/* Built-in functions.
 */

static double f_reset(void)
{
	DeleteMogulRules(global.parser->rules);
	global.parser->rules = NewMogulRules();
	MogulClearObjects(global.plot);
	return 0.0;
}

static double f_render(void)
{
	if(global.device)
	  MogulRender(global.plot, global.device);
	return 0.0;
}

static double f_plotExtent(void)
{
	double r, c[3];
	MogulObjectExtent(global.plot, c, &r);
	return r;
}

/* Get the next character from the lexical source.
 * Comments are filtered out here.
 */
static int lex_get_char(struct lex_s *lex)
{
	int comment = 0;

	do {

	  lex->lastc = lex->c;
	  lex->c = EOF;

	  switch(lex->type) {
	    case SRC_FILE:
	      lex->c = fgetc(lex->source.f);
	      if(lex->c != EOF) (lex->ch)++;
	      break;

	    case SRC_STRING:
	      if(lex->eof || lex->source.s[lex->ch] == '\0') {
		if(lex->eof)
		  lex->c = EOF;
		else {
		  /* add an extra newline the first time */
		  lex->c = '\n';
		  lex->eof = 1;
		}
	      }
	      else {
		lex->c = lex->source.s[(lex->ch)++];
	      }
	      break;
	  }

	  if(lex->c == '\n') (lex->line)++;

	  if(comment && lex->lastc == '\n') comment = 0;

	  if (lex->c == '#' && lex->lastc == '\n') comment = 1;

	} while(comment);

	return lex->c;
}

static void lex_unget_char(struct lex_s *lex, int c)
{
	if(lex->type == SRC_FILE) ungetc(c, lex->source.f);

	if(c == '\n') --(lex->line);

	if(lex->eof) 
	  lex->eof = 0;
	else
	  --(lex->ch);
}

static int lex_double(struct lex_s *lex, double *d)
{
	int c, l = 0;
	char buf[64];

	while((c = lex_get_char(lex)) != EOF &&
	      strchr("+-.e01234567890", c) && l < 62) buf[l++] = c;

	if(c != EOF) lex_unget_char(lex, c);

	buf[l] = '\0';
	if(sscanf(buf, "%lf", d) == 1) return 1;

	return 0;
}

static int match_keyword(char *keyword)
{
	int i, which = -1, n = 0;
	unsigned int l = strlen(keyword);

	for(i=0; lextable[i].s != NULL; i++) {
	  if(strncmp(keyword, lextable[i].s, l) == 0) {
	    n++;
	    which = i;
	  }
	}

	if(n == 1) return which;

	return -1;
}

#define isokforid(CHAR) (isalnum(CHAR) || (CHAR) == '.' || (CHAR) == '_')

/* This lexical analyzer is a modified version of that found in
 * Stallmans's "mfcalc" program in the BNU bison manual.
 */
#if defined(DEBUG) && 0
int yylex(void)
{
	int x_yylex(void);
	int code = x_yylex();
	switch(code) {
	  case NUM: printf("<NUM %g>", yylval.number); break;
	  case STRING: printf("<STRING \"%s\">", yylval.string); break;
	  case IDENTIFIER: printf("<IDENTIFIER %s>", yylval.sym->name); break;
	  case R_VAR: printf("<R_VAR %s (%g)>", yylval.sym->name, yylval.sym->value.r); break;
	  case R_FUN: printf("<R_FUN %s()>", yylval.sym->name); break;
	  case R_FUN_R: printf("<R_FUN_R %s(x)>", yylval.sym->name); break;
	  case '\n': putchar(code); break;
	  default:
	    if(code < 128 && isprint(code)) printf("<'%c' (code %d)>", (char) code, code);
	    else printf("<code %d>", code);
	    break;
	}
	fflush(stdout);
	return code;
}
int x_yylex(void)
#else
int yylex(void)
#endif
{
	int i, c, which;
	double sign = 1.0;

	static int buflen = 0;
	static char *buf = NULL;

	while((c = lex_get_char(&global.lex)) == ' ' || c == '\t');

	if(c == EOF) return 0;

	/* These can start a number, or be codes of their own.
	 */
	if(c == '+' || c == '-') 
	{
	  int csave = c;
	  c = lex_get_char(&global.lex);
	  if(c == '.' || isdigit(c)) {
	    sign = (csave == '-' ? -1.0 : + 1.0);
	  }
	  else {
	    lex_unget_char(&global.lex, c);
	    return csave;
	  }
	}

	if(c == '.' || isdigit(c))
	{
	  lex_unget_char(&global.lex, c);
	  if(lex_double(&global.lex, &yylval.number) == 1) {
	    yylval.number *= sign;
	    return NUM;
	  }
	  return 0;
	}

	if(isalpha(c))
	{
	  i = 0;
	  while(c != EOF && isokforid(c))
	  {
	    if(i >= buflen) {
	      buflen += 64;
	      buf = ResizePointerArray(buf, buflen);
	    }
	    buf[i++] = c;
	    c = lex_get_char(&global.lex);
	  }

	  lex_unget_char(&global.lex, c);
	  buf[i] = '\0';

	  /* If this is a keyword, return the proper code */
	  if((which = match_keyword(buf)) >= 0) return lextable[which].n;

	  /* Else, it may be an already defined symbol */
	  yylval.sym = find_symtab(global.parser, buf);
	  if(yylval.sym) return yylval.sym->type;

	  /* Finally, it must be a new identifier */
	  yylval.sym = new_symtab(global.parser, AddToStringPool(global.parser->pool, buf), 0);
	  return IDENTIFIER;
	}

	if(c == '"')
	{
	  i = 0;
	  do {
	    c = lex_get_char(&global.lex);
	    if(i >= buflen) {
	      buflen += 64;
	      buf = ResizePointerArray(buf, buflen);
	    }
	    if(c == '\\') {
	      int cc = lex_get_char(&global.lex);
	      switch(cc) {
		case 't': cc = '\t'; break;
		case 'n': cc = '\n'; break;
		default: break;
	      }
	      buf[i++] = cc;
	    }
	    else
	      buf[i++] = c;

	  } while(c != EOF && c != '"');

	  buf[i - 1] = '\0';
	  yylval.string = AddToStringPool(global.parser->pool, buf);
	  return STRING;
	}

	return c;
}

int yyerror(char *s)
{
	static char buf[128];

	if(global.lex.type == SRC_FILE && global.lex.source.f == stdin)
	  sprintf(buf, "%s\n", s);
	else if(global.lex.type == SRC_STRING)
	{
	  int first, last, c;
	  first = global.lex.ch - 16;
	  if(first < 0) first = 0;
	  for(last = first; last < first + 32; last++)
	    if(global.lex.source.s[last] == '\0' ||
	       global.lex.source.s[last] == '\n') break;
	  c = global.lex.source.s[last];
	  global.lex.source.s[last] = '\0';

	  sprintf(buf, "%s (line %d) near \"%s\"\n", s,
	    global.lex.line + 1, global.lex.source.s + first);
	  global.lex.source.s[last] = c;
	}
	else
	  sprintf(buf, "%s (line %d)\n", s, global.lex.line + 1);

	global.nerror++;
	MogulParserMsg = buf;

	return 0;
}

static int do_parse(MogulParser parser, MogulPlot plot, MogulDevice device)
{
	int r = 0, own_nl = 0;

	global.lex.line = global.lex.ch = 0;
	global.lex.eof = 0;
	global.lex.c = '\n'; /* to be stored as the last read character */

	global.plot = plot;
	global.device = device;
	global.parser = parser;
	global.autoRender = 0;

	if(global.g && global.nl == NULL) {
	  global.nl = nlalloc(1.e10);
	  newnl(global.g, global.nl);
	  own_nl = 1;
	}

	global.context = NULL;
	global.ncontext = 0;
	global.nerror = 0;
	push_context(1, INITIAL_CONTEXT);

	if(global.lex.type == SRC_FILE && global.lex.source.f == stdin) {
	  printf("%s", copyrightString);
	  global.prompt = "mogul> ";
	}
	else
	  global.prompt = NULL;

	prompt();
	r = yyparse();

	if(own_nl) nlfree(global.nl);
	delete_context();

	if(r || global.nerror) MogulErrno = MoErrParserError;

	return (r || global.nerror ? -1 : 0);
}

MogulParser NewMogulParser(void)
{
	MogulParser parser = NewPointer(parser);

	static struct {char *name; double (*fun)(double);} fun_r[] = {
	  {"sin", sin}, {"cos", cos}, {"sqrt", sqrt}, {"acos", acos},
	  {"asin", asin}, {"tan", tan}, {"exp", exp}, {"log", log},
	  {NULL, NULL}
	};

	static struct {char *name; double (*fun)(void);} fun[] = {
	  {"reset", f_reset}, {"render", f_render}, {"plotExtent", f_plotExtent},
	  {NULL, NULL}
	};

	parser->rules = NewMogulRules();
	parser->symtab = NULL;
	{ int i;
	  struct symtab_s *sym;
	  for(i=0; fun[i].name; i++) {
	    sym = new_symtab(parser, fun[i].name, R_FUN);
	    sym->value.r_fun = fun[i].fun;
	  }
	  for(i=0; fun_r[i].name; i++) {
	    sym = new_symtab(parser, fun_r[i].name, R_FUN_R);
	    sym->value.r_fun_r = fun_r[i].fun;
	  }
	}
	parser->pool = NewStringPool();
	return parser;
}

void DeleteMogulParser(MogulParser parser)
{
	if(parser) {
	  struct symtab_s *sym, *nsym;
	  DeleteMogulRules(parser->rules);
	  DeleteStringPool(parser->pool);
	  sym = parser->symtab;
	  while(sym) {
	    nsym = sym->next;
	    myfree(sym);
	    sym = nsym;
	  }
	  myfree(parser);
	}
}

/* The geometry or device may be NULL.
 */
int MogulParseString(MogulParser parser,
  char *command, MogulPlot p, MogulDevice device, Geometry *g, NeighborList *nl)
{
	if(command == NULL) return 0;

	global.lex.type = SRC_STRING;
	global.lex.source.s = command;

	global.g = g;
	global.nl = nl;

	return do_parse(parser, p, device);
}

/* The geometry or device may be NULL.
 */
int MogulParseFile(MogulParser parser,
  char *fn, MogulPlot p, MogulDevice device, Geometry *g, NeighborList *nl)
{
	int r;
	FILE *f;

	if(fn == NULL) return 0;

	f = (strcmp(fn, "-") ? fopen(fn, "r") : stdin);
	if(f == NULL) {
	    fprintf(stderr, "mogul: cannot open %s for input\n", fn);
	    return 1;
	}
	global.lex.type = SRC_FILE;
	global.lex.source.f = f;

	global.g = g;
	global.nl = nl;

	r = do_parse(parser, p, device);

	if(f != stdin) fclose(f);

	return r;
}

