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