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}
1899