xref: /freebsd-11-stable/contrib/groff/src/preproc/pic/pic.y (revision 22e7cbb8744990e63e02dc3985780d932c67b1a4)
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 
5 This file is part of groff.
6 
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11 
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16 
17 You should have received a copy of the GNU General Public License along
18 with groff; see the file COPYING.  If not, write to the Free Software
19 Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
20 %{
21 #include "pic.h"
22 #include "ptable.h"
23 #include "object.h"
24 
25 extern int delim_flag;
26 extern void copy_rest_thru(const char *, const char *);
27 extern void copy_file_thru(const char *, const char *, const char *);
28 extern void push_body(const char *);
29 extern void do_for(char *var, double from, double to,
30 		   int by_is_multiplicative, double by, char *body);
31 extern void do_lookahead();
32 
33 /* Maximum number of characters produced by printf("%g") */
34 #define GDIGITS 14
35 
36 int yylex();
37 void yyerror(const char *);
38 
39 void reset(const char *nm);
40 void reset_all();
41 
42 place *lookup_label(const char *);
43 void define_label(const char *label, const place *pl);
44 
45 direction current_direction;
46 position current_position;
47 
48 implement_ptable(place)
49 
50 PTABLE(place) top_table;
51 
52 PTABLE(place) *current_table = &top_table;
53 saved_state *current_saved_state = 0;
54 
55 object_list olist;
56 
57 const char *ordinal_postfix(int n);
58 const char *object_type_name(object_type type);
59 char *format_number(const char *form, double n);
60 char *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
217 box "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
224 precedence than left and right, so that eg `line chop left'
225 parses 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'
238 works */
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 
275 top:
276 	optional_separator
277 	| element_list
278 		{
279 		  if (olist.head)
280 		    print_picture(olist.head);
281 		}
282 	;
283 
284 
285 element_list:
286 	optional_separator middle_element_list optional_separator
287 		{ $$ = $2; }
288 	;
289 
290 middle_element_list:
291 	element
292 		{ $$ = $1; }
293 	| middle_element_list separator element
294 		{ $$ = $1; }
295 	;
296 
297 optional_separator:
298 	/* empty */
299 	| separator
300 	;
301 
302 separator:
303 	';'
304 	| separator ';'
305 	;
306 
307 placeless_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 
436 macro_name:
437 	VARIABLE
438 	| LABEL
439 	;
440 
441 reset_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 
459 print_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 
480 print_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 
499 simple_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 
510 until:
511 	/* empty */
512 		{ $$ = 0; }
513 	| UNTIL TEXT
514 		{ $$ = $2.str; }
515 	;
516 
517 any_expr:
518 	expr
519 		{ $$ = $1; }
520 	| text_expr
521 		{ $$ = $1; }
522 	;
523 
524 text_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 
554 optional_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 
572 element:
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 
631 optional_element:
632 	/* empty */
633 		{}
634 	| element
635 		{}
636 	;
637 
638 object_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 
1080 text:
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 
1093 sprintf_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 
1129 position:
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 
1146 position_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 
1196 between:
1197 	BETWEEN
1198 	| OF THE WAY BETWEEN
1199 	;
1200 
1201 expr_pair:
1202 	expr ',' expr
1203 		{
1204 		  $$.x = $1;
1205 		  $$.y = $3;
1206 		}
1207 	| '(' expr_pair ')'
1208 		{ $$ = $2; }
1209 	;
1210 
1211 place:
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 
1241 label:
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 
1262 ordinal:
1263 	ORDINAL
1264 		{ $$ = $1; }
1265 	| '`' any_expr TH
1266 		{
1267 		  // XXX Check for overflow (and non-integers?).
1268 		  $$ = (int)$2;
1269 		}
1270 	;
1271 
1272 optional_ordinal_last:
1273 	LAST
1274 		{ $$ = 1; }
1275   	| ordinal LAST
1276 		{ $$ = $1; }
1277 	;
1278 
1279 nth_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 
1312 object_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 
1333 label_path:
1334  	'.' LABEL
1335 		{ $$ = new path($2); }
1336 	| label_path '.' LABEL
1337 		{
1338 		  $$ = $1;
1339 		  $$->append($3);
1340 		}
1341 	;
1342 
1343 relative_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 
1357 path:
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 
1392 corner:
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 
1459 expr:
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
1645 isn't under cfront */
1646 
1647 #ifdef const
1648 #undef const
1649 #endif
1650 
1651 static 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 
lookup_label(const char * label)1680 place *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 
define_label(const char * label,const place * pl)1695 void 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 
lookup_variable(const char * name,double * val)1702 int 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 
define_variable(const char * name,double val)1712 void 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 
parse_init()1731 void 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 
reset(const char * nm)1740 void 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 
reset_all()1757 void 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 
parse_cleanup()1771 void 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 
ordinal_postfix(int n)1802 const 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 
object_type_name(object_type type)1816 const 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 
1847 static char sprintf_buf[1024];
1848 
format_number(const char * form,double n)1849 char *format_number(const char *form, double n)
1850 {
1851   if (form == 0)
1852     form = "%g";
1853   return do_sprintf(form, &n, 1);
1854 }
1855 
do_sprintf(const char * form,const double * v,int nv)1856 char *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