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