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

%{
#include "pic.h"
#include "object.h"

//////////////////////////////////////////////////////////////////////
// PIC PARSER
// Public interface:
//
// parse_init()     [should be called only once]
// yyparse()
// parse_cleanup()  [should be called after each parse]
// define_variable(), lookup_variable()  [declared in object.h,
//                                        also used in lex.cc for `for' loops]
//
// delim_flag       [state flag read by lexer in lex.cc]
//
// The parser makes heavy use of special functions provided by the lexer,
// such as do_copy(), do_for(), copy_file_thru(), copy_rest_thru(),
// push_body(), and especially do_lookahead().  It also calls lex_warning()
// and lex_error().
//////////////////////////////////////////////////////////////////////

#undef fmod
#undef rand
#undef srand

extern "C" {
  double fmod(double, double);
  int rand();
  void srand(unsigned int);
}

// Maximum number of characters produced by printf("%g")
#define GDIGITS 14

static char sprintf_buf[1024];

direction current_direction;
position current_position;

implement_ptable(place)

PTABLE(place) top_table;

PTABLE(place) *current_table = &top_table;
saved_state *current_saved_state = 0;

object_list olist;

// external variable, used by lexer
int delim_flag = 0;

// forward references
static place *lookup_label(const char *label);
static void define_label(const char *label, const place *pl);
static void reset (const char *nm);
static void reset_all (void);
static const char * ordinal_postfix(int n);
static const char * object_type_name(object_type type);
static char * format_number(const char *form, double n);
static char * do_sprintf(const char *form, const double *v, int nv);

%}


%union {
	char *str;
	int n;
	double x;
	struct { double x, y; } pair;
	struct { double x; char *body; } if_data;
	struct { char *str; const char *filename; int lineno; } lstr;
	struct { double *v; int nv; int maxv; } dv;
	struct { double val; int is_multiplicative; } by;
	place pl;
	object *obj;
	corner crn;
	path *pth;
	object_spec *spec;
	saved_state *pstate;
	graphics_state state;
	object_type obtype;
}

%token <str> LABEL
%token <str> VARIABLE
%token <x> NUMBER
%token <lstr> TEXT
%token <lstr> COMMAND_LINE
%token <str> DELIMITED
%token <n> ORDINAL
%token TH
%token LEFT_ARROW_HEAD
%token RIGHT_ARROW_HEAD
%token DOUBLE_ARROW_HEAD
%token LAST
%token UP
%token DOWN
%token LEFT
%token RIGHT
%token BOX
%token CIRCLE
%token ELLIPSE
%token ARC
%token LINE
%token ARROW
%token MOVE
%token SPLINE
%token HEIGHT
%token RADIUS
%token WIDTH
%token DIAMETER
%token UP
%token DOWN
%token RIGHT
%token LEFT
%token FROM
%token TO
%token AT
%token WITH
%token BY
%token THEN
%token DOTTED
%token DASHED
%token CHOP
%token SAME
%token INVISIBLE
%token LJUST
%token RJUST
%token ABOVE
%token BELOW
%token OF
%token THE
%token WAY
%token BETWEEN
%token AND
%token HERE
%token DOT_N
%token DOT_E	
%token DOT_W
%token DOT_S
%token DOT_NE
%token DOT_SE
%token DOT_NW
%token DOT_SW
%token DOT_C
%token DOT_START
%token DOT_END
%token DOT_X
%token DOT_Y
%token DOT_HT
%token DOT_WID
%token DOT_RAD
%token SIN
%token COS
%token ATAN2
%token LOG
%token EXP
%token SQRT
%token K_MAX
%token K_MIN
%token INT
%token RAND
%token SRAND
%token COPY
%token THRU
%token TOP
%token BOTTOM
%token UPPER
%token LOWER
%token SH
%token PRINT
%token CW
%token CCW
%token FOR
%token DO
%token IF
%token ELSE
%token ANDAND
%token OROR
%token NOTEQUAL
%token EQUALEQUAL
%token LESSEQUAL
%token GREATEREQUAL
%token LEFT_CORNER
%token RIGHT_CORNER
%token CENTER
%token END
%token START
%token RESET
%token UNTIL
%token PLOT
%token THICKNESS
%token FILL
%token ALIGNED
%token SPRINTF
%token COMMAND

%token DEFINE
%token UNDEF

// this ensures that plot 17 "%g" parses as (plot 17 "%g")
%left PLOT
%left TEXT SPRINTF

// give text adjustments higher precedence than TEXT, so that
// box "foo" above ljust == box ("foo" above ljust)

%left LJUST RJUST ABOVE BELOW

%left LEFT RIGHT
// Give attributes that take an optional expression a higher precedence
// than left and right, so that eg `line chop left' parses properly.
%left CHOP DASHED DOTTED UP DOWN FILL
%left LABEL

%left VARIABLE NUMBER '(' SIN COS ATAN2 LOG EXP SQRT K_MAX K_MIN INT RAND SRAND LAST 
%left ORDINAL HERE '`'

// these need to be lower than '-'
%left HEIGHT RADIUS WIDTH DIAMETER FROM TO AT THICKNESS

// these must have higher precedence than CHOP so that `label %prec CHOP' works
%left DOT_N DOT_E DOT_W DOT_S DOT_NE DOT_SE DOT_NW DOT_SW DOT_C
%left DOT_START DOT_END TOP BOTTOM LEFT_CORNER RIGHT_CORNER
%left UPPER LOWER CENTER START END

%left ','
%left OROR
%left ANDAND
%left EQUALEQUAL NOTEQUAL
%left '<' '>' LESSEQUAL GREATEREQUAL

%left BETWEEN OF
%left AND

%left '+' '-'
%left '*' '/' '%'
%right '!'
%right '^'

