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