groff: remove version tag from directory
[dragonfly.git] / contrib / groff / src / preproc / pic / pic.y
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
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
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
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
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
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
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
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
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
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
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
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
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 }