%type <x> expr any_expr text_expr
%type <by> optional_by
%type <pair> expr_pair position_not_place
%type <if_data> simple_if
%type <obj> nth_primitive
%type <crn> corner
%type <pth> path label_path relative_path
%type <pl> place label element element_list middle_element_list
%type <spec> object_spec
%type <pair> position
%type <obtype> object_type
%type <n> optional_ordinal_last ordinal
%type <str> until
%type <dv> sprintf_args
%type <lstr> text print_args print_arg

%%

top:
	optional_separator
	| element_list
		{
		  if (olist.head)
		    print_picture(olist.head);
		}
	;


element_list:
	optional_separator middle_element_list optional_separator
		{ $$ = $2; }
	;

middle_element_list:
	element
		{ $$ = $1; }
	| middle_element_list separator element
		{ $$ = $1; }
	;

optional_separator:
	// empty
	| separator
	;

separator:
	';'
	| separator ';'
	;

placeless_element:
	VARIABLE '=' any_expr
		{
		  define_variable($1, $3);
		  a_delete $1;
		}
	| VARIABLE ':' '=' any_expr
		{
		  place *p = lookup_label($1);
		  if (!p) 
		    {
		      lex_error("variable `%1' not defined", $1);
		      YYABORT;
		    }
		  p->obj = 0;
		  p->x = $4;
		  p->y = 0.0;
		  a_delete $1;
		}
	| UP
		{ current_direction = UP_DIRECTION; }
	| DOWN
		{ current_direction = DOWN_DIRECTION; }
	| LEFT
		{ current_direction = LEFT_DIRECTION; }
	| RIGHT
		{ current_direction = RIGHT_DIRECTION; }
	| COMMAND_LINE
		{
		  olist.append(make_command_object($1.str, $1.filename,
						   $1.lineno));
		}
	| COMMAND print_args
		{
		  olist.append(make_command_object($2.str, $2.filename,
						   $2.lineno));
		}
	| PRINT print_args
		{
		  fprintf(stderr, "%s\n", $2.str);
		  a_delete $2.str;
	          fflush(stderr);
		}
	| SH
		{ delim_flag = 1; }
	  DELIMITED
		{
		  delim_flag = 0;
		  if (safer_flag)
		    lex_error("unsafe to run command `%1'", $3);
		  else
		    system($3);
		  a_delete $3;
		}
	| COPY TEXT
		{
		  if (yychar < 0)
		    do_lookahead();
		  do_copy($2.str);
		  // do not delete the filename
		}
	| COPY TEXT THRU
		{ delim_flag = 2; }
	  DELIMITED 
		{ delim_flag = 0; }
	  until
		{
		  if (yychar < 0)
		    do_lookahead();
		  copy_file_thru($2.str, $5, $7);
		  // do not delete the filename
		  a_delete $5;
		  a_delete $7;
		}
	| COPY THRU
		{ delim_flag = 2; }
	  DELIMITED
		{ delim_flag = 0; }
	  until
		{
		  if (yychar < 0)
		    do_lookahead();
		  copy_rest_thru($4, $6);
		  a_delete $4;
		  a_delete $6;
		}
	| FOR VARIABLE '=' expr TO expr optional_by DO
	  	{ delim_flag = 1; }
	  DELIMITED
	  	{
		  delim_flag = 0;
		  if (yychar < 0)
		    do_lookahead();
		  do_for($2, $4, $6, $7.is_multiplicative, $7.val, $10); 
		}
	| simple_if
		{
		  if (yychar < 0)
		    do_lookahead();
		  if ($1.x != 0.0)
		    push_body($1.body);
		  a_delete $1.body;
		}
	| simple_if ELSE
		{ delim_flag = 1; }
	  DELIMITED
		{
		  delim_flag = 0;
		  if (yychar < 0)
		    do_lookahead();
		  if ($1.x != 0.0)
		    push_body($1.body);
		  else
		    push_body($4);
		  a_delete $1.body;
		  a_delete $4;
		}
	| reset_variables
	| RESET
		{ define_variable("scale", 1.0); }
	;

reset_variables:
	RESET VARIABLE
		{ reset($2); a_delete $2; }
	| reset_variables VARIABLE
		{ reset($2); a_delete $2; }
	| reset_variables ',' VARIABLE
		{ reset($3); a_delete $3; }
	;

print_args:
	print_arg
		{ $$ = $1; }
	| print_args print_arg
		{
		  $$.str = new char[strlen($1.str) + strlen($2.str) + 1];
		  strcpy($$.str, $1.str);
		  strcat($$.str, $2.str);
		  a_delete $1.str;
		  a_delete $2.str;
		  if ($1.filename) 
		    {
		      $$.filename = $1.filename;
		      $$.lineno = $1.lineno;
		    }
		  else if ($2.filename) 
		    {
		      $$.filename = $2.filename;
		      $$.lineno = $2.lineno;
		    }
		}
	;

print_arg:
  	expr               %prec ','
		{
		  $$.str = new char[GDIGITS + 1];
		  sprintf($$.str, "%g", $1);
		  $$.filename = 0;
		  $$.lineno = 0;
		}
	| text
		{ $$ = $1; }
	| position          %prec ','
		{
		  $$.str = new char[GDIGITS + 2 + GDIGITS + 1];
		  sprintf($$.str, "%g, %g", $1.x, $1.y);
		  $$.filename = 0;
		  $$.lineno = 0;
		}

simple_if:
	IF any_expr THEN
		{ delim_flag = 1; }
	DELIMITED
		{ delim_flag = 0; $$.x = $2; $$.body = $5; }
	;

until:
	// empty
		{ $$ = 0; }
	| UNTIL TEXT
		{ $$ = $2.str; }
	;
	
any_expr:
	expr
		{ $$ = $1; }
	| text_expr
		{ $$ = $1; }
	;
	
