PageRenderTime 23ms CodeModel.GetById 9ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 1ms

/contrib/groff/src/preproc/pic/pic.y

https://bitbucket.org/freebsd/freebsd-head/
Happy | 1898 lines | 1807 code | 91 blank | 0 comment | 0 complexity | 44dd625f16f386f5b1aefd3762395d63 MD5 | raw file
   1/* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005
   2   Free Software Foundation, Inc.
   3     Written by James Clark (jjc@jclark.com)
   4
   5This file is part of groff.
   6
   7groff is free software; you can redistribute it and/or modify it under
   8the terms of the GNU General Public License as published by the Free
   9Software Foundation; either version 2, or (at your option) any later
  10version.
  11
  12groff is distributed in the hope that it will be useful, but WITHOUT ANY
  13WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  15for more details.
  16
  17You should have received a copy of the GNU General Public License along
  18with groff; see the file COPYING.  If not, write to the Free Software
  19Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
  20%{
  21#include "pic.h"
  22#include "ptable.h"
  23#include "object.h"
  24
  25extern int delim_flag;
  26extern void copy_rest_thru(const char *, const char *);
  27extern void copy_file_thru(const char *, const char *, const char *);
  28extern void push_body(const char *);
  29extern void do_for(char *var, double from, double to,
  30		   int by_is_multiplicative, double by, char *body);
  31extern void do_lookahead();
  32
  33/* Maximum number of characters produced by printf("%g") */
  34#define GDIGITS 14
  35
  36int yylex();
  37void yyerror(const char *);
  38
  39void reset(const char *nm);
  40void reset_all();
  41
  42place *lookup_label(const char *);
  43void define_label(const char *label, const place *pl);
  44
  45direction current_direction;
  46position current_position;
  47
  48implement_ptable(place)
  49
  50PTABLE(place) top_table;
  51
  52PTABLE(place) *current_table = &top_table;
  53saved_state *current_saved_state = 0;
  54
  55object_list olist;
  56
  57const char *ordinal_postfix(int n);
  58const char *object_type_name(object_type type);
  59char *format_number(const char *form, double n);
  60char *do_sprintf(const char *form, const double *v, int nv);
  61
  62%}
  63
  64
  65%union {
  66	char *str;
  67	int n;
  68	double x;
  69	struct { double x, y; } pair;
  70	struct { double x; char *body; } if_data;
  71	struct { char *str; const char *filename; int lineno; } lstr;
  72	struct { double *v; int nv; int maxv; } dv;
  73	struct { double val; int is_multiplicative; } by;
  74	place pl;
  75	object *obj;
  76	corner crn;
  77	path *pth;
  78	object_spec *spec;
  79	saved_state *pstate;
  80	graphics_state state;
  81	object_type obtype;
  82}
  83
  84%token <str> LABEL
  85%token <str> VARIABLE
  86%token <x> NUMBER
  87%token <lstr> TEXT
  88%token <lstr> COMMAND_LINE
  89%token <str> DELIMITED
  90%token <n> ORDINAL
  91%token TH
  92%token LEFT_ARROW_HEAD
  93%token RIGHT_ARROW_HEAD
  94%token DOUBLE_ARROW_HEAD
  95%token LAST
  96%token UP
  97%token DOWN
  98%token LEFT
  99%token RIGHT
 100%token BOX
 101%token CIRCLE
 102%token ELLIPSE
 103%token ARC
 104%token LINE
 105%token ARROW
 106%token MOVE
 107%token SPLINE
 108%token HEIGHT
 109%token RADIUS
 110%token FIGNAME
 111%token WIDTH
 112%token DIAMETER
 113%token UP
 114%token DOWN
 115%token RIGHT
 116%token LEFT
 117%token FROM
 118%token TO
 119%token AT
 120%token WITH
 121%token BY
 122%token THEN
 123%token SOLID
 124%token DOTTED
 125%token DASHED
 126%token CHOP
 127%token SAME
 128%token INVISIBLE
 129%token LJUST
 130%token RJUST
 131%token ABOVE
 132%token BELOW
 133%token OF
 134%token THE
 135%token WAY
 136%token BETWEEN
 137%token AND
 138%token HERE
 139%token DOT_N
 140%token DOT_E	
 141%token DOT_W
 142%token DOT_S
 143%token DOT_NE
 144%token DOT_SE
 145%token DOT_NW
 146%token DOT_SW
 147%token DOT_C
 148%token DOT_START
 149%token DOT_END
 150%token DOT_X
 151%token DOT_Y
 152%token DOT_HT
 153%token DOT_WID
 154%token DOT_RAD
 155%token SIN
 156%token COS
 157%token ATAN2
 158%token LOG
 159%token EXP
 160%token SQRT
 161%token K_MAX
 162%token K_MIN
 163%token INT
 164%token RAND
 165%token SRAND
 166%token COPY
 167%token THRU
 168%token TOP
 169%token BOTTOM
 170%token UPPER
 171%token LOWER
 172%token SH
 173%token PRINT
 174%token CW
 175%token CCW
 176%token FOR
 177%token DO
 178%token IF
 179%token ELSE
 180%token ANDAND
 181%token OROR
 182%token NOTEQUAL
 183%token EQUALEQUAL
 184%token LESSEQUAL
 185%token GREATEREQUAL
 186%token LEFT_CORNER
 187%token RIGHT_CORNER
 188%token NORTH
 189%token SOUTH
 190%token EAST
 191%token WEST
 192%token CENTER
 193%token END
 194%token START
 195%token RESET
 196%token UNTIL
 197%token PLOT
 198%token THICKNESS
 199%token FILL
 200%token COLORED
 201%token OUTLINED
 202%token SHADED
 203%token ALIGNED
 204%token SPRINTF
 205%token COMMAND
 206
 207%token DEFINE
 208%token UNDEF
 209
 210%left '.'
 211
 212/* this ensures that plot 17 "%g" parses as (plot 17 "%g") */
 213%left PLOT
 214%left TEXT SPRINTF
 215
 216/* give text adjustments higher precedence than TEXT, so that
 217box "foo" above ljust == box ("foo" above ljust)
 218*/
 219
 220%left LJUST RJUST ABOVE BELOW
 221
 222%left LEFT RIGHT
 223/* Give attributes that take an optional expression a higher
 224precedence than left and right, so that eg `line chop left'
 225parses properly. */
 226%left CHOP SOLID DASHED DOTTED UP DOWN FILL COLORED OUTLINED
 227%left LABEL
 228
 229%left VARIABLE NUMBER '(' SIN COS ATAN2 LOG EXP SQRT K_MAX K_MIN INT RAND SRAND LAST 
 230%left ORDINAL HERE '`'
 231
 232%left BOX CIRCLE ELLIPSE ARC LINE ARROW SPLINE '['
 233
 234/* these need to be lower than '-' */
 235%left HEIGHT RADIUS WIDTH DIAMETER FROM TO AT THICKNESS
 236
 237/* these must have higher precedence than CHOP so that `label %prec CHOP'
 238works */
 239%left DOT_N DOT_E DOT_W DOT_S DOT_NE DOT_SE DOT_NW DOT_SW DOT_C
 240%left DOT_START DOT_END TOP BOTTOM LEFT_CORNER RIGHT_CORNER
 241%left UPPER LOWER NORTH SOUTH EAST WEST CENTER START END
 242
 243%left ','
 244%left OROR
 245%left ANDAND
 246%left EQUALEQUAL NOTEQUAL
 247%left '<' '>' LESSEQUAL GREATEREQUAL
 248
 249%left BETWEEN OF
 250%left AND
 251
 252%left '+' '-'
 253%left '*' '/' '%'
 254%right '!'
 255%right '^'
 256
 257%type <x> expr any_expr text_expr
 258%type <by> optional_by
 259%type <pair> expr_pair position_not_place
 260%type <if_data> simple_if
 261%type <obj> nth_primitive
 262%type <crn> corner
 263%type <pth> path label_path relative_path
 264%type <pl> place label element element_list middle_element_list
 265%type <spec> object_spec
 266%type <pair> position
 267%type <obtype> object_type
 268%type <n> optional_ordinal_last ordinal
 269%type <str> macro_name until
 270%type <dv> sprintf_args
 271%type <lstr> text print_args print_arg
 272
 273%%
 274
 275top:
 276	optional_separator
 277	| element_list
 278		{
 279		  if (olist.head)
 280		    print_picture(olist.head);
 281		}
 282	;
 283
 284
 285element_list:
 286	optional_separator middle_element_list optional_separator
 287		{ $$ = $2; }
 288	;
 289
 290middle_element_list:
 291	element
 292		{ $$ = $1; }
 293	| middle_element_list separator element
 294		{ $$ = $1; }
 295	;
 296
 297optional_separator:
 298	/* empty */
 299	| separator
 300	;
 301
 302separator:
 303	';'
 304	| separator ';'
 305	;
 306
 307placeless_element:
 308	FIGNAME '=' macro_name
 309		{
 310		  a_delete graphname;
 311		  graphname = new char[strlen($3) + 1];
 312		  strcpy(graphname, $3);
 313		  a_delete $3;
 314		}
 315	|
 316	VARIABLE '=' any_expr
 317		{
 318		  define_variable($1, $3);
 319		  a_delete $1;
 320		}
 321	| VARIABLE ':' '=' any_expr
 322		{
 323		  place *p = lookup_label($1);
 324		  if (!p) {
 325		    lex_error("variable `%1' not defined", $1);
 326		    YYABORT;
 327		  }
 328		  p->obj = 0;
 329		  p->x = $4;
 330		  p->y = 0.0;
 331		  a_delete $1;
 332		}
 333	| UP
 334		{ current_direction = UP_DIRECTION; }
 335	| DOWN
 336		{ current_direction = DOWN_DIRECTION; }
 337	| LEFT
 338		{ current_direction = LEFT_DIRECTION; }
 339	| RIGHT
 340		{ current_direction = RIGHT_DIRECTION; }
 341	| COMMAND_LINE
 342		{
 343		  olist.append(make_command_object($1.str, $1.filename,
 344						   $1.lineno));
 345		}
 346	| COMMAND print_args
 347		{
 348		  olist.append(make_command_object($2.str, $2.filename,
 349						   $2.lineno));
 350		}
 351	| PRINT print_args
 352		{
 353		  fprintf(stderr, "%s\n", $2.str);
 354		  a_delete $2.str;
 355		  fflush(stderr);
 356		}
 357	| SH
 358		{ delim_flag = 1; }
 359	  DELIMITED
 360		{
 361		  delim_flag = 0;
 362		  if (safer_flag)
 363		    lex_error("unsafe to run command `%1'", $3);
 364		  else
 365		    system($3);
 366		  a_delete $3;
 367		}
 368	| COPY TEXT
 369		{
 370		  if (yychar < 0)
 371		    do_lookahead();
 372		  do_copy($2.str);
 373		  // do not delete the filename
 374		}
 375	| COPY TEXT THRU
 376		{ delim_flag = 2; }
 377	  DELIMITED 
 378		{ delim_flag = 0; }
 379	  until
 380		{
 381		  if (yychar < 0)
 382		    do_lookahead();
 383		  copy_file_thru($2.str, $5, $7);
 384		  // do not delete the filename
 385		  a_delete $5;
 386		  a_delete $7;
 387		}
 388	| COPY THRU
 389		{ delim_flag = 2; }
 390	  DELIMITED
 391		{ delim_flag = 0; }
 392	  until
 393		{
 394		  if (yychar < 0)
 395		    do_lookahead();
 396		  copy_rest_thru($4, $6);
 397		  a_delete $4;
 398		  a_delete $6;
 399		}
 400	| FOR VARIABLE '=' expr TO expr optional_by DO
 401	  	{ delim_flag = 1; }
 402	  DELIMITED
 403	  	{
 404		  delim_flag = 0;
 405		  if (yychar < 0)
 406		    do_lookahead();
 407		  do_for($2, $4, $6, $7.is_multiplicative, $7.val, $10); 
 408		}
 409	| simple_if
 410		{
 411		  if (yychar < 0)
 412		    do_lookahead();
 413		  if ($1.x != 0.0)
 414		    push_body($1.body);
 415		  a_delete $1.body;
 416		}
 417	| simple_if ELSE
 418		{ delim_flag = 1; }
 419	  DELIMITED
 420		{
 421		  delim_flag = 0;
 422		  if (yychar < 0)
 423		    do_lookahead();
 424		  if ($1.x != 0.0)
 425		    push_body($1.body);
 426		  else
 427		    push_body($4);
 428		  a_delete $1.body;
 429		  a_delete $4;
 430		}
 431	| reset_variables
 432	| RESET
 433		{ define_variable("scale", 1.0); }
 434	;
 435
 436macro_name:
 437	VARIABLE
 438	| LABEL
 439	;
 440
 441reset_variables:
 442	RESET VARIABLE
 443		{
 444		  reset($2);
 445		  a_delete $2;
 446		}
 447	| reset_variables VARIABLE
 448		{
 449		  reset($2);
 450		  a_delete $2;
 451		}
 452	| reset_variables ',' VARIABLE
 453		{
 454		  reset($3);
 455		  a_delete $3;
 456		}
 457	;
 458
 459print_args:
 460	print_arg
 461		{ $$ = $1; }
 462	| print_args print_arg
 463		{
 464		  $$.str = new char[strlen($1.str) + strlen($2.str) + 1];
 465		  strcpy($$.str, $1.str);
 466		  strcat($$.str, $2.str);
 467		  a_delete $1.str;
 468		  a_delete $2.str;
 469		  if ($1.filename) {
 470		    $$.filename = $1.filename;
 471		    $$.lineno = $1.lineno;
 472		  }
 473		  else if ($2.filename) {
 474		    $$.filename = $2.filename;
 475		    $$.lineno = $2.lineno;
 476		  }
 477		}
 478	;
 479
 480print_arg:
 481  	expr							%prec ','
 482		{
 483		  $$.str = new char[GDIGITS + 1];
 484		  sprintf($$.str, "%g", $1);
 485		  $$.filename = 0;
 486		  $$.lineno = 0;
 487		}
 488	| text
 489		{ $$ = $1; }
 490	| position						%prec ','
 491		{
 492		  $$.str = new char[GDIGITS + 2 + GDIGITS + 1];
 493		  sprintf($$.str, "%g, %g", $1.x, $1.y);
 494		  $$.filename = 0;
 495		  $$.lineno = 0;
 496		}
 497	;
 498
 499simple_if:
 500	IF any_expr THEN
 501		{ delim_flag = 1; }
 502	DELIMITED
 503		{
 504		  delim_flag = 0;
 505		  $$.x = $2;
 506		  $$.body = $5;
 507		}
 508	;
 509
 510until:
 511	/* empty */
 512		{ $$ = 0; }
 513	| UNTIL TEXT
 514		{ $$ = $2.str; }
 515	;
 516	
 517any_expr:
 518	expr
 519		{ $$ = $1; }
 520	| text_expr
 521		{ $$ = $1; }
 522	;
 523	
 524text_expr:
 525	text EQUALEQUAL text
 526		{
 527		  $$ = strcmp($1.str, $3.str) == 0;
 528		  a_delete $1.str;
 529		  a_delete $3.str;
 530		}
 531	| text NOTEQUAL text
 532		{
 533		  $$ = strcmp($1.str, $3.str) != 0;
 534		  a_delete $1.str;
 535		  a_delete $3.str;
 536		}
 537	| text_expr ANDAND text_expr
 538		{ $$ = ($1 != 0.0 && $3 != 0.0); }
 539	| text_expr ANDAND expr
 540		{ $$ = ($1 != 0.0 && $3 != 0.0); }
 541	| expr ANDAND text_expr
 542		{ $$ = ($1 != 0.0 && $3 != 0.0); }
 543	| text_expr OROR text_expr
 544		{ $$ = ($1 != 0.0 || $3 != 0.0); }
 545	| text_expr OROR expr
 546		{ $$ = ($1 != 0.0 || $3 != 0.0); }
 547	| expr OROR text_expr
 548		{ $$ = ($1 != 0.0 || $3 != 0.0); }
 549	| '!' text_expr
 550		{ $$ = ($2 == 0.0); }
 551	;
 552
 553
 554optional_by:
 555	/* empty */
 556		{
 557		  $$.val = 1.0;
 558		  $$.is_multiplicative = 0;
 559		}
 560	| BY expr
 561		{
 562		  $$.val = $2;
 563		  $$.is_multiplicative = 0;
 564		}
 565	| BY '*' expr
 566		{
 567		  $$.val = $3;
 568		  $$.is_multiplicative = 1;
 569		}
 570	;
 571
 572element:
 573	object_spec
 574		{
 575		  $$.obj = $1->make_object(&current_position,
 576					   &current_direction);
 577		  if ($$.obj == 0)
 578		    YYABORT;
 579		  delete $1;
 580		  if ($$.obj)
 581		    olist.append($$.obj);
 582		  else {
 583		    $$.x = current_position.x;
 584		    $$.y = current_position.y;
 585		  }
 586		}
 587	| LABEL ':' optional_separator element
 588		{
 589		  $$ = $4;
 590		  define_label($1, & $$);
 591		  a_delete $1;
 592		}
 593	| LABEL ':' optional_separator position_not_place
 594		{
 595		  $$.obj = 0;
 596		  $$.x = $4.x;
 597		  $$.y = $4.y;
 598		  define_label($1, & $$);
 599		  a_delete $1;
 600		}
 601	| LABEL ':' optional_separator place
 602		{
 603		  $$ = $4;
 604		  define_label($1, & $$);
 605		  a_delete $1;
 606		}
 607	| '{'
 608		{
 609		  $<state>$.x = current_position.x;
 610		  $<state>$.y = current_position.y;
 611		  $<state>$.dir = current_direction;
 612		}
 613	  element_list '}'
 614		{
 615		  current_position.x = $<state>2.x;
 616		  current_position.y = $<state>2.y;
 617		  current_direction = $<state>2.dir;
 618		}
 619	  optional_element
 620		{
 621		  $$ = $3;
 622		}
 623	| placeless_element
 624		{
 625		  $$.obj = 0;
 626		  $$.x = current_position.x;
 627		  $$.y = current_position.y;
 628		}
 629	;
 630
 631optional_element:
 632	/* empty */
 633		{}
 634	| element
 635		{}
 636	;
 637
 638object_spec:
 639	BOX
 640		{ $$ = new object_spec(BOX_OBJECT); }
 641	| CIRCLE
 642		{ $$ = new object_spec(CIRCLE_OBJECT); }
 643	| ELLIPSE
 644		{ $$ = new object_spec(ELLIPSE_OBJECT); }
 645	| ARC
 646		{
 647		  $$ = new object_spec(ARC_OBJECT);
 648		  $$->dir = current_direction;
 649		}
 650	| LINE
 651		{
 652		  $$ = new object_spec(LINE_OBJECT);
 653		  lookup_variable("lineht", & $$->segment_height);
 654		  lookup_variable("linewid", & $$->segment_width);
 655		  $$->dir = current_direction;
 656		}
 657	| ARROW
 658		{
 659		  $$ = new object_spec(ARROW_OBJECT);
 660		  lookup_variable("lineht", & $$->segment_height);
 661		  lookup_variable("linewid", & $$->segment_width);
 662		  $$->dir = current_direction;
 663		}
 664	| MOVE
 665		{
 666		  $$ = new object_spec(MOVE_OBJECT);
 667		  lookup_variable("moveht", & $$->segment_height);
 668		  lookup_variable("movewid", & $$->segment_width);
 669		  $$->dir = current_direction;
 670		}
 671	| SPLINE
 672		{
 673		  $$ = new object_spec(SPLINE_OBJECT);
 674		  lookup_variable("lineht", & $$->segment_height);
 675		  lookup_variable("linewid", & $$->segment_width);
 676		  $$->dir = current_direction;
 677		}
 678	| text							%prec TEXT
 679		{
 680		  $$ = new object_spec(TEXT_OBJECT);
 681		  $$->text = new text_item($1.str, $1.filename, $1.lineno);
 682		}
 683	| PLOT expr
 684		{
 685		  $$ = new object_spec(TEXT_OBJECT);
 686		  $$->text = new text_item(format_number(0, $2), 0, -1);
 687		}
 688	| PLOT expr text
 689		{
 690		  $$ = new object_spec(TEXT_OBJECT);
 691		  $$->text = new text_item(format_number($3.str, $2),
 692					   $3.filename, $3.lineno);
 693		  a_delete $3.str;
 694		}
 695	| '[' 
 696		{
 697		  saved_state *p = new saved_state;
 698		  $<pstate>$ = p;
 699		  p->x = current_position.x;
 700		  p->y = current_position.y;
 701		  p->dir = current_direction;
 702		  p->tbl = current_table;
 703		  p->prev = current_saved_state;
 704		  current_position.x = 0.0;
 705		  current_position.y = 0.0;
 706		  current_table = new PTABLE(place);
 707		  current_saved_state = p;
 708		  olist.append(make_mark_object());
 709		}
 710	  element_list ']'
 711		{
 712		  current_position.x = $<pstate>2->x;
 713		  current_position.y = $<pstate>2->y;
 714		  current_direction = $<pstate>2->dir;
 715		  $$ = new object_spec(BLOCK_OBJECT);
 716		  olist.wrap_up_block(& $$->oblist);
 717		  $$->tbl = current_table;
 718		  current_table = $<pstate>2->tbl;
 719		  current_saved_state = $<pstate>2->prev;
 720		  delete $<pstate>2;
 721		}
 722	| object_spec HEIGHT expr
 723		{
 724		  $$ = $1;
 725		  $$->height = $3;
 726		  $$->flags |= HAS_HEIGHT;
 727		}
 728	| object_spec RADIUS expr
 729		{
 730		  $$ = $1;
 731		  $$->radius = $3;
 732		  $$->flags |= HAS_RADIUS;
 733		}
 734	| object_spec WIDTH expr
 735		{
 736		  $$ = $1;
 737		  $$->width = $3;
 738		  $$->flags |= HAS_WIDTH;
 739		}
 740	| object_spec DIAMETER expr
 741		{
 742		  $$ = $1;
 743		  $$->radius = $3/2.0;
 744		  $$->flags |= HAS_RADIUS;
 745		}
 746	| object_spec expr					%prec HEIGHT
 747		{
 748		  $$ = $1;
 749		  $$->flags |= HAS_SEGMENT;
 750		  switch ($$->dir) {
 751		  case UP_DIRECTION:
 752		    $$->segment_pos.y += $2;
 753		    break;
 754		  case DOWN_DIRECTION:
 755		    $$->segment_pos.y -= $2;
 756		    break;
 757		  case RIGHT_DIRECTION:
 758		    $$->segment_pos.x += $2;
 759		    break;
 760		  case LEFT_DIRECTION:
 761		    $$->segment_pos.x -= $2;
 762		    break;
 763		  }
 764		}
 765	| object_spec UP
 766		{
 767		  $$ = $1;
 768		  $$->dir = UP_DIRECTION;
 769		  $$->flags |= HAS_SEGMENT;
 770		  $$->segment_pos.y += $$->segment_height;
 771		}
 772	| object_spec UP expr
 773		{
 774		  $$ = $1;
 775		  $$->dir = UP_DIRECTION;
 776		  $$->flags |= HAS_SEGMENT;
 777		  $$->segment_pos.y += $3;
 778		}
 779	| object_spec DOWN
 780		{
 781		  $$ = $1;
 782		  $$->dir = DOWN_DIRECTION;
 783		  $$->flags |= HAS_SEGMENT;
 784		  $$->segment_pos.y -= $$->segment_height;
 785		}
 786	| object_spec DOWN expr
 787		{
 788		  $$ = $1;
 789		  $$->dir = DOWN_DIRECTION;
 790		  $$->flags |= HAS_SEGMENT;
 791		  $$->segment_pos.y -= $3;
 792		}
 793	| object_spec RIGHT
 794		{
 795		  $$ = $1;
 796		  $$->dir = RIGHT_DIRECTION;
 797		  $$->flags |= HAS_SEGMENT;
 798		  $$->segment_pos.x += $$->segment_width;
 799		}
 800	| object_spec RIGHT expr
 801		{
 802		  $$ = $1;
 803		  $$->dir = RIGHT_DIRECTION;
 804		  $$->flags |= HAS_SEGMENT;
 805		  $$->segment_pos.x += $3;
 806		}
 807	| object_spec LEFT
 808		{
 809		  $$ = $1;
 810		  $$->dir = LEFT_DIRECTION;
 811		  $$->flags |= HAS_SEGMENT;
 812		  $$->segment_pos.x -= $$->segment_width;
 813		}
 814	| object_spec LEFT expr
 815		{
 816		  $$ = $1;
 817		  $$->dir = LEFT_DIRECTION;
 818		  $$->flags |= HAS_SEGMENT;
 819		  $$->segment_pos.x -= $3;
 820		}
 821	| object_spec FROM position
 822		{
 823		  $$ = $1;
 824		  $$->flags |= HAS_FROM;
 825		  $$->from.x = $3.x;
 826		  $$->from.y = $3.y;
 827		}
 828	| object_spec TO position
 829		{
 830		  $$ = $1;
 831		  if ($$->flags & HAS_SEGMENT)
 832		    $$->segment_list = new segment($$->segment_pos,
 833						   $$->segment_is_absolute,
 834						   $$->segment_list);
 835		  $$->flags |= HAS_SEGMENT;
 836		  $$->segment_pos.x = $3.x;
 837		  $$->segment_pos.y = $3.y;
 838		  $$->segment_is_absolute = 1;
 839		  $$->flags |= HAS_TO;
 840		  $$->to.x = $3.x;
 841		  $$->to.y = $3.y;
 842		}
 843	| object_spec AT position
 844		{
 845		  $$ = $1;
 846		  $$->flags |= HAS_AT;
 847		  $$->at.x = $3.x;
 848		  $$->at.y = $3.y;
 849		  if ($$->type != ARC_OBJECT) {
 850		    $$->flags |= HAS_FROM;
 851		    $$->from.x = $3.x;
 852		    $$->from.y = $3.y;
 853		  }
 854		}
 855	| object_spec WITH path
 856		{
 857		  $$ = $1;
 858		  $$->flags |= HAS_WITH;
 859		  $$->with = $3;
 860		}
 861	| object_spec WITH position				%prec ','
 862		{
 863		  $$ = $1;
 864		  $$->flags |= HAS_WITH;
 865		  position pos;
 866		  pos.x = $3.x;
 867		  pos.y = $3.y;
 868		  $$->with = new path(pos);
 869		}
 870	| object_spec BY expr_pair
 871		{
 872		  $$ = $1;
 873		  $$->flags |= HAS_SEGMENT;
 874		  $$->segment_pos.x += $3.x;
 875		  $$->segment_pos.y += $3.y;
 876		}
 877	| object_spec THEN
 878  		{
 879		  $$ = $1;
 880		  if ($$->flags & HAS_SEGMENT) {
 881		    $$->segment_list = new segment($$->segment_pos,
 882						   $$->segment_is_absolute,
 883						   $$->segment_list);
 884		    $$->flags &= ~HAS_SEGMENT;
 885		    $$->segment_pos.x = $$->segment_pos.y = 0.0;
 886		    $$->segment_is_absolute = 0;
 887		  }
 888		}
 889	| object_spec SOLID
 890		{
 891		  $$ = $1;	// nothing
 892		}
 893	| object_spec DOTTED
 894		{
 895		  $$ = $1;
 896		  $$->flags |= IS_DOTTED;
 897		  lookup_variable("dashwid", & $$->dash_width);
 898		}
 899	| object_spec DOTTED expr
 900		{
 901		  $$ = $1;
 902		  $$->flags |= IS_DOTTED;
 903		  $$->dash_width = $3;
 904		}
 905	| object_spec DASHED
 906		{
 907		  $$ = $1;
 908		  $$->flags |= IS_DASHED;
 909		  lookup_variable("dashwid", & $$->dash_width);
 910		}
 911	| object_spec DASHED expr
 912		{
 913		  $$ = $1;
 914		  $$->flags |= IS_DASHED;
 915		  $$->dash_width = $3;
 916		}
 917	| object_spec FILL
 918		{
 919		  $$ = $1;
 920		  $$->flags |= IS_DEFAULT_FILLED;
 921		}
 922	| object_spec FILL expr
 923		{
 924		  $$ = $1;
 925		  $$->flags |= IS_FILLED;
 926		  $$->fill = $3;
 927		}
 928	| object_spec SHADED text
 929		{
 930		  $$ = $1;
 931		  $$->flags |= (IS_SHADED | IS_FILLED);
 932		  $$->shaded = new char[strlen($3.str)+1];
 933		  strcpy($$->shaded, $3.str);
 934		}
 935	| object_spec COLORED text
 936		{
 937		  $$ = $1;
 938		  $$->flags |= (IS_SHADED | IS_OUTLINED | IS_FILLED);
 939		  $$->shaded = new char[strlen($3.str)+1];
 940		  strcpy($$->shaded, $3.str);
 941		  $$->outlined = new char[strlen($3.str)+1];
 942		  strcpy($$->outlined, $3.str);
 943		}
 944	| object_spec OUTLINED text
 945		{
 946		  $$ = $1;
 947		  $$->flags |= IS_OUTLINED;
 948		  $$->outlined = new char[strlen($3.str)+1];
 949		  strcpy($$->outlined, $3.str);
 950		}
 951	| object_spec CHOP
 952  		{
 953		  $$ = $1;
 954		  // line chop chop means line chop 0 chop 0
 955		  if ($$->flags & IS_DEFAULT_CHOPPED) {
 956		    $$->flags |= IS_CHOPPED;
 957		    $$->flags &= ~IS_DEFAULT_CHOPPED;
 958		    $$->start_chop = $$->end_chop = 0.0;
 959		  }
 960		  else if ($$->flags & IS_CHOPPED) {
 961		    $$->end_chop = 0.0;
 962		  }
 963		  else {
 964		    $$->flags |= IS_DEFAULT_CHOPPED;
 965		  }
 966		}
 967	| object_spec CHOP expr
 968		{
 969		  $$ = $1;
 970		  if ($$->flags & IS_DEFAULT_CHOPPED) {
 971		    $$->flags |= IS_CHOPPED;
 972		    $$->flags &= ~IS_DEFAULT_CHOPPED;
 973		    $$->start_chop = 0.0;
 974		    $$->end_chop = $3;
 975		  }
 976		  else if ($$->flags & IS_CHOPPED) {
 977		    $$->end_chop = $3;
 978		  }
 979		  else {
 980		    $$->start_chop = $$->end_chop = $3;
 981		    $$->flags |= IS_CHOPPED;
 982		  }
 983		}
 984	| object_spec SAME
 985		{
 986		  $$ = $1;
 987		  $$->flags |= IS_SAME;
 988		}
 989	| object_spec INVISIBLE
 990		{
 991		  $$ = $1;
 992		  $$->flags |= IS_INVISIBLE;
 993		}
 994	| object_spec LEFT_ARROW_HEAD
 995		{
 996		  $$ = $1;
 997		  $$->flags |= HAS_LEFT_ARROW_HEAD;
 998		}
 999	| object_spec RIGHT_ARROW_HEAD
