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