text_expr:
	text EQUALEQUAL text
		{
		  $$ = strcmp($1.str, $3.str) == 0;
		  a_delete $1.str;
		  a_delete $3.str;
		}
	| text NOTEQUAL text
		{
		  $$ = strcmp($1.str, $3.str) != 0;
		  a_delete $1.str;
		  a_delete $3.str;
		}
	| text_expr ANDAND text_expr
		{ $$ = ($1 != 0.0 && $3 != 0.0); }
	| text_expr ANDAND expr
		{ $$ = ($1 != 0.0 && $3 != 0.0); }
	| expr ANDAND text_expr
		{ $$ = ($1 != 0.0 && $3 != 0.0); }
	| text_expr OROR text_expr
		{ $$ = ($1 != 0.0 || $3 != 0.0); }
	| text_expr OROR expr
		{ $$ = ($1 != 0.0 || $3 != 0.0); }
	| expr OROR text_expr
		{ $$ = ($1 != 0.0 || $3 != 0.0); }
	| '!' text_expr
		{ $$ = ($2 == 0.0); }
	;


optional_by:
	// empty
		{ $$.val = 1.0; $$.is_multiplicative = 0; }
	| BY expr
		{ $$.val = $2; $$.is_multiplicative = 0; }
	| BY '*' expr
		{ $$.val = $3; $$.is_multiplicative = 1; }
	;

element:
	object_spec
		{
		  $$.obj = $1->make_object(&current_position,
					   &current_direction);
		  if ($$.obj == 0)
		    YYABORT;
		  delete $1;
		  if ($$.obj)
		    olist.append($$.obj);
		  else 
		    {
		      $$.x = current_position.x;
		      $$.y = current_position.y;
		    }
		}
	| LABEL ':' optional_separator element
		{ $$ = $4; define_label($1, & $$); a_delete $1; }
	| LABEL ':' optional_separator position_not_place
		{
		  $$.obj = 0;
		  $$.x = $4.x;
		  $$.y = $4.y;
		  define_label($1, & $$);
		  a_delete $1;
		}
	| LABEL ':' optional_separator place
		{
		  $$ = $4;
		  define_label($1, & $$);
		  a_delete $1;
		}
	| '{'
		{
		  $<state>$.x = current_position.x;
		  $<state>$.y = current_position.y;
		  $<state>$.dir = current_direction;
		}
	  element_list '}'
		{
		  current_position.x = $<state>2.x;
		  current_position.y = $<state>2.y;
		  current_direction = $<state>2.dir;
		}
	  optional_element
		{
		  $$ = $3;
		}
	| placeless_element
		{
		  $$.obj = 0;
		  $$.x = current_position.x;
		  $$.y = current_position.y;
		}
	;

optional_element:
	// empty
		{}
	| element
		{}
	;

