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