1000		{
1001		  $$ = $1;
1002		  $$->flags |= HAS_RIGHT_ARROW_HEAD;
1003		}
1004	| object_spec DOUBLE_ARROW_HEAD
1005		{
1006		  $$ = $1;
1007		  $$->flags |= (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD);
1008		}
1009	| object_spec CW
1010		{
1011		  $$ = $1;
1012		  $$->flags |= IS_CLOCKWISE;
1013		}
1014	| object_spec CCW
1015		{
1016		  $$ = $1;
1017		  $$->flags &= ~IS_CLOCKWISE;
1018		}
1019	| object_spec text					%prec TEXT
1020		{
1021		  $$ = $1;
1022		  text_item **p;
1023		  for (p = & $$->text; *p; p = &(*p)->next)
1024		    ;
1025		  *p = new text_item($2.str, $2.filename, $2.lineno);
1026		}
1027	| object_spec LJUST
1028		{
1029		  $$ = $1;
1030		  if ($$->text) {
1031		    text_item *p;
1032		    for (p = $$->text; p->next; p = p->next)
1033		      ;
1034		    p->adj.h = LEFT_ADJUST;
1035		  }
1036		}
1037	| object_spec RJUST
1038		{
1039		  $$ = $1;
1040		  if ($$->text) {
1041		    text_item *p;
1042		    for (p = $$->text; p->next; p = p->next)
1043		      ;
1044		    p->adj.h = RIGHT_ADJUST;
1045		  }
1046		}
1047	| object_spec ABOVE
1048		{
1049		  $$ = $1;
1050		  if ($$->text) {
1051		    text_item *p;
1052		    for (p = $$->text; p->next; p = p->next)
1053		      ;
1054		    p->adj.v = ABOVE_ADJUST;
1055		  }
1056		}
1057	| object_spec BELOW
1058		{
1059		  $$ = $1;
1060		  if ($$->text) {
1061		    text_item *p;
1062		    for (p = $$->text; p->next; p = p->next)
1063		      ;
1064		    p->adj.v = BELOW_ADJUST;
1065		  }
1066		}
1067	| object_spec THICKNESS expr
1068		{
1069		  $$ = $1;
1070		  $$->flags |= HAS_THICKNESS;
1071		  $$->thickness = $3;
1072		}
1073	| object_spec ALIGNED
1074		{
1075		  $$ = $1;
1076		  $$->flags |= IS_ALIGNED;
1077		}
1078	;
1079
1080text:
1081	TEXT
1082		{ $$ = $1; }
1083	| SPRINTF '(' TEXT sprintf_args ')'
1084		{
1085		  $$.filename = $3.filename;
1086		  $$.lineno = $3.lineno;
1087		  $$.str = do_sprintf($3.str, $4.v, $4.nv);
1088		  a_delete $4.v;
1089		  a_delete $3.str;
1090		}
1091	;
1092
1093sprintf_args:
1094	/* empty */
1095		{
1096		  $$.v = 0;
1097		  $$.nv = 0;
1098		  $$.maxv = 0;
1099		}
1100	| sprintf_args ',' expr
1101		{
1102		  $$ = $1;
1103		  if ($$.nv >= $$.maxv) {
1104		    if ($$.nv == 0) {
1105		      $$.v = new double[4];
1106		      $$.maxv = 4;
1107		    }
1108		    else {
1109		      double *oldv = $$.v;
1110		      $$.maxv *= 2;
1111#if 0
1112		      $$.v = new double[$$.maxv];
1113		      memcpy($$.v, oldv, $$.nv*sizeof(double));
1114#else
1115		      // workaround for bug in Compaq C++ V6.5-033
1116		      // for Compaq Tru64 UNIX V5.1A (Rev. 1885)
1117		      double *foo = new double[$$.maxv];
1118		      memcpy(foo, oldv, $$.nv*sizeof(double));
1119		      $$.v = foo;
1120#endif
1121		      a_delete oldv;
1122		    }
1123		  }
1124		  $$.v[$$.nv] = $3;
1125		  $$.nv += 1;
1126		}
1127	;
1128
1129position:
1130  	position_not_place
1131		{ $$ = $1; }
1132	| place
1133  		{
1134		  position pos = $1;
1135		  $$.x = pos.x;
1136		  $$.y = pos.y;
1137		}
1138	| '(' place ')'
1139		{
1140		  position pos = $2;
1141		  $$.x = pos.x;
1142		  $$.y = pos.y;
1143		}
1144	;
1145
1146position_not_place:
1147	expr_pair
1148		{ $$ = $1; }
1149	| position '+' expr_pair
1150		{
1151		  $$.x = $1.x + $3.x;
1152		  $$.y = $1.y + $3.y;
1153		}
1154	| '(' position '+' expr_pair ')'
1155		{
1156		  $$.x = $2.x + $4.x;
1157		  $$.y = $2.y + $4.y;
1158		}
1159	| position '-' expr_pair
1160		{
1161		  $$.x = $1.x - $3.x;
1162		  $$.y = $1.y - $3.y;
1163		}
1164	| '(' position '-' expr_pair ')'
1165		{
1166		  $$.x = $2.x - $4.x;
1167		  $$.y = $2.y - $4.y;
1168		}
1169	| '(' position ',' position ')'
1170		{
1171		  $$.x = $2.x;
1172		  $$.y = $4.y;
1173		}
1174	| expr between position AND position
1175		{
1176		  $$.x = (1.0 - $1)*$3.x + $1*$5.x;
1177		  $$.y = (1.0 - $1)*$3.y + $1*$5.y;
1178		}
1179	| '(' expr between position AND position ')'
1180		{
1181		  $$.x = (1.0 - $2)*$4.x + $2*$6.x;
1182		  $$.y = (1.0 - $2)*$4.y + $2*$6.y;
1183		}
1184	| expr '<' position ',' position '>'
1185		{
1186		  $$.x = (1.0 - $1)*$3.x + $1*$5.x;
1187		  $$.y = (1.0 - $1)*$3.y + $1*$5.y;
1188		}
1189	| '(' expr '<' position ',' position '>' ')'
1190		{
1191		  $$.x = (1.0 - $2)*$4.x + $2*$6.x;
1192		  $$.y = (1.0 - $2)*$4.y + $2*$6.y;
1193		}
1194	;
1195
1196between:
1197	BETWEEN
1198	| OF THE WAY BETWEEN
1199	;
1200
1201expr_pair:
1202	expr ',' expr
1203		{
1204		  $$.x = $1;
1205		  $$.y = $3;
1206		}
1207	| '(' expr_pair ')'
1208		{ $$ = $2; }
1209	;
1210
1211place:
1212	/* line at A left == line (at A) left */
1213	label							%prec CHOP
1214		{ $$ = $1; }
1215	| label corner
1216		{
1217		  path pth($2);
1218		  if (!pth.follow($1, & $$))
1219		    YYABORT;
1220		}
1221	| corner label
1222		{
1223		  path pth($1);
1224		  if (!pth.follow($2, & $$))
1225		    YYABORT;
1226		}
1227	| corner OF label
1228		{
1229		  path pth($1);
1230		  if (!pth.follow($3, & $$))
1231		    YYABORT;
1232		}
1233	| HERE
1234		{
1235		  $$.x = current_position.x;
1236		  $$.y = current_position.y;
1237		  $$.obj = 0;
1238		}
1239	;
1240
1241label:
1242	LABEL
1243		{
1244		  place *p = lookup_label($1);
1245		  if (!p) {
1246		    lex_error("there is no place `%1'", $1);
1247		    YYABORT;
1248		  }
1249		  $$ = *p;
1250		  a_delete $1;
1251		}
1252	| nth_primitive
1253		{ $$.obj = $1; }
1254	| label '.' LABEL
1255		{
1256		  path pth($3);
1257		  if (!pth.follow($1, & $$))
1258		    YYABORT;
1259		}
1260	;
1261
1262ordinal:
1263	ORDINAL
1264		{ $$ = $1; }
1265	| '`' any_expr TH
1266		{
1267		  // XXX Check for overflow (and non-integers?).
1268		  $$ = (int)$2;
1269		}
1270	;
1271
1272optional_ordinal_last:
1273	LAST
1274		{ $$ = 1; }
1275  	| ordinal LAST
1276		{ $$ = $1; }
1277	;
1278
1279nth_primitive:
1280	ordinal object_type
1281		{
1282		  int count = 0;
1283		  object *p;
1284		  for (p = olist.head; p != 0; p = p->next)
1285		    if (p->type() == $2 && ++count == $1) {
1286		      $$ = p;
1287		      break;
1288		    }
1289		  if (p == 0) {
1290		    lex_error("there is no %1%2 %3", $1, ordinal_postfix($1),
1291			      object_type_name($2));
1292		    YYABORT;
1293		  }
1294		}
1295	| optional_ordinal_last object_type
1296		{
1297		  int count = 0;
1298		  object *p;
1299		  for (p = olist.tail; p != 0; p = p->prev)
1300		    if (p->type() == $2 && ++count == $1) {
1301		      $$ = p;
1302		      break;
1303		    }
1304		  if (p == 0) {
1305		    lex_error("there is no %1%2 last %3", $1,
1306			      ordinal_postfix($1), object_type_name($2));
1307		    YYABORT;
1308		  }
1309		}
1310	;
1311
1312object_type:
1313	BOX
1314  		{ $$ = BOX_OBJECT; }
1315	| CIRCLE
1316		{ $$ = CIRCLE_OBJECT; }
1317	| ELLIPSE
1318		{ $$ = ELLIPSE_OBJECT; }
1319	| ARC
1320		{ $$ = ARC_OBJECT; }
1321	| LINE
1322		{ $$ = LINE_OBJECT; }
1323	| ARROW
1324		{ $$ = ARROW_OBJECT; }
1325	| SPLINE
1326		{ $$ = SPLINE_OBJECT; }
1327	| '[' ']'
1328		{ $$ = BLOCK_OBJECT; }
1329	| TEXT
1330		{ $$ = TEXT_OBJECT; }
1331	;
1332
1333label_path:
1334 	'.' LABEL
1335		{ $$ = new path($2); }
1336	| label_path '.' LABEL
1337		{
1338		  $$ = $1;
1339		  $$->append($3);
1340		}
1341	;
1342
1343relative_path:
1344	corner							%prec CHOP
1345		{ $$ = new path($1); }
1346	/* give this a lower precedence than LEFT and RIGHT so that
1347	   [A: box] with .A left == [A: box] with (.A left) */
1348  	| label_path						%prec TEXT
1349		{ $$ = $1; }
1350	| label_path corner
1351		{
1352		  $$ = $1;
1353		  $$->append($2);
1354		}
1355	;
1356
1357path:
1358	relative_path
1359		{ $$ = $1; }
1360	| '(' relative_path ',' relative_path ')'
1361		{
1362		  $$ = $2;
1363		  $$->set_ypath($4);
1364		}
1365	/* The rest of these rules are a compatibility sop. */
1366	| ORDINAL LAST object_type relative_path
1367		{
1368		  lex_warning("`%1%2 last %3' in `with' argument ignored",
1369			      $1, ordinal_postfix($1), object_type_name($3));
1370		  $$ = $4;
1371		}
1372	| LAST object_type relative_path
1373		{
1374		  lex_warning("`last %1' in `with' argument ignored",
1375			      object_type_name($2));
1376		  $$ = $3;
1377		}
1378	| ORDINAL object_type relative_path
1379		{
1380		  lex_warning("`%1%2 %3' in `with' argument ignored",
1381			      $1, ordinal_postfix($1), object_type_name($2));
1382		  $$ = $3;
1383		}
1384	| LABEL relative_path
1385		{
1386		  lex_warning("initial `%1' in `with' argument ignored", $1);
1387		  a_delete $1;
1388		  $$ = $2;
1389		}
1390	;
1391
1392corner:
1393	DOT_N
1394		{ $$ = &object::north; }
1395	| DOT_E	
1396		{ $$ = &object::east; }
1397	| DOT_W
1398		{ $$ = &object::west; }
1399	| DOT_S
1400		{ $$ = &object::south; }
1401	| DOT_NE
1402		{ $$ = &object::north_east; }
1403	| DOT_SE
1404		{ $$ = &object:: south_east; }
1405	| DOT_NW
1406		{ $$ = &object::north_west; }
1407	| DOT_SW
1408		{ $$ = &object::south_west; }
1409	| DOT_C
1410		{ $$ = &object::center; }
1411	| DOT_START
1412		{ $$ = &object::start; }
1413	| DOT_END
1414		{ $$ = &object::end; }
1415  	| TOP
1416		{ $$ = &object::north; }
1417	| BOTTOM
1418		{ $$ = &object::south; }
1419	| LEFT
1420		{ $$ = &object::west; }
1421	| RIGHT
1422		{ $$ = &object::east; }
1423	| UPPER LEFT
1424		{ $$ = &object::north_west; }
1425	| LOWER LEFT
1426		{ $$ = &object::south_west; }
1427	| UPPER RIGHT
1428		{ $$ = &object::north_east; }
1429	| LOWER RIGHT
1430		{ $$ = &object::south_east; }
1431	| LEFT_CORNER
1432		{ $$ = &object::west; }
1433	| RIGHT_CORNER
1434		{ $$ = &object::east; }
1435	| UPPER LEFT_CORNER
1436		{ $$ = &object::north_west; }
1437	| LOWER LEFT_CORNER
1438		{ $$ = &object::south_west; }
1439	| UPPER RIGHT_CORNER
1440		{ $$ = &object::north_east; }
1441	| LOWER RIGHT_CORNER
1442		{ $$ = &object::south_east; }
1443	| NORTH
1444		{ $$ = &object::north; }
1445	| SOUTH
1446		{ $$ = &object::south; }
1447	| EAST
1448		{ $$ = &object::east; }
1449	| WEST
1450		{ $$ = &object::west; }
1451	| CENTER
1452		{ $$ = &object::center; }
1453	| START
1454		{ $$ = &object::start; }
1455	| END
1456		{ $$ = &object::end; }
1457	;
1458
1459expr:
1460	VARIABLE
1461		{
1462		  if (!lookup_variable($1, & $$)) {
1463		    lex_error("there is no variable `%1'", $1);
1464		    YYABORT;
1465		  }
1466		  a_delete $1;
1467		}
1468	| NUMBER
1469		{ $$ = $1; }
1470	| place DOT_X
1471  		{
1472		  if ($1.obj != 0)
1473		    $$ = $1.obj->origin().x;
1474		  else
1475		    $$ = $1.x;
1476		}			
1477	| place DOT_Y
1478		{
1479		  if ($1.obj != 0)
1480		    $$ = $1.obj->origin().y;
1481		  else
1482		    $$ = $1.y;
1483		}
1484	| place DOT_HT
1485		{
1486		  if ($1.obj != 0)
1487		    $$ = $1.obj->height();
1488		  else
1489		    $$ = 0.0;
1490		}
1491	| place DOT_WID
1492		{
1493		  if ($1.obj != 0)
1494		    $$ = $1.obj->width();
1495		  else
1496		    $$ = 0.0;
1497		}
1498	| place DOT_RAD
1499		{
1500		  if ($1.obj != 0)
1501		    $$ = $1.obj->radius();
1502		  else
1503		    $$ = 0.0;
1504		}
1505	| expr '+' expr
1506		{ $$ = $1 + $3; }
1507	| expr '-' expr
1508		{ $$ = $1 - $3; }
1509	| expr '*' expr
1510		{ $$ = $1 * $3; }
1511	| expr '/' expr
1512		{
1513		  if ($3 == 0.0) {
1514		    lex_error("division by zero");
1515		    YYABORT;
1516		  }
1517		  $$ = $1/$3;
1518		}
1519	| expr '%' expr
1520		{
1521		  if ($3 == 0.0) {
1522		    lex_error("modulus by zero");
1523		    YYABORT;
1524		  }
1525		  $$ = fmod($1, $3);
1526		}
1527	| expr '^' expr
1528		{
1529		  errno = 0;
1530		  $$ = pow($1, $3);
1531		  if (errno == EDOM) {
1532		    lex_error("arguments to `^' operator out of domain");
1533		    YYABORT;
1534		  }
1535		  if (errno == ERANGE) {
1536		    lex_error("result of `^' operator out of range");
1537		    YYABORT;
1538		  }
1539		}
1540	| '-' expr						%prec '!'
1541		{ $$ = -$2; }
1542	| '(' any_expr ')'
1543		{ $$ = $2; }
1544	| SIN '(' any_expr ')'
1545		{
1546		  errno = 0;
1547		  $$ = sin($3);
1548		  if (errno == ERANGE) {
1549		    lex_error("sin result out of range");
1550		    YYABORT;
1551		  }
1552		}
1553	| COS '(' any_expr ')'
1554		{
1555		  errno = 0;
1556		  $$ = cos($3);
1557		  if (errno == ERANGE) {
1558		    lex_error("cos result out of range");
1559		    YYABORT;
1560		  }
1561		}
1562	| ATAN2 '(' any_expr ',' any_expr ')'
1563		{
1564		  errno = 0;
1565		  $$ = atan2($3, $5);
1566		  if (errno == EDOM) {
1567		    lex_error("atan2 argument out of domain");
1568		    YYABORT;
1569		  }
1570		  if (errno == ERANGE) {
1571		    lex_error("atan2 result out of range");
1572		    YYABORT;
1573		  }
1574		}
1575	| LOG '(' any_expr ')'
1576		{
1577		  errno = 0;
1578		  $$ = log10($3);
1579		  if (errno == ERANGE) {
1580		    lex_error("log result out of range");
1581		    YYABORT;
1582		  }
1583		}
1584	| EXP '(' any_expr ')'
1585		{
1586		  errno = 0;
1587		  $$ = pow(10.0, $3);
1588		  if (errno == ERANGE) {
1589		    lex_error("exp result out of range");
1590		    YYABORT;
1591		  }
1592		}
1593	| SQRT '(' any_expr ')'
1594		{
1595		  errno = 0;
1596		  $$ = sqrt($3);
1597		  if (errno == EDOM) {
1598		    lex_error("sqrt argument out of domain");
1599		    YYABORT;
1600		  }
1601		}
1602	| K_MAX '(' any_expr ',' any_expr ')'
1603		{ $$ = $3 > $5 ? $3 : $5; }
1604	| K_MIN '(' any_expr ',' any_expr ')'
1605		{ $$ = $3 < $5 ? $3 : $5; }
1606	| INT '(' any_expr ')'
1607		{ $$ = floor($3); }
1608	| RAND '(' any_expr ')'
1609		{ $$ = 1.0 + floor(((rand()&0x7fff)/double(0x7fff))*$3); }
1610	| RAND '(' ')'
1611		{
1612		  /* return a random number in the range [0,1) */
1613		  /* portable, but not very random */
1614		  $$ = (rand() & 0x7fff) / double(0x8000);
1615		}
1616	| SRAND '(' any_expr ')'
1617		{
1618		  $$ = 0;
1619		  srand((unsigned int)$3);
1620		}
1621	| expr '<' expr
1622		{ $$ = ($1 < $3); }
1623	| expr LESSEQUAL expr
1624		{ $$ = ($1 <= $3); }
1625	| expr '>' expr
1626		{ $$ = ($1 > $3); }
1627	| expr GREATEREQUAL expr
1628		{ $$ = ($1 >= $3); }
1629	| expr EQUALEQUAL expr
1630		{ $$ = ($1 == $3); }
1631	| expr NOTEQUAL expr
1632		{ $$ = ($1 != $3); }
1633	| expr ANDAND expr
1634		{ $$ = ($1 != 0.0 && $3 != 0.0); }
1635	| expr OROR expr
1636		{ $$ = ($1 != 0.0 || $3 != 0.0); }
1637	| '!' expr
1638		{ $$ = ($2 == 0.0); }
1639
1640	;
1641
1642%%
1643
1644/* bison defines const to be empty unless __STDC__ is defined, which it
1645isn't under cfront */
1646
1647#ifdef const
1648#undef const
1649#endif
1650
1651static struct {
1652  const char *name;
1653  double val;
1654  int scaled;		     // non-zero if val should be multiplied by scale
1655} defaults_table[] = {
1656  { "arcrad", .25, 1 },
1657  { "arrowht", .1, 1 },
1658  { "arrowwid", .05, 1 },
1659  { "circlerad", .25, 1 },
1660  { "boxht", .5, 1 },
1661  { "boxwid", .75, 1 },
1662  { "boxrad", 0.0, 1 },
1663  { "dashwid", .05, 1 },
1664  { "ellipseht", .5, 1 },
1665  { "ellipsewid", .75, 1 },
1666  { "moveht", .5, 1 },
1667  { "movewid", .5, 1 },
1668  { "lineht", .5, 1 },
1669  { "linewid", .5, 1 },
1670  { "textht", 0.0, 1 },
1671  { "textwid", 0.0, 1 },
1672  { "scale", 1.0, 0 },
1673  { "linethick", -1.0, 0 },		// in points
1674  { "fillval", .5, 0 },
1675  { "arrowhead", 1.0, 0 },
1676  { "maxpswid", 8.5, 0 },
1677  { "maxpsht", 11.0, 0 },
1678};
1679
1680place *lookup_label(const char *label)
1681{
1682  saved_state *state = current_saved_state;
1683  PTABLE(place) *tbl = current_table;
1684  for (;;) {
1685    place *pl = tbl->lookup(label);
1686    if (pl)
1687      return pl;
1688    if (!state)
1689      return 0;
1690    tbl = state->tbl;
1691    state = state->prev;
1692  }
1693}
1694
1695void define_label(const char *label, const place *pl)
1696{
1697  place *p = new place[1];
1698  *p = *pl;
1699  current_table->define(label, p);
1700}
1701
1702int lookup_variable(const char *name, double *val)
1703{
1704  place *pl = lookup_label(name);
1705  if (pl) {
1706    *val = pl->x;
1707    return 1;
1708  }
1709  return 0;
1710}
1711
1712void define_variable(const char *name, double val)
1713{
1714  place *p = new place[1];
1715  p->obj = 0;
1716  p->x = val;
1717  p->y = 0.0;
1718  current_table->define(name, p);
1719  if (strcmp(name, "scale") == 0) {
1720    // When the scale changes, reset all scaled pre-defined variables to
1721    // their default values.
1722    for (unsigned int i = 0;
1723	 i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++) 
1724      if (defaults_table[i].scaled)
1725	define_variable(defaults_table[i].name, val*defaults_table[i].val);
1726  }
1727}
1728
1729// called once only (not once per parse)
1730
1731void parse_init()
1732{
1733  current_direction = RIGHT_DIRECTION;
1734  current_position.x = 0.0;
1735  current_position.y = 0.0;
1736  // This resets everything to its default value.
1737  reset_all();
1738}
1739
1740void reset(const char *nm)
1741{
1742  for (unsigned int i = 0;
1743       i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
1744    if (strcmp(nm, defaults_table[i].name) == 0) {
1745      double val = defaults_table[i].val;
1746      if (defaults_table[i].scaled) {
1747	double scale;
1748	lookup_variable("scale", &scale);
1749	val *= scale;
1750      }
1751      define_variable(defaults_table[i].name, val);
1752      return;
1753    }
1754  lex_error("`%1' is not a predefined variable", nm);
1755}
1756
1757void reset_all()
1758{
1759  // We only have to explicitly reset the pre-defined variables that
1760  // aren't scaled because `scale' is not scaled, and changing the
1761  // value of `scale' will reset all the pre-defined variables that
1762  // are scaled.
1763  for (unsigned int i = 0;
1764       i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
1765    if (!defaults_table[i].scaled)
1766      define_variable(defaults_table[i].name, defaults_table[i].val);
1767}
1768
1769// called after each parse
1770
1771void parse_cleanup()
1772{
1773  while (current_saved_state != 0) {
1774    delete current_table;
1775    current_table = current_saved_state->tbl;
1776    saved_state *tem = current_saved_state;
1777    current_saved_state = current_saved_state->prev;
1778    delete tem;
1779  }
1780  assert(current_table == &top_table);
1781  PTABLE_ITERATOR(place) iter(current_table);
1782  const char *key;
1783  place *pl;
1784  while (iter.next(&key, &pl))
1785    if (pl->obj != 0) {
1786      position pos = pl->obj->origin();
1787      pl->obj = 0;
1788      pl->x = pos.x;
1789      pl->y = pos.y;
1790    }
1791  while (olist.head != 0) {
1792    object *tem = olist.head;
1793    olist.head = olist.head->next;
1794    delete tem;
1795  }
1796  olist.tail = 0;
1797  current_direction = RIGHT_DIRECTION;
1798  current_position.x = 0.0;
1799  current_position.y = 0.0;
1800}
1801
1802const char *ordinal_postfix(int n)
1803{
1804  if (n < 10 || n > 20)
1805    switch (n % 10) {
1806    case 1:
1807      return "st";
1808    case 2:
1809      return "nd";
1810    case 3:
1811      return "rd";
1812    }
1813  return "th";
1814}
1815
1816const char *object_type_name(object_type type)
1817{
1818  switch (type) {
1819  case BOX_OBJECT:
1820    return "box";
1821  case CIRCLE_OBJECT:
1822    return "circle";
1823  case ELLIPSE_OBJECT:
1824    return "ellipse";
1825  case ARC_OBJECT:
1826    return "arc";
1827  case SPLINE_OBJECT:
1828    return "spline";
1829  case LINE_OBJECT:
1830    return "line";
1831  case ARROW_OBJECT:
1832    return "arrow";
1833  case MOVE_OBJECT:
1834    return "move";
1835  case TEXT_OBJECT:
1836    return "\"\"";
1837  case BLOCK_OBJECT:
1838    return "[]";
1839  case OTHER_OBJECT:
1840  case MARK_OBJECT:
1841  default:
1842    break;
1843  }
1844  return "object";
1845}
1846
1847static char sprintf_buf[1024];
1848
1849char *format_number(const char *form, double n)
1850{
1851  if (form == 0)
1852    form = "%g";
1853  return do_sprintf(form, &n, 1);
1854}
1855
1856char *do_sprintf(const char *form, const double *v, int nv)
1857{
1858  string result;
1859  int i = 0;
1860  string one_format;
1861  while (*form) {
1862    if (*form == '%') {
1863      one_format += *form++;
1864      for (; *form != '\0' && strchr("#-+ 0123456789.", *form) != 0; form++)
1865	one_format += *form;
1866      if (*form == '\0' || strchr("eEfgG%", *form) == 0) {
1867	lex_error("bad sprintf format");
1868	result += one_format;
1869	result += form;
1870	break;
1871      }
1872      if (*form == '%') {
1873	one_format += *form++;
1874	one_format += '\0';
1875	snprintf(sprintf_buf, sizeof(sprintf_buf),
1876		 "%s", one_format.contents());
1877      }
1878      else {
1879	if (i >= nv) {
1880	  lex_error("too few arguments to snprintf");
1881	  result += one_format;
1882	  result += form;
1883	  break;
1884	}
1885	one_format += *form++;
1886	one_format += '\0';
1887	snprintf(sprintf_buf, sizeof(sprintf_buf),
1888		 one_format.contents(), v[i++]);
1889      }
1890      one_format.clear();
1891      result += sprintf_buf;
1892    }
1893    else
1894      result += *form++;
1895  }
1896  result += '\0';
1897  return strsave(result.contents());
1898}