object_spec:
	BOX
		{
		  $$ = new object_spec(BOX_OBJECT);
		}
	| CIRCLE
		{
		  $$ = new object_spec(CIRCLE_OBJECT);
		}
	| ELLIPSE
		{
		  $$ = new object_spec(ELLIPSE_OBJECT);
		}
	| ARC
		{
		  $$ = new object_spec(ARC_OBJECT);
		  $$->dir = current_direction;
		}
	| LINE
		{
		  $$ = new object_spec(LINE_OBJECT);
		  lookup_variable("lineht", & $$->segment_height);
		  lookup_variable("linewid", & $$->segment_width);
		  $$->dir = current_direction;
		}
	| ARROW
		{
		  $$ = new object_spec(ARROW_OBJECT);
		  lookup_variable("lineht", & $$->segment_height);
		  lookup_variable("linewid", & $$->segment_width);
		  $$->dir = current_direction;
		}
	| MOVE
		{
		  $$ = new object_spec(MOVE_OBJECT);
		  lookup_variable("moveht", & $$->segment_height);
		  lookup_variable("movewid", & $$->segment_width);
		  $$->dir = current_direction;
		}
	| SPLINE
		{
		  $$ = new object_spec(SPLINE_OBJECT);
		  lookup_variable("lineht", & $$->segment_height);
		  lookup_variable("linewid", & $$->segment_width);
		  $$->dir = current_direction;
		}
	| text   %prec TEXT
		{
		  $$ = new object_spec(TEXT_OBJECT);
		  $$->text = new text_item($1.str, $1.filename, $1.lineno);
		}
	| PLOT expr
		{
		  $$ = new object_spec(TEXT_OBJECT);
		  $$->text = new text_item(format_number(0, $2), 0, -1);
		}
	| PLOT expr text
		{
		  $$ = new object_spec(TEXT_OBJECT);
		  $$->text = new text_item(format_number($3.str, $2),
					   $3.filename, $3.lineno);
		  a_delete $3.str;
		}
	| '[' 
		{
		  saved_state *p = new saved_state;
		  $<pstate>$ = p;
		  p->x = current_position.x;
		  p->y = current_position.y;
		  p->dir = current_direction;
		  p->tbl = current_table;
		  p->prev = current_saved_state;
		  current_position.x = 0.0;
		  current_position.y = 0.0;
		  current_table = new PTABLE(place);
		  current_saved_state = p;
		  olist.append(make_mark_object());
		}
	  element_list ']'
		{
		  current_position.x = $<pstate>2->x;
		  current_position.y = $<pstate>2->y;
		  current_direction = $<pstate>2->dir;
		  $$ = new object_spec(BLOCK_OBJECT);
		  olist.wrap_up_block(& $$->oblist);
		  $$->tbl = current_table;
		  current_table = $<pstate>2->tbl;
		  current_saved_state = $<pstate>2->prev;
		  delete $<pstate>2;
		}
	| object_spec HEIGHT expr
		{
		  $$ = $1;
		  $$->height = $3;
		  $$->flags |= HAS_HEIGHT;
		}
	| object_spec RADIUS expr
		{
		  $$ = $1;
		  $$->radius = $3;
		  $$->flags |= HAS_RADIUS;
		}
	| object_spec WIDTH expr
		{
		  $$ = $1;
		  $$->width = $3;
		  $$->flags |= HAS_WIDTH;
		}
	| object_spec DIAMETER expr
		{
		  $$ = $1;
		  $$->radius = $3/2.0;
		  $$->flags |= HAS_RADIUS;
		}
	| object_spec expr %prec HEIGHT
		{
		  $$ = $1;
		  $$->flags |= HAS_SEGMENT;
		  switch ($$->dir) 
		    {
		    case UP_DIRECTION:
		      $$->segment_pos.y += $2;
		      break;
		    case DOWN_DIRECTION:
		      $$->segment_pos.y -= $2;
		      break;
		    case RIGHT_DIRECTION:
		      $$->segment_pos.x += $2;
		      break;
		    case LEFT_DIRECTION:
		      $$->segment_pos.x -= $2;
		      break;
		    }
		}
	| object_spec UP
		{
		  $$ = $1;
		  $$->dir = UP_DIRECTION;
		  $$->flags |= HAS_SEGMENT;
		  $$->segment_pos.y += $$->segment_height;
		}
	| object_spec UP expr
		{
		  $$ = $1;
		  $$->dir = UP_DIRECTION;
		  $$->flags |= HAS_SEGMENT;
		  $$->segment_pos.y += $3;
		}
	| object_spec DOWN
		{
		  $$ = $1;
		  $$->dir = DOWN_DIRECTION;
		  $$->flags |= HAS_SEGMENT;
		  $$->segment_pos.y -= $$->segment_height;
		}
	| object_spec DOWN expr
		{
		  $$ = $1;
		  $$->dir = DOWN_DIRECTION;
		  $$->flags |= HAS_SEGMENT;
		  $$->segment_pos.y -= $3;
		}
	| object_spec RIGHT
		{
		  $$ = $1;
		  $$->dir = RIGHT_DIRECTION;
		  $$->flags |= HAS_SEGMENT;
		  $$->segment_pos.x += $$->segment_width;
		}
	| object_spec RIGHT expr
		{
		  $$ = $1;
		  $$->dir = RIGHT_DIRECTION;
		  $$->flags |= HAS_SEGMENT;
		  $$->segment_pos.x += $3;
		}
	| object_spec LEFT
		{
		  $$ = $1;
		  $$->dir = LEFT_DIRECTION;
		  $$->flags |= HAS_SEGMENT;
		  $$->segment_pos.x -= $$->segment_width;
		}
	| object_spec LEFT expr
		{
		  $$ = $1;
		  $$->dir = LEFT_DIRECTION;
		  $$->flags |= HAS_SEGMENT;
		  $$->segment_pos.x -= $3;
		}
	| object_spec FROM position
		{
		  $$ = $1;
		  $$->flags |= HAS_FROM;
		  $$->from.x = $3.x;
		  $$->from.y = $3.y;
		}
	| object_spec TO position
		{
		  $$ = $1;
		  if ($$->flags & HAS_SEGMENT)
		    $$->segment_list = new segment($$->segment_pos,
						   $$->segment_is_absolute,
						   $$->segment_list);
		  $$->flags |= HAS_SEGMENT;
		  $$->segment_pos.x = $3.x;
		  $$->segment_pos.y = $3.y;
		  $$->segment_is_absolute = 1;
		  $$->flags |= HAS_TO;
		  $$->to.x = $3.x;
		  $$->to.y = $3.y;
		}
	| object_spec AT position
		{
		  $$ = $1;
		  $$->flags |= HAS_AT;
		  $$->at.x = $3.x;
		  $$->at.y = $3.y;
		  if ($$->type != ARC_OBJECT)
		    {
		      $$->flags |= HAS_FROM;
		      $$->from.x = $3.x;
		      $$->from.y = $3.y;
		    }
		}
	| object_spec WITH path
		{
		  $$ = $1;
		  $$->flags |= HAS_WITH;
		  $$->with = $3;
		}
	| object_spec BY expr_pair
		{
		  $$ = $1;
		  $$->flags |= HAS_SEGMENT;
		  $$->segment_pos.x += $3.x;
		  $$->segment_pos.y += $3.y;
		}
	| object_spec THEN
  		{
		  $$ = $1;
		  if ($$->flags & HAS_SEGMENT) 
		    {
		      $$->segment_list = new segment($$->segment_pos,
						     $$->segment_is_absolute,
						     $$->segment_list);
		      $$->flags &= ~HAS_SEGMENT;
		      $$->segment_pos.x = $$->segment_pos.y = 0.0;
		      $$->segment_is_absolute = 0;
		    }
		}
	| object_spec DOTTED
		{
		  $$ = $1;
		  $$->flags |= IS_DOTTED;
		  lookup_variable("dashwid", & $$->dash_width);
		}
	| object_spec DOTTED expr
		{
		  $$ = $1;
		  $$->flags |= IS_DOTTED;
		  $$->dash_width = $3;
		}
	| object_spec DASHED
		{
		  $$ = $1;
		  $$->flags |= IS_DASHED;
		  lookup_variable("dashwid", & $$->dash_width);
		}
	| object_spec DASHED expr
		{
		  $$ = $1;
		  $$->flags |= IS_DASHED;
		  $$->dash_width = $3;
		}
	| object_spec FILL
		{
		  $$ = $1;
		  $$->flags |= IS_DEFAULT_FILLED;
		}
	| object_spec FILL expr
		{
		  $$ = $1;
		  $$->flags |= IS_FILLED;
		  $$->fill = $3;
		}
	| object_spec CHOP
  		{
		  $$ = $1;
		  // line chop chop means line chop 0 chop 0
		  if ($$->flags & IS_DEFAULT_CHOPPED) 
		    {
		      $$->flags |= IS_CHOPPED;
		      $$->flags &= ~IS_DEFAULT_CHOPPED;
		      $$->start_chop = $$->end_chop = 0.0;
		    }
		  else if ($$->flags & IS_CHOPPED) 
		    {
		      $$->end_chop = 0.0;
		    }
		  else 
		    {
		      $$->flags |= IS_DEFAULT_CHOPPED;
		    }
		}
	| object_spec CHOP expr
		{
		  $$ = $1;
		  if ($$->flags & IS_DEFAULT_CHOPPED) 
		    {
		      $$->flags |= IS_CHOPPED;
		      $$->flags &= ~IS_DEFAULT_CHOPPED;
		      $$->start_chop = 0.0;
		      $$->end_chop = $3;
		    }
		  else if ($$->flags & IS_CHOPPED) 
		    {
		      $$->end_chop = $3;
		    }
		  else 
		    {
		      $$->start_chop = $$->end_chop = $3;
		      $$->flags |= IS_CHOPPED;
		    }
		}
	| object_spec SAME
		{
		  $$ = $1;
		  $$->flags |= IS_SAME;
		}
	| object_spec INVISIBLE
		{
		  $$ = $1;
		  $$->flags |= IS_INVISIBLE;
		}
	| object_spec LEFT_ARROW_HEAD
		{
		  $$ = $1;
		  $$->flags |= HAS_LEFT_ARROW_HEAD;
		}
	| object_spec RIGHT_ARROW_HEAD
		{
		  $$ = $1;
		  $$->flags |= HAS_RIGHT_ARROW_HEAD;
		}
	| object_spec DOUBLE_ARROW_HEAD
		{
		  $$ = $1;
		  $$->flags |= (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD);
		}
	| object_spec CW
		{
		  $$ = $1;
		  $$->flags |= IS_CLOCKWISE;
		}
	| object_spec CCW
		{
		  $$ = $1;
		  $$->flags &= ~IS_CLOCKWISE;
		}
	| object_spec text   %prec TEXT
		{
		  $$ = $1;
		  text_item **p;
		  for (p = & $$->text; *p; p = &(*p)->next)
		    ;
		  *p = new text_item($2.str, $2.filename, $2.lineno);
		}
	| object_spec LJUST
		{
		  $$ = $1;
		  if ($$->text) 
		    {
		      text_item *p;
		      for (p = $$->text; p->next; p = p->next)
			;
		      p->adj.h = LEFT_ADJUST;
		    }
		}
	| object_spec RJUST
		{
		  $$ = $1;
		  if ($$->text) 
		    {
		      text_item *p;
		      for (p = $$->text; p->next; p = p->next)
			;
		      p->adj.h = RIGHT_ADJUST;
		    }
		}
	| object_spec ABOVE
		{
		  $$ = $1;
		  if ($$->text) 
		    {
		      text_item *p;
		      for (p = $$->text; p->next; p = p->next)
			;
		      p->adj.v = ABOVE_ADJUST;
		    }
		}
	| object_spec BELOW
		{
		  $$ = $1;
		  if ($$->text) 
		    {
		      text_item *p;
		      for (p = $$->text; p->next; p = p->next)
			;
		      p->adj.v = BELOW_ADJUST;
		    }
		}
	| object_spec THICKNESS expr
		{
		  $$ = $1;
		  $$->flags |= HAS_THICKNESS;
		  $$->thickness = $3;
		}
	| object_spec ALIGNED
		{
		  $$ = $1;
		  $$->flags |= IS_ALIGNED;
		}
	;

