groff before CVS: release 1.06
[s-roff.git] / pic / pic.y
blob7bcd6fe145e9cdadba9a9d6a098d18838d81fd78
1 /* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
2 Written by James Clark (jjc@jclark.com)
4 This file is part of groff.
6 groff is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2, or (at your option) any later
9 version.
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.
16 You should have received a copy of the GNU General Public License along
17 with groff; see the file COPYING. If not, write to the Free Software
18 Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
20 #include "pic.h"
21 #include "ptable.h"
22 #include "object.h"
24 extern int delim_flag;
25 extern void do_copy(const char *);
26 extern void copy_rest_thru(const char *, const char *);
27 extern void copy_file_thru(const char *, const char *, const char *);
28 extern void push_body(const char *);
29 extern void do_for(char *var, double from, double to,
30 int by_is_multiplicative, double by, char *body);
31 extern void do_lookahead();
33 #undef fmod
34 #undef rand
36 extern "C" {
37 double fmod(double, double);
38 int rand();
41 /* Maximum number of characters produced by printf("%g") */
42 #define GDIGITS 14
44 #ifndef __BORLANDC__
45 #define YYDEBUG 1
46 #endif /* __BORLANDC__ */
48 int yylex();
49 void yyerror(const char *);
51 void reset(const char *nm);
52 void reset_all();
54 place *lookup_label(const char *);
55 void define_label(const char *label, const place *pl);
57 direction current_direction;
58 position current_position;
60 implement_ptable(place)
62 PTABLE(place) top_table;
64 PTABLE(place) *current_table = &top_table;
65 saved_state *current_saved_state = 0;
67 object_list olist;
69 const char *ordinal_postfix(int n);
70 const char *object_type_name(object_type type);
71 char *format_number(const char *form, double n);
72 char *do_sprintf(const char *form, const double *v, int nv);
77 %union {
78 char *str;
79 int n;
80 double x;
81 struct { double x, y; } pair;
82 struct { double x; char *body; } if_data;
83 struct { char *str; const char *filename; int lineno; } lstr;
84 struct { double *v; int nv; int maxv; } dv;
85 struct { double val; int is_multiplicative; } by;
86 place pl;
87 object *obj;
88 corner crn;
89 path *pth;
90 object_spec *spec;
91 saved_state *pstate;
92 graphics_state state;
93 object_type obtype;
96 %token <str> LABEL
97 %token <str> VARIABLE
98 %token <x> NUMBER
99 %token <lstr> TEXT
100 %token <lstr> COMMAND_LINE
101 %token <str> DELIMITED
102 %token <n> ORDINAL
103 %token TH
104 %token LEFT_ARROW_HEAD
105 %token RIGHT_ARROW_HEAD
106 %token DOUBLE_ARROW_HEAD
107 %token LAST
108 %token UP
109 %token DOWN
110 %token LEFT
111 %token RIGHT
112 %token BOX
113 %token CIRCLE
114 %token ELLIPSE
115 %token ARC
116 %token LINE
117 %token ARROW
118 %token MOVE
119 %token SPLINE
120 %token HEIGHT
121 %token RADIUS
122 %token WIDTH
123 %token DIAMETER
124 %token UP
125 %token DOWN
126 %token RIGHT
127 %token LEFT
128 %token FROM
129 %token TO
130 %token AT
131 %token WITH
132 %token BY
133 %token THEN
134 %token DOTTED
135 %token DASHED
136 %token CHOP
137 %token SAME
138 %token INVISIBLE
139 %token LJUST
140 %token RJUST
141 %token ABOVE
142 %token BELOW
143 %token OF
144 %token THE
145 %token WAY
146 %token BETWEEN
147 %token AND
148 %token HERE
149 %token DOT_N
150 %token DOT_E
151 %token DOT_W
152 %token DOT_S
153 %token DOT_NE
154 %token DOT_SE
155 %token DOT_NW
156 %token DOT_SW
157 %token DOT_C
158 %token DOT_START
159 %token DOT_END
160 %token DOT_X
161 %token DOT_Y
162 %token DOT_HT
163 %token DOT_WID
164 %token DOT_RAD
165 %token SIN
166 %token COS
167 %token ATAN2
168 %token LOG
169 %token EXP
170 %token SQRT
171 %token MAX
172 %token MIN
173 %token INT
174 %token RAND
175 %token COPY
176 %token THRU
177 %token TOP
178 %token BOTTOM
179 %token UPPER
180 %token LOWER
181 %token SH
182 %token PRINT
183 %token CW
184 %token CCW
185 %token FOR
186 %token DO
187 %token IF
188 %token ELSE
189 %token ANDAND
190 %token OROR
191 %token NOTEQUAL
192 %token EQUALEQUAL
193 %token LESSEQUAL
194 %token GREATEREQUAL
195 %token LEFT_CORNER
196 %token RIGHT_CORNER
197 %token CENTER
198 %token END
199 %token START
200 %token RESET
201 %token UNTIL
202 %token PLOT
203 %token THICKNESS
204 %token FILL
205 %token ALIGNED
206 %token SPRINTF
207 %token COMMAND
209 %token DEFINE
210 %token UNDEF
212 /* this ensures that plot 17 "%g" parses as (plot 17 "%g") */
213 %left PLOT
214 %left TEXT SPRINTF
216 /* give text adjustments higher precedence than TEXT, so that
217 box "foo" above ljust == box ("foo" above ljust)
220 %left LJUST RJUST ABOVE BELOW
222 %left LEFT RIGHT
223 /* Give attributes that take an optional expression a higher
224 precedence than left and right, so that eg `line chop left'
225 parses properly. */
226 %left CHOP DASHED DOTTED UP DOWN FILL
227 %left LABEL
229 %left VARIABLE NUMBER '(' SIN COS ATAN2 LOG EXP SQRT MAX MIN INT RAND LAST
230 %left ORDINAL HERE '`'
232 /* these need to be lower than '-' */
233 %left HEIGHT RADIUS WIDTH DIAMETER FROM TO AT THICKNESS
235 /* these must have higher precedence than CHOP so that `label %prec CHOP'
236 works */
237 %left DOT_N DOT_E DOT_W DOT_S DOT_NE DOT_SE DOT_NW DOT_SW DOT_C
238 %left DOT_START DOT_END TOP BOTTOM LEFT_CORNER RIGHT_CORNER
239 %left UPPER LOWER CENTER START END
241 %left ','
242 %left OROR
243 %left ANDAND
244 %left EQUALEQUAL NOTEQUAL
245 %left '<' '>' LESSEQUAL GREATEREQUAL
247 %left BETWEEN OF
248 %left AND
250 %left '+' '-'
251 %left '*' '/' '%'
252 %right '!'
253 %right '^'
255 %type <x> expr any_expr text_expr
256 %type <by> optional_by
257 %type <pair> expr_pair position_not_place
258 %type <if_data> simple_if
259 %type <obj> nth_primitive
260 %type <crn> corner
261 %type <pth> path label_path relative_path
262 %type <pl> place label element element_list middle_element_list
263 %type <spec> object_spec
264 %type <pair> position
265 %type <obtype> object_type
266 %type <n> optional_ordinal_last ordinal
267 %type <str> until
268 %type <dv> sprintf_args
269 %type <lstr> text print_args print_arg
273 top:
274 optional_separator
275 | element_list
277 if (olist.head)
278 print_picture(olist.head);
283 element_list:
284 optional_separator middle_element_list optional_separator
285 { $$ = $2; }
288 middle_element_list:
289 element
290 { $$ = $1; }
291 | middle_element_list separator element
292 { $$ = $1; }
295 optional_separator:
296 /* empty */
297 | separator
300 separator:
302 | separator ';'
305 placeless_element:
306 VARIABLE '=' any_expr
308 define_variable($1, $3);
309 a_delete $1;
311 | VARIABLE ':' '=' any_expr
313 place *p = lookup_label($1);
314 if (!p) {
315 lex_error("variable `%1' not defined", $1);
316 YYABORT;
318 p->obj = 0;
319 p->x = $4;
320 p->y = 0.0;
321 a_delete $1;
323 | UP
324 { current_direction = UP_DIRECTION; }
325 | DOWN
326 { current_direction = DOWN_DIRECTION; }
327 | LEFT
328 { current_direction = LEFT_DIRECTION; }
329 | RIGHT
330 { current_direction = RIGHT_DIRECTION; }
331 | COMMAND_LINE
333 olist.append(make_command_object($1.str, $1.filename,
334 $1.lineno));
336 | COMMAND print_args
338 olist.append(make_command_object($2.str, $2.filename,
339 $2.lineno));
341 | PRINT print_args
343 fprintf(stderr, "%s\n", $2.str);
344 fflush(stderr);
346 | SH
347 { delim_flag = 1; }
348 DELIMITED
350 delim_flag = 0;
351 system($3);
352 a_delete $3;
354 | COPY TEXT
356 if (yychar < 0)
357 do_lookahead();
358 do_copy($2.str);
359 // do not delete the filename
361 | COPY TEXT THRU
362 { delim_flag = 2; }
363 DELIMITED
364 { delim_flag = 0; }
365 until
367 if (yychar < 0)
368 do_lookahead();
369 copy_file_thru($2.str, $5, $7);
370 // do not delete the filename
371 a_delete $5;
372 a_delete $7;
374 | COPY THRU
375 { delim_flag = 2; }
376 DELIMITED
377 { delim_flag = 0; }
378 until
380 if (yychar < 0)
381 do_lookahead();
382 copy_rest_thru($4, $6);
383 a_delete $4;
384 a_delete $6;
386 | FOR VARIABLE '=' expr TO expr optional_by DO
387 { delim_flag = 1; }
388 DELIMITED
390 delim_flag = 0;
391 if (yychar < 0)
392 do_lookahead();
393 do_for($2, $4, $6, $7.is_multiplicative, $7.val, $10);
395 | simple_if
397 if (yychar < 0)
398 do_lookahead();
399 if ($1.x != 0.0)
400 push_body($1.body);
401 a_delete $1.body;
403 | simple_if ELSE
404 { delim_flag = 1; }
405 DELIMITED
407 delim_flag = 0;
408 if (yychar < 0)
409 do_lookahead();
410 if ($1.x != 0.0)
411 push_body($1.body);
412 else
413 push_body($4);
414 a_delete $1.body;
415 a_delete $4;
417 | reset_variables
418 | RESET
419 { define_variable("scale", 1.0); }
422 reset_variables:
423 RESET VARIABLE
424 { reset($2); a_delete $2; }
425 | reset_variables VARIABLE
426 { reset($2); a_delete $2; }
427 | reset_variables ',' VARIABLE
428 { reset($3); a_delete $3; }
431 print_args:
432 print_arg
433 { $$ = $1; }
434 | print_args print_arg
436 $$.str = new char[strlen($1.str) + strlen($2.str) + 1];
437 strcpy($$.str, $1.str);
438 strcat($$.str, $2.str);
439 a_delete $1.str;
440 a_delete $2.str;
441 if ($1.filename) {
442 $$.filename = $1.filename;
443 $$.lineno = $1.lineno;
445 else if ($2.filename) {
446 $$.filename = $2.filename;
447 $$.lineno = $2.lineno;
452 print_arg:
453 expr %prec ','
455 $$.str = new char[GDIGITS + 1];
456 sprintf($$.str, "%g", $1);
457 $$.filename = 0;
458 $$.lineno = 0;
460 | text
461 { $$ = $1; }
462 | position %prec ','
464 $$.str = new char[GDIGITS + 2 + GDIGITS + 1];
465 sprintf($$.str, "%g, %g", $1.x, $1.y);
466 $$.filename = 0;
467 $$.lineno = 0;
470 simple_if:
471 IF any_expr THEN
472 { delim_flag = 1; }
473 DELIMITED
474 { delim_flag = 0; $$.x = $2; $$.body = $5; }
477 until:
478 /* empty */
479 { $$ = 0; }
480 | UNTIL TEXT
481 { $$ = $2.str; }
484 any_expr:
485 expr
486 { $$ = $1; }
487 | text_expr
488 { $$ = $1; }
491 text_expr:
492 text EQUALEQUAL text
494 $$ = strcmp($1.str, $3.str) == 0;
495 a_delete $1.str;
496 a_delete $3.str;
498 | text NOTEQUAL text
500 $$ = strcmp($1.str, $3.str) != 0;
501 a_delete $1.str;
502 a_delete $3.str;
504 | text_expr ANDAND text_expr
505 { $$ = ($1 != 0.0 && $3 != 0.0); }
506 | text_expr ANDAND expr
507 { $$ = ($1 != 0.0 && $3 != 0.0); }
508 | expr ANDAND text_expr
509 { $$ = ($1 != 0.0 && $3 != 0.0); }
510 | text_expr OROR text_expr
511 { $$ = ($1 != 0.0 || $3 != 0.0); }
512 | text_expr OROR expr
513 { $$ = ($1 != 0.0 || $3 != 0.0); }
514 | expr OROR text_expr
515 { $$ = ($1 != 0.0 || $3 != 0.0); }
516 | '!' text_expr
517 { $$ = ($2 == 0.0); }
521 optional_by:
522 /* empty */
523 { $$.val = 1.0; $$.is_multiplicative = 0; }
524 | BY expr
525 { $$.val = $2; $$.is_multiplicative = 0; }
526 | BY '*' expr
527 { $$.val = $3; $$.is_multiplicative = 1; }
530 element:
531 object_spec
533 $$.obj = $1->make_object(&current_position,
534 &current_direction);
535 if ($$.obj == 0)
536 YYABORT;
537 delete $1;
538 if ($$.obj)
539 olist.append($$.obj);
540 else {
541 $$.x = current_position.x;
542 $$.y = current_position.y;
545 | LABEL ':' optional_separator element
546 { $$ = $4; define_label($1, & $$); a_delete $1; }
547 | LABEL ':' optional_separator position_not_place
549 $$.obj = 0;
550 $$.x = $4.x;
551 $$.y = $4.y;
552 define_label($1, & $$);
553 a_delete $1;
555 | LABEL ':' optional_separator place
557 $$ = $4;
558 define_label($1, & $$);
559 a_delete $1;
561 | '{'
563 $<state>$.x = current_position.x;
564 $<state>$.y = current_position.y;
565 $<state>$.dir = current_direction;
567 element_list '}'
569 current_position.x = $<state>2.x;
570 current_position.y = $<state>2.y;
571 current_direction = $<state>2.dir;
573 optional_element
575 $$ = $3;
577 | placeless_element
579 $$.obj = 0;
580 $$.x = current_position.x;
581 $$.y = current_position.y;
585 optional_element:
586 /* empty */
588 | element
592 object_spec:
595 $$ = new object_spec(BOX_OBJECT);
597 | CIRCLE
599 $$ = new object_spec(CIRCLE_OBJECT);
601 | ELLIPSE
603 $$ = new object_spec(ELLIPSE_OBJECT);
605 | ARC
607 $$ = new object_spec(ARC_OBJECT);
608 $$->dir = current_direction;
610 | LINE
612 $$ = new object_spec(LINE_OBJECT);
613 lookup_variable("lineht", & $$->segment_height);
614 lookup_variable("linewid", & $$->segment_width);
615 $$->dir = current_direction;
617 | ARROW
619 $$ = new object_spec(ARROW_OBJECT);
620 lookup_variable("lineht", & $$->segment_height);
621 lookup_variable("linewid", & $$->segment_width);
622 $$->dir = current_direction;
624 | MOVE
626 $$ = new object_spec(MOVE_OBJECT);
627 lookup_variable("moveht", & $$->segment_height);
628 lookup_variable("movewid", & $$->segment_width);
629 $$->dir = current_direction;
631 | SPLINE
633 $$ = new object_spec(SPLINE_OBJECT);
634 lookup_variable("lineht", & $$->segment_height);
635 lookup_variable("linewid", & $$->segment_width);
636 $$->dir = current_direction;
638 | text %prec TEXT
640 $$ = new object_spec(TEXT_OBJECT);
641 $$->text = new text_item($1.str, $1.filename, $1.lineno);
643 | PLOT expr
645 $$ = new object_spec(TEXT_OBJECT);
646 $$->text = new text_item(format_number(0, $2), 0, -1);
648 | PLOT expr text
650 $$ = new object_spec(TEXT_OBJECT);
651 $$->text = new text_item(format_number($3.str, $2),
652 $3.filename, $3.lineno);
653 a_delete $3.str;
655 | '['
657 saved_state *p = new saved_state;
658 $<pstate>$ = p;
659 p->x = current_position.x;
660 p->y = current_position.y;
661 p->dir = current_direction;
662 p->tbl = current_table;
663 p->prev = current_saved_state;
664 current_position.x = 0.0;
665 current_position.y = 0.0;
666 current_table = new PTABLE(place);
667 current_saved_state = p;
668 olist.append(make_mark_object());
670 element_list ']'
672 current_position.x = $<pstate>2->x;
673 current_position.y = $<pstate>2->y;
674 current_direction = $<pstate>2->dir;
675 $$ = new object_spec(BLOCK_OBJECT);
676 olist.wrap_up_block(& $$->oblist);
677 $$->tbl = current_table;
678 current_table = $<pstate>2->tbl;
679 current_saved_state = $<pstate>2->prev;
680 delete $<pstate>2;
682 | object_spec HEIGHT expr
684 $$ = $1;
685 $$->height = $3;
686 $$->flags |= HAS_HEIGHT;
688 | object_spec RADIUS expr
690 $$ = $1;
691 $$->radius = $3;
692 $$->flags |= HAS_RADIUS;
694 | object_spec WIDTH expr
696 $$ = $1;
697 $$->width = $3;
698 $$->flags |= HAS_WIDTH;
700 | object_spec DIAMETER expr
702 $$ = $1;
703 $$->radius = $3/2.0;
704 $$->flags |= HAS_RADIUS;
706 | object_spec expr %prec HEIGHT
708 $$ = $1;
709 $$->flags |= HAS_SEGMENT;
710 switch ($$->dir) {
711 case UP_DIRECTION:
712 $$->segment_pos.y += $2;
713 break;
714 case DOWN_DIRECTION:
715 $$->segment_pos.y -= $2;
716 break;
717 case RIGHT_DIRECTION:
718 $$->segment_pos.x += $2;
719 break;
720 case LEFT_DIRECTION:
721 $$->segment_pos.x -= $2;
722 break;
725 | object_spec UP
727 $$ = $1;
728 $$->dir = UP_DIRECTION;
729 $$->flags |= HAS_SEGMENT;
730 $$->segment_pos.y += $$->segment_height;
732 | object_spec UP expr
734 $$ = $1;
735 $$->dir = UP_DIRECTION;
736 $$->flags |= HAS_SEGMENT;
737 $$->segment_pos.y += $3;
739 | object_spec DOWN
741 $$ = $1;
742 $$->dir = DOWN_DIRECTION;
743 $$->flags |= HAS_SEGMENT;
744 $$->segment_pos.y -= $$->segment_height;
746 | object_spec DOWN expr
748 $$ = $1;
749 $$->dir = DOWN_DIRECTION;
750 $$->flags |= HAS_SEGMENT;
751 $$->segment_pos.y -= $3;
753 | object_spec RIGHT
755 $$ = $1;
756 $$->dir = RIGHT_DIRECTION;
757 $$->flags |= HAS_SEGMENT;
758 $$->segment_pos.x += $$->segment_width;
760 | object_spec RIGHT expr
762 $$ = $1;
763 $$->dir = RIGHT_DIRECTION;
764 $$->flags |= HAS_SEGMENT;
765 $$->segment_pos.x += $3;
767 | object_spec LEFT
769 $$ = $1;
770 $$->dir = LEFT_DIRECTION;
771 $$->flags |= HAS_SEGMENT;
772 $$->segment_pos.x -= $$->segment_width;
774 | object_spec LEFT expr
776 $$ = $1;
777 $$->dir = LEFT_DIRECTION;
778 $$->flags |= HAS_SEGMENT;
779 $$->segment_pos.x -= $3;
781 | object_spec FROM position
783 $$ = $1;
784 $$->flags |= HAS_FROM;
785 $$->from.x = $3.x;
786 $$->from.y = $3.y;
788 | object_spec TO position
790 $$ = $1;
791 if ($$->flags & HAS_SEGMENT)
792 $$->segment_list = new segment($$->segment_pos,
793 $$->segment_is_absolute,
794 $$->segment_list);
795 $$->flags |= HAS_SEGMENT;
796 $$->segment_pos.x = $3.x;
797 $$->segment_pos.y = $3.y;
798 $$->segment_is_absolute = 1;
799 $$->flags |= HAS_TO;
800 $$->to.x = $3.x;
801 $$->to.y = $3.y;
803 | object_spec AT position
805 $$ = $1;
806 $$->flags |= HAS_AT;
807 $$->at.x = $3.x;
808 $$->at.y = $3.y;
809 if ($$->type != ARC_OBJECT) {
810 $$->flags |= HAS_FROM;
811 $$->from.x = $3.x;
812 $$->from.y = $3.y;
815 | object_spec WITH path
817 $$ = $1;
818 $$->flags |= HAS_WITH;
819 $$->with = $3;
821 | object_spec BY expr_pair
823 $$ = $1;
824 $$->flags |= HAS_SEGMENT;
825 $$->segment_pos.x += $3.x;
826 $$->segment_pos.y += $3.y;
828 | object_spec THEN
830 $$ = $1;
831 if ($$->flags & HAS_SEGMENT) {
832 $$->segment_list = new segment($$->segment_pos,
833 $$->segment_is_absolute,
834 $$->segment_list);
835 $$->flags &= ~HAS_SEGMENT;
836 $$->segment_pos.x = $$->segment_pos.y = 0.0;
837 $$->segment_is_absolute = 0;
840 | object_spec DOTTED
842 $$ = $1;
843 $$->flags |= IS_DOTTED;
844 lookup_variable("dashwid", & $$->dash_width);
846 | object_spec DOTTED expr
848 $$ = $1;
849 $$->flags |= IS_DOTTED;
850 $$->dash_width = $3;
852 | object_spec DASHED
854 $$ = $1;
855 $$->flags |= IS_DASHED;
856 lookup_variable("dashwid", & $$->dash_width);
858 | object_spec DASHED expr
860 $$ = $1;
861 $$->flags |= IS_DASHED;
862 $$->dash_width = $3;
864 | object_spec FILL
866 $$ = $1;
867 $$->flags |= IS_DEFAULT_FILLED;
869 | object_spec FILL expr
871 $$ = $1;
872 $$->flags |= IS_FILLED;
873 $$->fill = $3;
875 | object_spec CHOP
877 $$ = $1;
878 // line chop chop means line chop 0 chop 0
879 if ($$->flags & IS_DEFAULT_CHOPPED) {
880 $$->flags |= IS_CHOPPED;
881 $$->flags &= ~IS_DEFAULT_CHOPPED;
882 $$->start_chop = $$->end_chop = 0.0;
884 else if ($$->flags & IS_CHOPPED) {
885 $$->end_chop = 0.0;
887 else {
888 $$->flags |= IS_DEFAULT_CHOPPED;
891 | object_spec CHOP expr
893 $$ = $1;
894 if ($$->flags & IS_DEFAULT_CHOPPED) {
895 $$->flags |= IS_CHOPPED;
896 $$->flags &= ~IS_DEFAULT_CHOPPED;
897 $$->start_chop = 0.0;
898 $$->end_chop = $3;
900 else if ($$->flags & IS_CHOPPED) {
901 $$->end_chop = $3;
903 else {
904 $$->start_chop = $$->end_chop = $3;
905 $$->flags |= IS_CHOPPED;
908 | object_spec SAME
910 $$ = $1;
911 $$->flags |= IS_SAME;
913 | object_spec INVISIBLE
915 $$ = $1;
916 $$->flags |= IS_INVISIBLE;
918 | object_spec LEFT_ARROW_HEAD
920 $$ = $1;
921 $$->flags |= HAS_LEFT_ARROW_HEAD;
923 | object_spec RIGHT_ARROW_HEAD
925 $$ = $1;
926 $$->flags |= HAS_RIGHT_ARROW_HEAD;
928 | object_spec DOUBLE_ARROW_HEAD
930 $$ = $1;
931 $$->flags |= (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD);
933 | object_spec CW
935 $$ = $1;
936 $$->flags |= IS_CLOCKWISE;
938 | object_spec CCW
940 $$ = $1;
941 $$->flags &= ~IS_CLOCKWISE;
943 | object_spec text %prec TEXT
945 $$ = $1;
946 for (text_item **p = & $$->text; *p; p = &(*p)->next)
948 *p = new text_item($2.str, $2.filename, $2.lineno);
950 | object_spec LJUST
952 $$ = $1;
953 if ($$->text) {
954 for (text_item *p = $$->text; p->next; p = p->next)
956 p->adj.h = LEFT_ADJUST;
959 | object_spec RJUST
961 $$ = $1;
962 if ($$->text) {
963 for (text_item *p = $$->text; p->next; p = p->next)
965 p->adj.h = RIGHT_ADJUST;
968 | object_spec ABOVE
970 $$ = $1;
971 if ($$->text) {
972 for (text_item *p = $$->text; p->next; p = p->next)
974 p->adj.v = ABOVE_ADJUST;
977 | object_spec BELOW
979 $$ = $1;
980 if ($$->text) {
981 for (text_item *p = $$->text; p->next; p = p->next)
983 p->adj.v = BELOW_ADJUST;
986 | object_spec THICKNESS expr
988 $$ = $1;
989 $$->flags |= HAS_THICKNESS;
990 $$->thickness = $3;
992 | object_spec ALIGNED
994 $$ = $1;
995 $$->flags |= IS_ALIGNED;
999 text:
1000 TEXT
1002 $$ = $1;
1004 | SPRINTF '(' TEXT sprintf_args ')'
1006 $$.filename = $3.filename;
1007 $$.lineno = $3.lineno;
1008 $$.str = do_sprintf($3.str, $4.v, $4.nv);
1009 a_delete $4.v;
1010 a_delete $3.str;
1014 sprintf_args:
1015 /* empty */
1017 $$.v = 0;
1018 $$.nv = 0;
1019 $$.maxv = 0;
1021 | sprintf_args ',' expr
1023 $$ = $1;
1024 if ($$.nv >= $$.maxv) {
1025 if ($$.nv == 0) {
1026 $$.v = new double[4];
1027 $$.maxv = 4;
1029 else {
1030 double *oldv = $$.v;
1031 $$.maxv *= 2;
1032 $$.v = new double[$$.maxv];
1033 memcpy($$.v, oldv, $$.nv*sizeof(double));
1034 a_delete oldv;
1037 $$.v[$$.nv] = $3;
1038 $$.nv += 1;
1042 position:
1043 position_not_place
1044 { $$ = $1; }
1045 | place
1047 position pos = $1;
1048 $$.x = pos.x;
1049 $$.y = pos.y;
1053 position_not_place:
1054 expr_pair
1055 { $$ = $1; }
1056 | position '+' expr_pair
1058 $$.x = $1.x + $3.x;
1059 $$.y = $1.y + $3.y;
1061 | position '-' expr_pair
1063 $$.x = $1.x - $3.x;
1064 $$.y = $1.y - $3.y;
1066 | '(' position ',' position ')'
1068 $$.x = $2.x;
1069 $$.y = $4.y;
1071 | expr between position AND position
1073 $$.x = (1.0 - $1)*$3.x + $1*$5.x;
1074 $$.y = (1.0 - $1)*$3.y + $1*$5.y;
1076 | expr '<' position ',' position '>'
1078 $$.x = (1.0 - $1)*$3.x + $1*$5.x;
1079 $$.y = (1.0 - $1)*$3.y + $1*$5.y;
1083 between:
1084 BETWEEN
1085 | OF THE WAY BETWEEN
1088 expr_pair:
1089 expr ',' expr
1090 { $$.x = $1; $$.y = $3; }
1091 | '(' expr_pair ')'
1092 { $$ = $2; }
1095 place:
1096 label %prec CHOP /* line at A left == line (at A) left */
1097 { $$ = $1; }
1098 | label corner
1100 path pth($2);
1101 if (!pth.follow($1, & $$))
1102 YYABORT;
1104 | corner label
1106 path pth($1);
1107 if (!pth.follow($2, & $$))
1108 YYABORT;
1110 | corner OF label
1112 path pth($1);
1113 if (!pth.follow($3, & $$))
1114 YYABORT;
1116 | HERE
1118 $$.x = current_position.x;
1119 $$.y = current_position.y;
1120 $$.obj = 0;
1124 label:
1125 LABEL
1127 place *p = lookup_label($1);
1128 if (!p) {
1129 lex_error("there is no place `%1'", $1);
1130 YYABORT;
1132 $$ = *p;
1133 a_delete $1;
1135 | nth_primitive
1137 $$.obj = $1;
1139 | label '.' LABEL
1141 path pth($3);
1142 if (!pth.follow($1, & $$))
1143 YYABORT;
1147 ordinal:
1148 ORDINAL
1149 { $$ = $1; }
1150 | '`' any_expr TH
1152 // XXX Check for overflow (and non-integers?).
1153 $$ = (int)$2;
1157 optional_ordinal_last:
1158 LAST
1159 { $$ = 1; }
1160 | ordinal LAST
1161 { $$ = $1; }
1164 nth_primitive:
1165 ordinal object_type
1167 int count = 0;
1168 for (object *p = olist.head; p != 0; p = p->next)
1169 if (p->type() == $2 && ++count == $1) {
1170 $$ = p;
1171 break;
1173 if (p == 0) {
1174 lex_error("there is no %1%2 %3", $1, ordinal_postfix($1),
1175 object_type_name($2));
1176 YYABORT;
1179 | optional_ordinal_last object_type
1181 int count = 0;
1182 for (object *p = olist.tail; p != 0; p = p->prev)
1183 if (p->type() == $2 && ++count == $1) {
1184 $$ = p;
1185 break;
1187 if (p == 0) {
1188 lex_error("there is no %1%2 last %3", $1,
1189 ordinal_postfix($1), object_type_name($2));
1190 YYABORT;
1195 object_type:
1197 { $$ = BOX_OBJECT; }
1198 | CIRCLE
1199 { $$ = CIRCLE_OBJECT; }
1200 | ELLIPSE
1201 { $$ = ELLIPSE_OBJECT; }
1202 | ARC
1203 { $$ = ARC_OBJECT; }
1204 | LINE
1205 { $$ = LINE_OBJECT; }
1206 | ARROW
1207 { $$ = ARROW_OBJECT; }
1208 | SPLINE
1209 { $$ = SPLINE_OBJECT; }
1210 | '[' ']'
1211 { $$ = BLOCK_OBJECT; }
1212 | TEXT
1213 { $$ = TEXT_OBJECT; }
1216 label_path:
1217 '.' LABEL
1219 $$ = new path($2);
1221 | label_path '.' LABEL
1223 $$ = $1;
1224 $$->append($3);
1228 relative_path:
1229 corner
1231 $$ = new path($1);
1233 /* give this a lower precedence than LEFT and RIGHT so that
1234 [A: box] with .A left == [A: box] with (.A left) */
1236 | label_path %prec TEXT
1238 $$ = $1;
1240 | label_path corner
1242 $$ = $1;
1243 $$->append($2);
1247 path:
1248 relative_path
1250 $$ = $1;
1252 /* The rest of these rules are a compatibility sop. */
1253 | ORDINAL LAST object_type relative_path
1255 lex_warning("`%1%2 last %3' in `with' argument ignored",
1256 $1, ordinal_postfix($1), object_type_name($3));
1257 $$ = $4;
1259 | LAST object_type relative_path
1261 lex_warning("`last %1' in `with' argument ignored",
1262 object_type_name($2));
1263 $$ = $3;
1265 | ORDINAL object_type relative_path
1267 lex_warning("`%1%2 %3' in `with' argument ignored",
1268 $1, ordinal_postfix($1), object_type_name($2));
1269 $$ = $3;
1271 | LABEL relative_path
1273 lex_warning("initial `%1' in `with' argument ignored", $1);
1274 a_delete $1;
1275 $$ = $2;
1279 corner:
1280 DOT_N
1281 { $$ = &object::north; }
1282 | DOT_E
1283 { $$ = &object::east; }
1284 | DOT_W
1285 { $$ = &object::west; }
1286 | DOT_S
1287 { $$ = &object::south; }
1288 | DOT_NE
1289 { $$ = &object::north_east; }
1290 | DOT_SE
1291 { $$ = &object:: south_east; }
1292 | DOT_NW
1293 { $$ = &object::north_west; }
1294 | DOT_SW
1295 { $$ = &object::south_west; }
1296 | DOT_C
1297 { $$ = &object::center; }
1298 | DOT_START
1299 { $$ = &object::start; }
1300 | DOT_END
1301 { $$ = &object::end; }
1302 | TOP
1303 { $$ = &object::north; }
1304 | BOTTOM
1305 { $$ = &object::south; }
1306 | LEFT
1307 { $$ = &object::west; }
1308 | RIGHT
1309 { $$ = &object::east; }
1310 | UPPER LEFT
1311 { $$ = &object::north_west; }
1312 | LOWER LEFT
1313 { $$ = &object::south_west; }
1314 | UPPER RIGHT
1315 { $$ = &object::north_east; }
1316 | LOWER RIGHT
1317 { $$ = &object::south_east; }
1318 | LEFT_CORNER
1319 { $$ = &object::west; }
1320 | RIGHT_CORNER
1321 { $$ = &object::east; }
1322 | UPPER LEFT_CORNER
1323 { $$ = &object::north_west; }
1324 | LOWER LEFT_CORNER
1325 { $$ = &object::south_west; }
1326 | UPPER RIGHT_CORNER
1327 { $$ = &object::north_east; }
1328 | LOWER RIGHT_CORNER
1329 { $$ = &object::south_east; }
1330 | CENTER
1331 { $$ = &object::center; }
1332 | START
1333 { $$ = &object::start; }
1334 | END
1335 { $$ = &object::end; }
1338 expr:
1339 VARIABLE
1341 if (!lookup_variable($1, & $$)) {
1342 lex_error("there is no variable `%1'", $1);
1343 YYABORT;
1345 a_delete $1;
1347 | NUMBER
1348 { $$ = $1; }
1349 | place DOT_X
1351 if ($1.obj != 0)
1352 $$ = $1.obj->origin().x;
1353 else
1354 $$ = $1.x;
1356 | place DOT_Y
1358 if ($1.obj != 0)
1359 $$ = $1.obj->origin().y;
1360 else
1361 $$ = $1.y;
1363 | place DOT_HT
1365 if ($1.obj != 0)
1366 $$ = $1.obj->height();
1367 else
1368 $$ = 0.0;
1370 | place DOT_WID
1372 if ($1.obj != 0)
1373 $$ = $1.obj->width();
1374 else
1375 $$ = 0.0;
1377 | place DOT_RAD
1379 if ($1.obj != 0)
1380 $$ = $1.obj->radius();
1381 else
1382 $$ = 0.0;
1384 | expr '+' expr
1385 { $$ = $1 + $3; }
1386 | expr '-' expr
1387 { $$ = $1 - $3; }
1388 | expr '*' expr
1389 { $$ = $1 * $3; }
1390 | expr '/' expr
1392 if ($3 == 0.0) {
1393 lex_error("division by zero");
1394 YYABORT;
1396 $$ = $1/$3;
1398 | expr '%' expr
1400 if ($3 == 0.0) {
1401 lex_error("modulus by zero");
1402 YYABORT;
1404 $$ = fmod($1, $3);
1406 | expr '^' expr
1408 errno = 0;
1409 $$ = pow($1, $3);
1410 if (errno == EDOM) {
1411 lex_error("arguments to `^' operator out of domain");
1412 YYABORT;
1414 if (errno == ERANGE) {
1415 lex_error("result of `^' operator out of range");
1416 YYABORT;
1419 | '-' expr %prec '!'
1420 { $$ = -$2; }
1421 | '(' any_expr ')'
1422 { $$ = $2; }
1423 | SIN '(' any_expr ')'
1425 errno = 0;
1426 $$ = sin($3);
1427 if (errno == ERANGE) {
1428 lex_error("sin result out of range");
1429 YYABORT;
1432 | COS '(' any_expr ')'
1434 errno = 0;
1435 $$ = cos($3);
1436 if (errno == ERANGE) {
1437 lex_error("cos result out of range");
1438 YYABORT;
1441 | ATAN2 '(' any_expr ',' any_expr ')'
1443 errno = 0;
1444 $$ = atan2($3, $5);
1445 if (errno == EDOM) {
1446 lex_error("atan2 argument out of domain");
1447 YYABORT;
1449 if (errno == ERANGE) {
1450 lex_error("atan2 result out of range");
1451 YYABORT;
1454 | LOG '(' any_expr ')'
1456 errno = 0;
1457 $$ = log10($3);
1458 if (errno == ERANGE) {
1459 lex_error("log result out of range");
1460 YYABORT;
1463 | EXP '(' any_expr ')'
1465 errno = 0;
1466 $$ = pow(10.0, $3);
1467 if (errno == ERANGE) {
1468 lex_error("exp result out of range");
1469 YYABORT;
1472 | SQRT '(' any_expr ')'
1474 errno = 0;
1475 $$ = sqrt($3);
1476 if (errno == EDOM) {
1477 lex_error("sqrt argument out of domain");
1478 YYABORT;
1481 | MAX '(' any_expr ',' any_expr ')'
1482 { $$ = $3 > $5 ? $3 : $5; }
1483 | MIN '(' any_expr ',' any_expr ')'
1484 { $$ = $3 < $5 ? $3 : $5; }
1485 | INT '(' any_expr ')'
1486 { $$ = floor($3); }
1487 | RAND '(' any_expr ')'
1488 { $$ = 1.0 + floor(((rand()&0x7fff)/double(0x7fff))*$3); }
1489 | RAND '(' ')'
1491 /* return a random number in the range [0,1) */
1492 /* portable, but not very random */
1493 $$ = (rand() & 0x7fff) / double(0x8000);
1495 | expr '<' expr
1496 { $$ = ($1 < $3); }
1497 | expr LESSEQUAL expr
1498 { $$ = ($1 <= $3); }
1499 | expr '>' expr
1500 { $$ = ($1 > $3); }
1501 | expr GREATEREQUAL expr
1502 { $$ = ($1 >= $3); }
1503 | expr EQUALEQUAL expr
1504 { $$ = ($1 == $3); }
1505 | expr NOTEQUAL expr
1506 { $$ = ($1 != $3); }
1507 | expr ANDAND expr
1508 { $$ = ($1 != 0.0 && $3 != 0.0); }
1509 | expr OROR expr
1510 { $$ = ($1 != 0.0 || $3 != 0.0); }
1511 | '!' expr
1512 { $$ = ($2 == 0.0); }
1518 /* bison defines const to be empty unless __STDC__ is defined, which it
1519 isn't under cfront */
1521 #ifdef const
1522 #undef const
1523 #endif
1525 static struct {
1526 const char *name;
1527 double val;
1528 int scaled; // non-zero if val should be multiplied by scale
1529 } defaults_table[] = {
1530 "arcrad", .25, 1,
1531 "arrowht", .1, 1,
1532 "arrowwid", .05, 1,
1533 "circlerad", .25, 1,
1534 "boxht", .5, 1,
1535 "boxwid", .75, 1,
1536 "boxrad", 0.0, 1,
1537 "dashwid", .05, 1,
1538 "ellipseht", .5, 1,
1539 "ellipsewid", .75, 1,
1540 "moveht", .5, 1,
1541 "movewid", .5, 1,
1542 "lineht", .5, 1,
1543 "linewid", .5, 1,
1544 "textht", 0.0, 1,
1545 "textwid", 0.0, 1,
1546 "scale", 1.0, 0,
1547 "linethick", -1.0, 0, // in points
1548 "fillval", .5, 0,
1549 "arrowhead", 1.0, 0,
1550 "maxpswid", 8.5, 0,
1551 "maxpsht", 11.0, 0,
1554 place *lookup_label(const char *label)
1556 saved_state *state = current_saved_state;
1557 PTABLE(place) *tbl = current_table;
1558 for (;;) {
1559 place *pl = tbl->lookup(label);
1560 if (pl)
1561 return pl;
1562 if (!state)
1563 return 0;
1564 tbl = state->tbl;
1565 state = state->prev;
1569 void define_label(const char *label, const place *pl)
1571 place *p = new place;
1572 *p = *pl;
1573 current_table->define(label, p);
1576 int lookup_variable(const char *name, double *val)
1578 place *pl = lookup_label(name);
1579 if (pl) {
1580 *val = pl->x;
1581 return 1;
1583 return 0;
1586 void define_variable(const char *name, double val)
1588 place *p = new place;
1589 p->obj = 0;
1590 p->x = val;
1591 p->y = 0.0;
1592 current_table->define(name, p);
1593 if (strcmp(name, "scale") == 0) {
1594 // When the scale changes, reset all scaled pre-defined variables to
1595 // their default values.
1596 for (int i = 0; i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
1597 if (defaults_table[i].scaled)
1598 define_variable(defaults_table[i].name, val*defaults_table[i].val);
1602 // called once only (not once per parse)
1604 void parse_init()
1606 current_direction = RIGHT_DIRECTION;
1607 current_position.x = 0.0;
1608 current_position.y = 0.0;
1609 // This resets everything to its default value.
1610 reset_all();
1613 void reset(const char *nm)
1615 for (int i = 0; i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
1616 if (strcmp(nm, defaults_table[i].name) == 0) {
1617 double val = defaults_table[i].val;
1618 if (defaults_table[i].scaled) {
1619 double scale;
1620 lookup_variable("scale", &scale);
1621 val *= scale;
1623 define_variable(defaults_table[i].name, val);
1624 return;
1626 lex_error("`%1' is not a predefined variable", nm);
1629 void reset_all()
1631 // We only have to explicitly reset the pre-defined variables that
1632 // aren't scaled because `scale' is not scaled, and changing the
1633 // value of `scale' will reset all the pre-defined variables that
1634 // are scaled.
1635 for (int i = 0; i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++)
1636 if (!defaults_table[i].scaled)
1637 define_variable(defaults_table[i].name, defaults_table[i].val);
1640 // called after each parse
1642 void parse_cleanup()
1644 while (current_saved_state != 0) {
1645 delete current_table;
1646 current_table = current_saved_state->tbl;
1647 saved_state *tem = current_saved_state;
1648 current_saved_state = current_saved_state->prev;
1649 delete tem;
1651 assert(current_table == &top_table);
1652 PTABLE_ITERATOR(place) iter(current_table);
1653 const char *key;
1654 place *pl;
1655 while (iter.next(&key, &pl))
1656 if (pl->obj != 0) {
1657 position pos = pl->obj->origin();
1658 pl->obj = 0;
1659 pl->x = pos.x;
1660 pl->y = pos.y;
1662 while (olist.head != 0) {
1663 object *tem = olist.head;
1664 olist.head = olist.head->next;
1665 delete tem;
1667 olist.tail = 0;
1668 current_direction = RIGHT_DIRECTION;
1669 current_position.x = 0.0;
1670 current_position.y = 0.0;
1673 const char *ordinal_postfix(int n)
1675 if (n < 10 || n > 20)
1676 switch (n % 10) {
1677 case 1:
1678 return "st";
1679 case 2:
1680 return "nd";
1681 case 3:
1682 return "rd";
1684 return "th";
1687 const char *object_type_name(object_type type)
1689 switch (type) {
1690 case BOX_OBJECT:
1691 return "box";
1692 case CIRCLE_OBJECT:
1693 return "circle";
1694 case ELLIPSE_OBJECT:
1695 return "ellipse";
1696 case ARC_OBJECT:
1697 return "arc";
1698 case SPLINE_OBJECT:
1699 return "spline";
1700 case LINE_OBJECT:
1701 return "line";
1702 case ARROW_OBJECT:
1703 return "arrow";
1704 case MOVE_OBJECT:
1705 return "move";
1706 case TEXT_OBJECT:
1707 return "\"\"";
1708 case BLOCK_OBJECT:
1709 return "[]";
1710 case OTHER_OBJECT:
1711 case MARK_OBJECT:
1712 default:
1713 break;
1715 return "object";
1718 static char sprintf_buf[1024];
1720 char *format_number(const char *form, double n)
1722 if (form == 0)
1723 form = "%g";
1724 else {
1725 // this is a fairly feeble attempt at validation of the format
1726 int nspecs = 0;
1727 for (const char *p = form; *p != '\0'; p++)
1728 if (*p == '%') {
1729 if (p[1] == '%')
1730 p++;
1731 else
1732 nspecs++;
1734 if (nspecs > 1) {
1735 lex_error("bad format `%1'", form);
1736 return strsave(form);
1739 sprintf(sprintf_buf, form, n);
1740 return strsave(sprintf_buf);
1743 char *do_sprintf(const char *form, const double *v, int nv)
1745 string result;
1746 int i = 0;
1747 string one_format;
1748 while (*form) {
1749 if (*form == '%') {
1750 one_format += *form++;
1751 for (; *form != '\0' && strchr("#-+ 0123456789.", *form) != 0; form++)
1752 one_format += *form;
1753 if (*form == '\0' || strchr("eEfgG%", *form) == 0) {
1754 lex_error("bad sprintf format");
1755 result += one_format;
1756 result += form;
1757 break;
1759 if (*form == '%') {
1760 one_format += *form++;
1761 one_format += '\0';
1762 sprintf(sprintf_buf, one_format.contents());
1764 else {
1765 if (i >= nv) {
1766 lex_error("too few arguments to sprintf");
1767 result += one_format;
1768 result += form;
1769 break;
1771 one_format += *form++;
1772 one_format += '\0';
1773 sprintf(sprintf_buf, one_format.contents(), v[i++]);
1775 one_format.clear();
1776 result += sprintf_buf;
1778 else
1779 result += *form++;
1781 result += '\0';
1782 return strsave(result.contents());