text:
	TEXT
		{
		  $$ = $1;
		}
	| SPRINTF '(' TEXT sprintf_args ')'
		{
		  $$.filename = $3.filename;
		  $$.lineno = $3.lineno;
		  $$.str = do_sprintf($3.str, $4.v, $4.nv);
		  a_delete $4.v;
		  a_delete $3.str;
		}
	;

sprintf_args:
	// empty
		{
		  $$.v = 0;
		  $$.nv = 0;
		  $$.maxv = 0;
		}
	| sprintf_args ',' expr
		{
		  $$ = $1;
		  if ($$.nv >= $$.maxv) 
		    {
		      if ($$.nv == 0) 
			{
			  $$.v = new double[4];
			  $$.maxv = 4;
			}
		      else 
			{
			  double *oldv = $$.v;
			  $$.maxv *= 2;
			  $$.v = new double[$$.maxv];
			  memcpy($$.v, oldv, $$.nv*sizeof(double));
			  a_delete oldv;
			}
		    }
		  $$.v[$$.nv] = $3;
		  $$.nv += 1;
		}
	;

position:
  	position_not_place
		{ $$ = $1; }
	| place
  		{
		  position pos = $1;
		  $$.x = pos.x;
		  $$.y = pos.y;
		}
	;

position_not_place:
	expr_pair
		{ $$ = $1; }
	| position '+' expr_pair
		{
		  $$.x = $1.x + $3.x;
		  $$.y = $1.y + $3.y;
		}
	| position '-' expr_pair
		{
		  $$.x = $1.x - $3.x;
		  $$.y = $1.y - $3.y;
		}
	| '(' position ',' position ')'
		{
		  $$.x = $2.x;
		  $$.y = $4.y;
		}
	| expr between position AND position
		{
		  $$.x = (1.0 - $1)*$3.x + $1*$5.x;
		  $$.y = (1.0 - $1)*$3.y + $1*$5.y;
		}
	| expr '<' position ',' position '>'
		{
		  $$.x = (1.0 - $1)*$3.x + $1*$5.x;
		  $$.y = (1.0 - $1)*$3.y + $1*$5.y;
		}
	;

between:
	BETWEEN
	| OF THE WAY BETWEEN
	;

expr_pair:
	expr ',' expr
		{ $$.x = $1; $$.y = $3; }
	| '(' expr_pair ')'
		{ $$ = $2; }
	;

place:
	label  %prec CHOP // line at A left == line (at A) left
		{ $$ = $1; }
	| label corner
		{
		  path pth($2);
		  if (!pth.follow($1, & $$))
		    YYABORT;
		}
	| corner label
		{
		  path pth($1);
		  if (!pth.follow($2, & $$))
		    YYABORT;
		}
	| corner OF label
		{
		  path pth($1);
		  if (!pth.follow($3, & $$))
		    YYABORT;
		}
	| HERE
		{
		  $$.x = current_position.x;
		  $$.y = current_position.y;
		  $$.obj = 0;
		}
	;

label:
	LABEL
		{
		  place *p = lookup_label($1);
		  if (!p) 
		    {
		      lex_error("there is no place `%1'", $1);
		      YYABORT;
		    }
		  $$ = *p;
		  a_delete $1;
		}
	| nth_primitive
		{
		  $$.obj = $1;
		}
	| label '.' LABEL
		{
		  path pth($3);
		  if (!pth.follow($1, & $$))
		    YYABORT;
		}
	;

ordinal:
	ORDINAL
		{ $$ = $1; }
	| '`' any_expr TH
		{
		  // XXX Check for overflow (and non-integers?).
		  $$ = (int)$2;
		}
	;

optional_ordinal_last:
        LAST
		{ $$ = 1; }
  	| ordinal LAST
		{ $$ = $1; }
	;

nth_primitive:
	ordinal object_type
		{
		  int count = 0;
		  object *p;
		  for (p = olist.head; p != 0; p = p->next)
		    if (p->type() == $2 && ++count == $1) 
		      {
			$$ = p;
			break;
		      }
		  if (p == 0) 
		    {
		      lex_error("there is no %1%2 %3", $1, ordinal_postfix($1),
				object_type_name($2));
		      YYABORT;
		    }
		}
	| optional_ordinal_last object_type
		{
		  int count = 0;
		  object *p;
		  for (p = olist.tail; p != 0; p = p->prev)
		    if (p->type() == $2 && ++count == $1) 
		      {
			$$ = p;
			break;
		      }
		  if (p == 0) 
		    {
		      lex_error("there is no %1%2 last %3", $1,
				ordinal_postfix($1), object_type_name($2));
		      YYABORT;
		    }
		}
	;

object_type:
	BOX
  		{ $$ = BOX_OBJECT; }
	| CIRCLE
		{ $$ = CIRCLE_OBJECT; }
	| ELLIPSE
		{ $$ = ELLIPSE_OBJECT; }
	| ARC
		{ $$ = ARC_OBJECT; }
	| LINE
		{ $$ = LINE_OBJECT; }
	| ARROW
		{ $$ = ARROW_OBJECT; }
	| SPLINE
		{ $$ = SPLINE_OBJECT; }
	| '[' ']'
		{ $$ = BLOCK_OBJECT; }
	| TEXT
		{ $$ = TEXT_OBJECT; }
	;

label_path:
 	'.' LABEL
		{
		  $$ = new path($2);
		}
	| label_path '.' LABEL
		{
		  $$ = $1;
		  $$->append($3);
		}
	;

relative_path:
	corner
		{
		  $$ = new path($1);
		}
	// give this a lower precedence than LEFT and RIGHT so that
	// [A: box] with .A left == [A: box] with (.A left)

  	| label_path %prec TEXT
		{
		  $$ = $1;
		}
	| label_path corner
		{
		  $$ = $1;
		  $$->append($2);
		}
	;

path:
	relative_path
		{
		  $$ = $1;
		}
	| '(' relative_path ',' relative_path ')'
		{
		  $$ = $2;
		  $$->set_ypath($4);
		}
	// The rest of these rules are a compatibility sop.
	| ORDINAL LAST object_type relative_path
		{
		  lex_warning("`%1%2 last %3' in `with' argument ignored",
			      $1, ordinal_postfix($1), object_type_name($3));
		  $$ = $4;
		}
	| LAST object_type relative_path
		{
		  lex_warning("`last %1' in `with' argument ignored",
			      object_type_name($2));
		  $$ = $3;
		}
	| ORDINAL object_type relative_path
		{
		  lex_warning("`%1%2 %3' in `with' argument ignored",
			      $1, ordinal_postfix($1), object_type_name($2));
		  $$ = $3;
		}
	| LABEL relative_path
		{
		  lex_warning("initial `%1' in `with' argument ignored", $1);
		  a_delete $1;
		  $$ = $2;
		}
	;

corner:
	DOT_N
		{ $$ = &object::north; }
	| DOT_E	
		{ $$ = &object::east; }
	| DOT_W
		{ $$ = &object::west; }
	| DOT_S
		{ $$ = &object::south; }
	| DOT_NE
		{ $$ = &object::north_east; }
	| DOT_SE
		{ $$ = &object:: south_east; }
	| DOT_NW
		{ $$ = &object::north_west; }
	| DOT_SW
		{ $$ = &object::south_west; }
	| DOT_C
		{ $$ = &object::center; }
	| DOT_START
		{ $$ = &object::start; }
	| DOT_END
		{ $$ = &object::end; }
  	| TOP
		{ $$ = &object::north; }
	| BOTTOM
		{ $$ = &object::south; }
	| LEFT
		{ $$ = &object::west; }
	| RIGHT
		{ $$ = &object::east; }
	| UPPER LEFT
		{ $$ = &object::north_west; }
	| LOWER LEFT
		{ $$ = &object::south_west; }
	| UPPER RIGHT
		{ $$ = &object::north_east; }
	| LOWER RIGHT
		{ $$ = &object::south_east; }
	| LEFT_CORNER
		{ $$ = &object::west; }
	| RIGHT_CORNER
		{ $$ = &object::east; }
	| UPPER LEFT_CORNER
		{ $$ = &object::north_west; }
	| LOWER LEFT_CORNER
		{ $$ = &object::south_west; }
	| UPPER RIGHT_CORNER
		{ $$ = &object::north_east; }
	| LOWER RIGHT_CORNER
		{ $$ = &object::south_east; }
	| CENTER
		{ $$ = &object::center; }
	| START
		{ $$ = &object::start; }
	| END
		{ $$ = &object::end; }
	;

expr:
	VARIABLE
		{
		  if (!lookup_variable($1, & $$)) 
		    {
		      lex_error("there is no variable `%1'", $1);
		      YYABORT;
		    }
		  a_delete $1;
		}
	| NUMBER
		{ $$ = $1; }
	| place DOT_X
  		{
		  if ($1.obj != 0)
		    $$ = $1.obj->origin().x;
		  else
		    $$ = $1.x;
		}			
	| place DOT_Y
		{
		  if ($1.obj != 0)
		    $$ = $1.obj->origin().y;
		  else
		    $$ = $1.y;
		}
	| place DOT_HT
		{
		  if ($1.obj != 0)
		    $$ = $1.obj->height();
		  else
		    $$ = 0.0;
		}
	| place DOT_WID
		{
		  if ($1.obj != 0)
		    $$ = $1.obj->width();
		  else
		    $$ = 0.0;
		}
	| place DOT_RAD
		{
		  if ($1.obj != 0)
		    $$ = $1.obj->radius();
		  else
		    $$ = 0.0;
		}
	| expr '+' expr
		{ $$ = $1 + $3; }
	| expr '-' expr
		{ $$ = $1 - $3; }
	| expr '*' expr
		{ $$ = $1 * $3; }
	| expr '/' expr
		{
		  if ($3 == 0.0) 
		    {
		      lex_error("division by zero");
		      YYABORT;
		    }
		  $$ = $1/$3;
		}
	| expr '%' expr
		{
		  if ($3 == 0.0) 
		    {
		      lex_error("modulus by zero");
		      YYABORT;
		    }
		  $$ = fmod($1, $3);
		}
	| expr '^' expr
		{
		  errno = 0;
		  $$ = pow($1, $3);
		  if (errno == EDOM) 
		    {
		      lex_error("arguments to `^' operator out of domain");
		      YYABORT;
		    }
		  if (errno == ERANGE) 
		    {
		      lex_error("result of `^' operator out of range");
		      YYABORT;
		  }
		}
	| '-' expr    %prec '!'
		{ $$ = -$2; }
	| '(' any_expr ')'
		{ $$ = $2; }
	| SIN '(' any_expr ')'
		{
		  errno = 0;
		  $$ = sin($3);
		  if (errno == ERANGE) 
		    {
		      lex_error("sin result out of range");
		      YYABORT;
		    }
		}
	| COS '(' any_expr ')'
		{
		  errno = 0;
		  $$ = cos($3);
		  if (errno == ERANGE) 
		    {
		      lex_error("cos result out of range");
		      YYABORT;
		    }
		}
	| ATAN2 '(' any_expr ',' any_expr ')'
		{
		  errno = 0;
		  $$ = atan2($3, $5);
		  if (errno == EDOM) 
		    {
		      lex_error("atan2 argument out of domain");
		      YYABORT;
		    }
		  if (errno == ERANGE)
		    {
		      lex_error("atan2 result out of range");
		      YYABORT;
		  }
		}
	| LOG '(' any_expr ')'
		{
		  errno = 0;
		  $$ = log10($3);
		  if (errno == ERANGE) 
		    {
		      lex_error("log result out of range");
		      YYABORT;
		    }
		}
	| EXP '(' any_expr ')'
		{
		  errno = 0;
		  $$ = pow(10.0, $3);
		  if (errno == ERANGE) 
		    {
		      lex_error("exp result out of range");
		      YYABORT;
		    }
		}
	| SQRT '(' any_expr ')'
		{
		  errno = 0;
		  $$ = sqrt($3);
		  if (errno == EDOM) 
		    {
		      lex_error("sqrt argument out of domain");
		      YYABORT;
		    }
		}
	| K_MAX '(' any_expr ',' any_expr ')'
		{ $$ = $3 > $5 ? $3 : $5; }
	| K_MIN '(' any_expr ',' any_expr ')'
		{ $$ = $3 < $5 ? $3 : $5; }
	| INT '(' any_expr ')'
		{ $$ = floor($3); }
	| RAND '(' any_expr ')'
		{ $$ = 1.0 + floor(((rand()&0x7fff)/double(0x7fff))*$3); }
	| RAND '(' ')'
		{
		  // return a random number in the range [0,1)
		  // portable, but not very random
		  $$ = (rand() & 0x7fff) / double(0x8000);
		}
        | SRAND '(' any_expr ')'
                { $$ = 0; srand((unsigned int)$3); }
	| expr '<' expr
		{ $$ = ($1 < $3); }
	| expr LESSEQUAL expr
		{ $$ = ($1 <= $3); }
	| expr '>' expr
		{ $$ = ($1 > $3); }
	| expr GREATEREQUAL expr
		{ $$ = ($1 >= $3); }
	| expr EQUALEQUAL expr
		{ $$ = ($1 == $3); }
	| expr NOTEQUAL expr
		{ $$ = ($1 != $3); }
	| expr ANDAND expr
		{ $$ = ($1 != 0.0 && $3 != 0.0); }
	| expr OROR expr
		{ $$ = ($1 != 0.0 || $3 != 0.0); }
	| '!' expr
		{ $$ = ($2 == 0.0); }

	;

%%

// bison defines const to be empty unless __STDC__ is defined, which it
// isn't under cfront

#ifdef const
#undef const
#endif

static struct 
{
  const char *name;
  double val;
  int scaled;		     // non-zero if val should be multiplied by scale
} defaults_table[] = {
  { "arcrad", .25, 1 },
  { "arrowht", .1, 1 },
  { "arrowwid", .05, 1 },
  { "circlerad", .25, 1 },
  { "boxht", .5, 1 },
  { "boxwid", .75, 1 },
  { "boxrad", 0.0, 1 },
  { "dashwid", .05, 1 },
  { "ellipseht", .5, 1 },
  { "ellipsewid", .75, 1 },
  { "moveht", .5, 1 },
  { "movewid", .5, 1 },
  { "lineht", .5, 1 },
  { "linewid", .5, 1 },
  { "textht", 0.0, 1 },
  { "textwid", 0.0, 1 },
  { "scale", 1.0, 0 },
  { "linethick", -1.0, 1 },	// in points (<0 is default); now scaled
  { "fillval", .5, 0 },
  { "arrowhead", 1.0, 0 },
  { "maxpswid", 8.5, 0 },
  { "maxpsht", 11.0, 0 },
};

static place *
lookup_label(const char *label)
{
  saved_state *state = current_saved_state;
  PTABLE(place) *tbl = current_table;
  for (;;) 
    {
      place *pl = tbl->lookup(label);
      if (pl)
	return pl;
      if (!state)
	return 0;
      tbl = state->tbl;
      state = state->prev;
    }
}

static void 
define_label(const char *label, const place *pl)
{
  place *p = new place;
  *p = *pl;
  current_table->define(label, p);
}

int
lookup_variable(const char *name, double *val)
{
  place *pl = lookup_label(name);
  if (pl) 
    {
      *val = pl->x;
      return 1;
    }
  return 0;
}

void 
define_variable(const char *name, double val)
{
  place *p = new place;
  p->obj = 0;
  p->x = val;
  p->y = 0.0;
  current_table->define(name, p);
  if (strcmp(name, "scale") == 0) 
    {
      // When the scale changes, reset all scaled pre-defined variables to
      // their default values.
      for (unsigned int i = 0; i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++) 
	if (defaults_table[i].scaled)
	  define_variable(defaults_table[i].name, val*defaults_table[i].val);
    }
}

// called once only (not once per parse)

void 
parse_init (void)
{
  current_direction = RIGHT_DIRECTION;
  current_position.x = 0.0;
  current_position.y = 0.0;
  // This resets everything to its default value.
  reset_all();
}

static void 
reset (const char *nm)
{
  for (unsigned int i = 0; i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
    if (strcmp(nm, defaults_table[i].name) == 0) 
      {
	double val = defaults_table[i].val;
	if (defaults_table[i].scaled) 
	  {
	    double scale;
	    lookup_variable("scale", &scale);
	    val *= scale;
	  }
	define_variable(defaults_table[i].name, val);
	return;
      }
  lex_error("`%1' is not a predefined variable", nm);
}

static void
reset_all (void)
{
  // We only have to explicitly reset the pre-defined variables that
  // aren't scaled because `scale' is not scaled, and changing the
  // value of `scale' will reset all the pre-defined variables that
  // are scaled.
  for (unsigned int i = 0; i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
    if (!defaults_table[i].scaled)
      define_variable(defaults_table[i].name, defaults_table[i].val);
}

// called after each parse

void
parse_cleanup (void)
{
  while (current_saved_state != 0) 
    {
      delete current_table;
      current_table = current_saved_state->tbl;
      saved_state *tem = current_saved_state;
      current_saved_state = current_saved_state->prev;
      delete tem;
    }
  assert(current_table == &top_table);
  PTABLE_ITERATOR(place) iter(current_table);
  const char *key;
  place *pl;
  while (iter.next(&key, &pl))
    if (pl->obj != 0) 
      {
	position pos = pl->obj->origin();
	pl->obj = 0;
	pl->x = pos.x;
	pl->y = pos.y;
      }
  while (olist.head != 0) 
    {
      object *tem = olist.head;
      olist.head = olist.head->next;
      delete tem;
    }
  olist.tail = 0;
  current_direction = RIGHT_DIRECTION;
  current_position.x = 0.0;
  current_position.y = 0.0;
}

static const char *
ordinal_postfix(int n)
{
  if (n < 10 || n > 20)
    switch (n % 10) 
      {
      case 1:
	return "st";
      case 2:
	return "nd";
      case 3:
	return "rd";
      }
  return "th";
}

static const char *
object_type_name(object_type type)
{
  switch (type) 
    {
    case BOX_OBJECT:
      return "box";
    case CIRCLE_OBJECT:
      return "circle";
    case ELLIPSE_OBJECT:
      return "ellipse";
    case ARC_OBJECT:
      return "arc";
    case SPLINE_OBJECT:
      return "spline";
    case LINE_OBJECT:
      return "line";
    case ARROW_OBJECT:
      return "arrow";
    case MOVE_OBJECT:
      return "move";
    case TEXT_OBJECT:
      return "\"\"";
    case BLOCK_OBJECT:
      return "[]";
    case OTHER_OBJECT:
    case MARK_OBJECT:
    default:
      break;
    }
  return "object";
}

static char *
format_number(const char *form, double n)
{
  if (form == 0)
    form = "%g";
  else 
    {
      // this is a fairly feeble attempt at validation of the format
      int nspecs = 0;
      for (const char *p = form; *p != '\0'; p++)
	if (*p == '%') 
	  {
	    if (p[1] == '%')
	      p++;
	    else
	      nspecs++;
	  }
      if (nspecs > 1) 
	{
	  lex_error("bad format `%1'", form);
	  return strsave(form);
	}
    }
  sprintf(sprintf_buf, form, n);
  return strsave(sprintf_buf);
}

static char *
do_sprintf(const char *form, const double *v, int nv)
{
  string result;
  int i = 0;
  string one_format;
  while (*form) 
    {
      if (*form == '%') 
	{
	  one_format += *form++;
	  for (; *form != '\0' && strchr("#-+ 0123456789.", *form) != 0; form++)
	    one_format += *form;
	  if (*form == '\0' || strchr("eEfgG%", *form) == 0) 
	    {
	      lex_error("bad sprintf format");
	      result += one_format;
	      result += form;
	      break;
	    }
	  if (*form == '%') 
	    {
	      one_format += *form++;
	      one_format += '\0';
	      sprintf(sprintf_buf, "%s", one_format.contents());
	    }
	  else 
	    {
	      if (i >= nv) 
		{
		  lex_error("too few arguments to sprintf");
		  result += one_format;
		  result += form;
		  break;
		}
	      one_format += *form++;
	      one_format += '\0';
	      sprintf(sprintf_buf, one_format.contents(), v[i++]);
	    }
	  one_format.clear();
	  result += sprintf_buf;
	}
      else
	result += *form++;
    }
  result += '\0';
  return strsave(result.contents());
}
