diff options
Diffstat (limited to 'src/preproc/pic/pic.ypp')
-rw-r--r-- | src/preproc/pic/pic.ypp | 1957 |
1 files changed, 1957 insertions, 0 deletions
diff --git a/src/preproc/pic/pic.ypp b/src/preproc/pic/pic.ypp new file mode 100644 index 0000000..b2fa6dc --- /dev/null +++ b/src/preproc/pic/pic.ypp @@ -0,0 +1,1957 @@ +/* Copyright (C) 1989-2022 Free Software Foundation, Inc. + Written by James Clark (jjc@jclark.com) + +This file is part of groff. + +groff is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or +(at your option) any later version. + +groff is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +%{ +#include "pic.h" +#include "ptable.h" +#include "object.h" + +extern int delim_flag; +extern void copy_rest_thru(const char *, const char *); +extern void copy_file_thru(const char *, const char *, const char *); +extern void push_body(const char *); +extern void do_for(char *var, double from, double to, + int by_is_multiplicative, double by, char *body); +extern void do_lookahead(); + +/* Maximum number of characters produced by printf("%g") */ +#define GDIGITS 14 + +int yylex(); +void yyerror(const char *); + +void reset(const char *nm); +void reset_all(); + +place *lookup_label(const char *); +void define_label(const char *label, const place *pl); + +direction current_direction; +position current_position; + +implement_ptable(place) + +PTABLE(place) top_table; + +PTABLE(place) *current_table = &top_table; +saved_state *current_saved_state = 0; + +object_list olist; + +const char *ordinal_postfix(int n); +const char *object_type_name(object_type type); +char *format_number(const char *fmt, double n); +char *do_sprintf(const char *fmt, const double *v, int nv); + +%} + +%expect 2 + +%union { + char *str; + int n; + double x; + struct { double x, y; } pair; + struct { double x; char *body; } if_data; + struct { char *str; const char *filename; int lineno; } lstr; + struct { double *v; int nv; int maxv; } dv; + struct { double val; int is_multiplicative; } by; + place pl; + object *obj; + corner crn; + path *pth; + object_spec *spec; + saved_state *pstate; + graphics_state state; + object_type obtype; +} + +%token <str> LABEL +%token <str> VARIABLE +%token <x> NUMBER +%token <lstr> TEXT +%token <lstr> COMMAND_LINE +%token <str> DELIMITED +%token <n> ORDINAL +%token TH +%token LEFT_ARROW_HEAD +%token RIGHT_ARROW_HEAD +%token DOUBLE_ARROW_HEAD +%token LAST +%token BOX +%token CIRCLE +%token ELLIPSE +%token ARC +%token LINE +%token ARROW +%token MOVE +%token SPLINE +%token HEIGHT +%token RADIUS +%token FIGNAME +%token WIDTH +%token DIAMETER +%token UP +%token DOWN +%token RIGHT +%token LEFT +%token FROM +%token TO +%token AT +%token WITH +%token BY +%token THEN +%token SOLID +%token DOTTED +%token DASHED +%token CHOP +%token SAME +%token INVISIBLE +%token LJUST +%token RJUST +%token ABOVE +%token BELOW +%token OF +%token THE +%token WAY +%token BETWEEN +%token AND +%token HERE +%token DOT_N +%token DOT_E +%token DOT_W +%token DOT_S +%token DOT_NE +%token DOT_SE +%token DOT_NW +%token DOT_SW +%token DOT_C +%token DOT_START +%token DOT_END +%token DOT_X +%token DOT_Y +%token DOT_HT +%token DOT_WID +%token DOT_RAD +%token SIN +%token COS +%token ATAN2 +%token LOG +%token EXP +%token SQRT +%token K_MAX +%token K_MIN +%token INT +%token RAND +%token SRAND +%token COPY +%token THRU +%token TOP +%token BOTTOM +%token UPPER +%token LOWER +%token SH +%token PRINT +%token CW +%token CCW +%token FOR +%token DO +%token IF +%token ELSE +%token ANDAND +%token OROR +%token NOTEQUAL +%token EQUALEQUAL +%token LESSEQUAL +%token GREATEREQUAL +%token LEFT_CORNER +%token RIGHT_CORNER +%token NORTH +%token SOUTH +%token EAST +%token WEST +%token CENTER +%token END +%token START +%token RESET +%token UNTIL +%token PLOT +%token THICKNESS +%token FILL +%token COLORED +%token OUTLINED +%token SHADED +%token XSLANTED +%token YSLANTED +%token ALIGNED +%token SPRINTF +%token COMMAND + +%token DEFINE +%token UNDEF + +%left '.' + +/* this ensures that plot 17 "%g" parses as (plot 17 "%g") */ +%left PLOT +%left TEXT SPRINTF + +/* give text adjustments higher precedence than TEXT, so that +box "foo" above ljust == box ("foo" above ljust) +*/ + +%left LJUST RJUST ABOVE BELOW + +%left LEFT RIGHT +/* Give attributes that take an optional expression a higher +precedence than left and right, so that, e.g., 'line chop left' +parses properly. */ +%left CHOP SOLID DASHED DOTTED UP DOWN FILL COLORED OUTLINED +%left XSLANTED YSLANTED +%left LABEL + +%left VARIABLE NUMBER '(' SIN COS ATAN2 LOG EXP SQRT K_MAX K_MIN INT RAND SRAND LAST +%left ORDINAL HERE '`' + +%left BOX CIRCLE ELLIPSE ARC LINE ARROW SPLINE '[' + +/* these need to be lower than '-' */ +%left HEIGHT RADIUS WIDTH DIAMETER FROM TO AT THICKNESS + +/* these must have higher precedence than CHOP so that 'label %prec CHOP' +works */ +%left DOT_N DOT_E DOT_W DOT_S DOT_NE DOT_SE DOT_NW DOT_SW DOT_C +%left DOT_START DOT_END TOP BOTTOM LEFT_CORNER RIGHT_CORNER +%left UPPER LOWER NORTH SOUTH EAST WEST CENTER START END + +%left ',' +%left OROR +%left ANDAND +%left EQUALEQUAL NOTEQUAL +%left '<' '>' LESSEQUAL GREATEREQUAL + +%left BETWEEN OF +%left AND + +%left '+' '-' +%left '*' '/' '%' +%right '!' +%right '^' + +%type <x> expr expr_lower_than expr_not_lower_than any_expr text_expr +%type <by> optional_by +%type <pair> expr_pair position_not_place +%type <if_data> simple_if +%type <obj> nth_primitive +%type <crn> corner +%type <pth> path label_path relative_path +%type <pl> place label element element_list middle_element_list +%type <spec> object_spec +%type <pair> position +%type <obtype> object_type +%type <n> optional_ordinal_last ordinal +%type <str> macro_name until +%type <dv> sprintf_args +%type <lstr> text print_args print_arg + +%% + +top: + optional_separator + | element_list + { + if (olist.head) + print_picture(olist.head); + } + ; + + +element_list: + optional_separator middle_element_list optional_separator + { $$ = $2; } + ; + +middle_element_list: + element + { $$ = $1; } + | middle_element_list separator element + { $$ = $1; } + ; + +optional_separator: + /* empty */ + | separator + ; + +separator: + ';' + | separator ';' + ; + +placeless_element: + FIGNAME '=' macro_name + { + delete[] graphname; + graphname = new char[strlen($3) + 1]; + strcpy(graphname, $3); + delete[] $3; + } + | + VARIABLE '=' any_expr + { + define_variable($1, $3); + free($1); + } + | VARIABLE ':' '=' any_expr + { + place *p = lookup_label($1); + if (!p) { + lex_error("variable '%1' not defined", $1); + YYABORT; + } + p->obj = 0; + p->x = $4; + p->y = 0.0; + free($1); + } + | UP + { current_direction = UP_DIRECTION; } + | DOWN + { current_direction = DOWN_DIRECTION; } + | LEFT + { current_direction = LEFT_DIRECTION; } + | RIGHT + { current_direction = RIGHT_DIRECTION; } + | COMMAND_LINE + { + olist.append(make_command_object($1.str, $1.filename, + $1.lineno)); + } + | COMMAND print_args + { + olist.append(make_command_object($2.str, $2.filename, + $2.lineno)); + } + | PRINT print_args + { + fprintf(stderr, "%s\n", $2.str); + delete[] $2.str; + fflush(stderr); + } + | SH + { delim_flag = 1; } + DELIMITED + { + delim_flag = 0; + if (safer_flag) + lex_error("unsafe to run command '%1'; ignoring", + $3); + else { + int retval = system($3); + if (retval < 0) + lex_error("error running command '%1': system()" + " returned %2", $3, retval); + } + delete[] $3; + } + | COPY TEXT + { + if (yychar < 0) + do_lookahead(); + do_copy($2.str); + // do not delete the filename + } + | COPY TEXT THRU + { delim_flag = 2; } + DELIMITED + { delim_flag = 0; } + until + { + if (yychar < 0) + do_lookahead(); + copy_file_thru($2.str, $5, $7); + // do not delete the filename + delete[] $5; + delete[] $7; + } + | COPY THRU + { delim_flag = 2; } + DELIMITED + { delim_flag = 0; } + until + { + if (yychar < 0) + do_lookahead(); + copy_rest_thru($4, $6); + delete[] $4; + delete[] $6; + } + | FOR VARIABLE '=' expr TO expr optional_by DO + { delim_flag = 1; } + DELIMITED + { + delim_flag = 0; + if (yychar < 0) + do_lookahead(); + do_for($2, $4, $6, $7.is_multiplicative, $7.val, $10); + } + | simple_if + { + if (yychar < 0) + do_lookahead(); + if ($1.x != 0.0) + push_body($1.body); + delete[] $1.body; + } + | simple_if ELSE + { delim_flag = 1; } + DELIMITED + { + delim_flag = 0; + if (yychar < 0) + do_lookahead(); + if ($1.x != 0.0) + push_body($1.body); + else + push_body($4); + free($1.body); + free($4); + } + | reset_variables + | RESET + { define_variable("scale", 1.0); } + ; + +macro_name: + VARIABLE + | LABEL + ; + +reset_variables: + RESET VARIABLE + { + reset($2); + delete[] $2; + } + | reset_variables VARIABLE + { + reset($2); + delete[] $2; + } + | reset_variables ',' VARIABLE + { + reset($3); + delete[] $3; + } + ; + +print_args: + print_arg + { $$ = $1; } + | print_args print_arg + { + $$.str = new char[strlen($1.str) + strlen($2.str) + 1]; + strcpy($$.str, $1.str); + strcat($$.str, $2.str); + delete[] $1.str; + delete[] $2.str; + if ($1.filename) { + $$.filename = $1.filename; + $$.lineno = $1.lineno; + } + else if ($2.filename) { + $$.filename = $2.filename; + $$.lineno = $2.lineno; + } + } + ; + +print_arg: + expr %prec ',' + { + $$.str = new char[GDIGITS + 1]; + sprintf($$.str, "%g", $1); + $$.filename = 0; + $$.lineno = 0; + } + | text + { $$ = $1; } + | position %prec ',' + { + $$.str = new char[GDIGITS + 2 + GDIGITS + 1]; + sprintf($$.str, "%g, %g", $1.x, $1.y); + $$.filename = 0; + $$.lineno = 0; + } + ; + +simple_if: + IF any_expr THEN + { delim_flag = 1; } + DELIMITED + { + delim_flag = 0; + $$.x = $2; + $$.body = $5; + } + ; + +until: + /* empty */ + { $$ = 0; } + | UNTIL TEXT + { $$ = $2.str; } + ; + +any_expr: + expr + { $$ = $1; } + | text_expr + { $$ = $1; } + ; + +text_expr: + text EQUALEQUAL text + { + $$ = strcmp($1.str, $3.str) == 0; + delete[] $1.str; + delete[] $3.str; + } + | text NOTEQUAL text + { + $$ = strcmp($1.str, $3.str) != 0; + delete[] $1.str; + delete[] $3.str; + } + | text_expr ANDAND text_expr + { $$ = ($1 != 0.0 && $3 != 0.0); } + | text_expr ANDAND expr + { $$ = ($1 != 0.0 && $3 != 0.0); } + | expr ANDAND text_expr + { $$ = ($1 != 0.0 && $3 != 0.0); } + | text_expr OROR text_expr + { $$ = ($1 != 0.0 || $3 != 0.0); } + | text_expr OROR expr + { $$ = ($1 != 0.0 || $3 != 0.0); } + | expr OROR text_expr + { $$ = ($1 != 0.0 || $3 != 0.0); } + | '!' text_expr + { $$ = ($2 == 0.0); } + ; + + +optional_by: + /* empty */ + { + $$.val = 1.0; + $$.is_multiplicative = 0; + } + | BY expr + { + $$.val = $2; + $$.is_multiplicative = 0; + } + | BY '*' expr + { + $$.val = $3; + $$.is_multiplicative = 1; + } + ; + +element: + object_spec + { + $$.obj = $1->make_object(¤t_position, + ¤t_direction); + if ($$.obj == 0) + YYABORT; + delete $1; + if ($$.obj) + olist.append($$.obj); + else { + $$.x = current_position.x; + $$.y = current_position.y; + } + } + | LABEL ':' optional_separator element + { + $$ = $4; + define_label($1, & $$); + free($1); + } + | LABEL ':' optional_separator position_not_place + { + $$.obj = 0; + $$.x = $4.x; + $$.y = $4.y; + define_label($1, & $$); + free($1); + } + | LABEL ':' optional_separator place + { + $$ = $4; + define_label($1, & $$); + free($1); + } + | '{' + { + $<state>$.x = current_position.x; + $<state>$.y = current_position.y; + $<state>$.dir = current_direction; + } + element_list '}' + { + current_position.x = $<state>2.x; + current_position.y = $<state>2.y; + current_direction = $<state>2.dir; + } + optional_element + { + $$ = $3; + } + | placeless_element + { + $$.obj = 0; + $$.x = current_position.x; + $$.y = current_position.y; + } + ; + +optional_element: + /* empty */ + {} + | element + {} + ; + +object_spec: + BOX + { $$ = new object_spec(BOX_OBJECT); } + | CIRCLE + { $$ = new object_spec(CIRCLE_OBJECT); } + | ELLIPSE + { $$ = new object_spec(ELLIPSE_OBJECT); } + | ARC + { + $$ = new object_spec(ARC_OBJECT); + $$->dir = current_direction; + } + | LINE + { + $$ = new object_spec(LINE_OBJECT); + lookup_variable("lineht", & $$->segment_height); + lookup_variable("linewid", & $$->segment_width); + $$->dir = current_direction; + } + | ARROW + { + $$ = new object_spec(ARROW_OBJECT); + lookup_variable("lineht", & $$->segment_height); + lookup_variable("linewid", & $$->segment_width); + $$->dir = current_direction; + } + | MOVE + { + $$ = new object_spec(MOVE_OBJECT); + lookup_variable("moveht", & $$->segment_height); + lookup_variable("movewid", & $$->segment_width); + $$->dir = current_direction; + } + | SPLINE + { + $$ = new object_spec(SPLINE_OBJECT); + lookup_variable("lineht", & $$->segment_height); + lookup_variable("linewid", & $$->segment_width); + $$->dir = current_direction; + } + | text %prec TEXT + { + $$ = new object_spec(TEXT_OBJECT); + $$->text = new text_item($1.str, $1.filename, $1.lineno); + } + | PLOT expr + { + lex_warning("'plot' is deprecated; use 'sprintf'" + " instead"); + $$ = new object_spec(TEXT_OBJECT); + $$->text = new text_item(format_number(0, $2), 0, -1); + } + | PLOT expr text + { + $$ = new object_spec(TEXT_OBJECT); + $$->text = new text_item(format_number($3.str, $2), + $3.filename, $3.lineno); + delete[] $3.str; + } + | '[' + { + saved_state *p = new saved_state; + $<pstate>$ = p; + p->x = current_position.x; + p->y = current_position.y; + p->dir = current_direction; + p->tbl = current_table; + p->prev = current_saved_state; + current_position.x = 0.0; + current_position.y = 0.0; + current_table = new PTABLE(place); + current_saved_state = p; + olist.append(make_mark_object()); + } + element_list ']' + { + current_position.x = $<pstate>2->x; + current_position.y = $<pstate>2->y; + current_direction = $<pstate>2->dir; + $$ = new object_spec(BLOCK_OBJECT); + olist.wrap_up_block(& $$->oblist); + $$->tbl = current_table; + current_table = $<pstate>2->tbl; + current_saved_state = $<pstate>2->prev; + delete $<pstate>2; + } + | object_spec HEIGHT expr + { + $$ = $1; + $$->height = $3; + $$->flags |= HAS_HEIGHT; + } + | object_spec RADIUS expr + { + $$ = $1; + $$->radius = $3; + $$->flags |= HAS_RADIUS; + } + | object_spec WIDTH expr + { + $$ = $1; + $$->width = $3; + $$->flags |= HAS_WIDTH; + } + | object_spec DIAMETER expr + { + $$ = $1; + $$->radius = $3/2.0; + $$->flags |= HAS_RADIUS; + } + | object_spec expr %prec HEIGHT + { + $$ = $1; + $$->flags |= HAS_SEGMENT; + switch ($$->dir) { + case UP_DIRECTION: + $$->segment_pos.y += $2; + break; + case DOWN_DIRECTION: + $$->segment_pos.y -= $2; + break; + case RIGHT_DIRECTION: + $$->segment_pos.x += $2; + break; + case LEFT_DIRECTION: + $$->segment_pos.x -= $2; + break; + } + } + | object_spec UP + { + $$ = $1; + $$->dir = UP_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.y += $$->segment_height; + } + | object_spec UP expr + { + $$ = $1; + $$->dir = UP_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.y += $3; + } + | object_spec DOWN + { + $$ = $1; + $$->dir = DOWN_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.y -= $$->segment_height; + } + | object_spec DOWN expr + { + $$ = $1; + $$->dir = DOWN_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.y -= $3; + } + | object_spec RIGHT + { + $$ = $1; + $$->dir = RIGHT_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x += $$->segment_width; + } + | object_spec RIGHT expr + { + $$ = $1; + $$->dir = RIGHT_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x += $3; + } + | object_spec LEFT + { + $$ = $1; + $$->dir = LEFT_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x -= $$->segment_width; + } + | object_spec LEFT expr + { + $$ = $1; + $$->dir = LEFT_DIRECTION; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x -= $3; + } + | object_spec FROM position + { + $$ = $1; + $$->flags |= HAS_FROM; + $$->from.x = $3.x; + $$->from.y = $3.y; + } + | object_spec TO position + { + $$ = $1; + if ($$->flags & HAS_SEGMENT) + $$->segment_list = new segment($$->segment_pos, + $$->segment_is_absolute, + $$->segment_list); + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x = $3.x; + $$->segment_pos.y = $3.y; + $$->segment_is_absolute = 1; + $$->flags |= HAS_TO; + $$->to.x = $3.x; + $$->to.y = $3.y; + } + | object_spec AT position + { + $$ = $1; + $$->flags |= HAS_AT; + $$->at.x = $3.x; + $$->at.y = $3.y; + if ($$->type != ARC_OBJECT) { + $$->flags |= HAS_FROM; + $$->from.x = $3.x; + $$->from.y = $3.y; + } + } + | object_spec WITH path + { + $$ = $1; + $$->flags |= HAS_WITH; + $$->with = $3; + } + | object_spec WITH position %prec ',' + { + $$ = $1; + $$->flags |= HAS_WITH; + position pos; + pos.x = $3.x; + pos.y = $3.y; + $$->with = new path(pos); + } + | object_spec BY expr_pair + { + $$ = $1; + $$->flags |= HAS_SEGMENT; + $$->segment_pos.x += $3.x; + $$->segment_pos.y += $3.y; + } + | object_spec THEN + { + $$ = $1; + if (!($$->flags & HAS_SEGMENT)) + switch ($$->dir) { + case UP_DIRECTION: + $$->segment_pos.y += $$->segment_width; + break; + case DOWN_DIRECTION: + $$->segment_pos.y -= $$->segment_width; + break; + case RIGHT_DIRECTION: + $$->segment_pos.x += $$->segment_width; + break; + case LEFT_DIRECTION: + $$->segment_pos.x -= $$->segment_width; + break; + } + $$->segment_list = new segment($$->segment_pos, + $$->segment_is_absolute, + $$->segment_list); + $$->flags &= ~HAS_SEGMENT; + $$->segment_pos.x = $$->segment_pos.y = 0.0; + $$->segment_is_absolute = 0; + } + | object_spec SOLID + { + $$ = $1; // nothing + } + | object_spec DOTTED + { + $$ = $1; + $$->flags |= IS_DOTTED; + lookup_variable("dashwid", & $$->dash_width); + } + | object_spec DOTTED expr + { + $$ = $1; + $$->flags |= IS_DOTTED; + $$->dash_width = $3; + } + | object_spec DASHED + { + $$ = $1; + $$->flags |= IS_DASHED; + lookup_variable("dashwid", & $$->dash_width); + } + | object_spec DASHED expr + { + $$ = $1; + $$->flags |= IS_DASHED; + $$->dash_width = $3; + } + | object_spec FILL + { + $$ = $1; + $$->flags |= IS_DEFAULT_FILLED; + } + | object_spec FILL expr + { + $$ = $1; + $$->flags |= IS_FILLED; + $$->fill = $3; + } + | object_spec XSLANTED expr + { + $$ = $1; + $$->flags |= IS_XSLANTED; + $$->xslanted = $3; + } + | object_spec YSLANTED expr + { + $$ = $1; + $$->flags |= IS_YSLANTED; + $$->yslanted = $3; + } + | object_spec SHADED text + { + $$ = $1; + $$->flags |= (IS_SHADED | IS_FILLED); + $$->shaded = new char[strlen($3.str)+1]; + strcpy($$->shaded, $3.str); + } + | object_spec COLORED text + { + $$ = $1; + $$->flags |= (IS_SHADED | IS_OUTLINED | IS_FILLED); + $$->shaded = new char[strlen($3.str)+1]; + strcpy($$->shaded, $3.str); + $$->outlined = new char[strlen($3.str)+1]; + strcpy($$->outlined, $3.str); + } + | object_spec OUTLINED text + { + $$ = $1; + $$->flags |= IS_OUTLINED; + $$->outlined = new char[strlen($3.str)+1]; + strcpy($$->outlined, $3.str); + } + | object_spec CHOP + { + $$ = $1; + // line chop chop means line chop 0 chop 0 + if ($$->flags & IS_DEFAULT_CHOPPED) { + $$->flags |= IS_CHOPPED; + $$->flags &= ~IS_DEFAULT_CHOPPED; + $$->start_chop = $$->end_chop = 0.0; + } + else if ($$->flags & IS_CHOPPED) { + $$->end_chop = 0.0; + } + else { + $$->flags |= IS_DEFAULT_CHOPPED; + } + } + | object_spec CHOP expr + { + $$ = $1; + if ($$->flags & IS_DEFAULT_CHOPPED) { + $$->flags |= IS_CHOPPED; + $$->flags &= ~IS_DEFAULT_CHOPPED; + $$->start_chop = 0.0; + $$->end_chop = $3; + } + else if ($$->flags & IS_CHOPPED) { + $$->end_chop = $3; + } + else { + $$->start_chop = $$->end_chop = $3; + $$->flags |= IS_CHOPPED; + } + } + | object_spec SAME + { + $$ = $1; + $$->flags |= IS_SAME; + } + | object_spec INVISIBLE + { + $$ = $1; + $$->flags |= IS_INVISIBLE; + } + | object_spec LEFT_ARROW_HEAD + { + $$ = $1; + $$->flags |= HAS_LEFT_ARROW_HEAD; + } + | object_spec RIGHT_ARROW_HEAD + { + $$ = $1; + $$->flags |= HAS_RIGHT_ARROW_HEAD; + } + | object_spec DOUBLE_ARROW_HEAD + { + $$ = $1; + $$->flags |= (HAS_LEFT_ARROW_HEAD|HAS_RIGHT_ARROW_HEAD); + } + | object_spec CW + { + $$ = $1; + $$->flags |= IS_CLOCKWISE; + } + | object_spec CCW + { + $$ = $1; + $$->flags &= ~IS_CLOCKWISE; + } + | object_spec text %prec TEXT + { + $$ = $1; + text_item **p; + for (p = & $$->text; *p; p = &(*p)->next) + ; + *p = new text_item($2.str, $2.filename, $2.lineno); + } + | object_spec LJUST + { + $$ = $1; + if ($$->text) { + text_item *p; + for (p = $$->text; p->next; p = p->next) + ; + p->adj.h = LEFT_ADJUST; + } + } + | object_spec RJUST + { + $$ = $1; + if ($$->text) { + text_item *p; + for (p = $$->text; p->next; p = p->next) + ; + p->adj.h = RIGHT_ADJUST; + } + } + | object_spec ABOVE + { + $$ = $1; + if ($$->text) { + text_item *p; + for (p = $$->text; p->next; p = p->next) + ; + p->adj.v = ABOVE_ADJUST; + } + } + | object_spec BELOW + { + $$ = $1; + if ($$->text) { + text_item *p; + for (p = $$->text; p->next; p = p->next) + ; + p->adj.v = BELOW_ADJUST; + } + } + | object_spec THICKNESS expr + { + $$ = $1; + $$->flags |= HAS_THICKNESS; + $$->thickness = $3; + } + | object_spec ALIGNED + { + $$ = $1; + $$->flags |= IS_ALIGNED; + } + ; + +text: + TEXT + { $$ = $1; } + | SPRINTF '(' TEXT sprintf_args ')' + { + $$.filename = $3.filename; + $$.lineno = $3.lineno; + $$.str = do_sprintf($3.str, $4.v, $4.nv); + delete[] $4.v; + free($3.str); + } + ; + +sprintf_args: + /* empty */ + { + $$.v = 0; + $$.nv = 0; + $$.maxv = 0; + } + | sprintf_args ',' expr + { + $$ = $1; + if ($$.nv >= $$.maxv) { + if ($$.nv == 0) { + $$.v = new double[4]; + $$.maxv = 4; + } + else { + double *oldv = $$.v; + $$.maxv *= 2; +#if 0 + $$.v = new double[$$.maxv]; + memcpy($$.v, oldv, $$.nv*sizeof(double)); +#else + // workaround for bug in Compaq C++ V6.5-033 + // for Compaq Tru64 UNIX V5.1A (Rev. 1885) + double *foo = new double[$$.maxv]; + memcpy(foo, oldv, $$.nv*sizeof(double)); + $$.v = foo; +#endif + delete[] oldv; + } + } + $$.v[$$.nv] = $3; + $$.nv += 1; + } + ; + +position: + position_not_place + { $$ = $1; } + | place + { + position pos = $1; + $$.x = pos.x; + $$.y = pos.y; + } + | '(' place ')' + { + position pos = $2; + $$.x = pos.x; + $$.y = pos.y; + } + ; + +position_not_place: + expr_pair + { $$ = $1; } + | position '+' expr_pair + { + $$.x = $1.x + $3.x; + $$.y = $1.y + $3.y; + } + | '(' position '+' expr_pair ')' + { + $$.x = $2.x + $4.x; + $$.y = $2.y + $4.y; + } + | position '-' expr_pair + { + $$.x = $1.x - $3.x; + $$.y = $1.y - $3.y; + } + | '(' position '-' expr_pair ')' + { + $$.x = $2.x - $4.x; + $$.y = $2.y - $4.y; + } + | '(' position ',' position ')' + { + $$.x = $2.x; + $$.y = $4.y; + } + | expr between position AND position + { + $$.x = (1.0 - $1)*$3.x + $1*$5.x; + $$.y = (1.0 - $1)*$3.y + $1*$5.y; + } + | '(' expr between position AND position ')' + { + $$.x = (1.0 - $2)*$4.x + $2*$6.x; + $$.y = (1.0 - $2)*$4.y + $2*$6.y; + } + /* the next two rules cause harmless shift/reduce warnings */ + | expr_not_lower_than '<' position ',' position '>' + { + $$.x = (1.0 - $1)*$3.x + $1*$5.x; + $$.y = (1.0 - $1)*$3.y + $1*$5.y; + } + | '(' expr_not_lower_than '<' position ',' position '>' ')' + { + $$.x = (1.0 - $2)*$4.x + $2*$6.x; + $$.y = (1.0 - $2)*$4.y + $2*$6.y; + } + ; + +between: + BETWEEN + | OF THE WAY BETWEEN + ; + +expr_pair: + expr ',' expr + { + $$.x = $1; + $$.y = $3; + } + | '(' expr_pair ')' + { $$ = $2; } + ; + +place: + /* line at A left == line (at A) left */ + label %prec CHOP + { $$ = $1; } + | label corner + { + path pth($2); + if (!pth.follow($1, & $$)) + YYABORT; + } + | corner label + { + path pth($1); + if (!pth.follow($2, & $$)) + YYABORT; + } + | corner OF label + { + path pth($1); + if (!pth.follow($3, & $$)) + YYABORT; + } + | HERE + { + $$.x = current_position.x; + $$.y = current_position.y; + $$.obj = 0; + } + ; + +label: + LABEL + { + place *p = lookup_label($1); + if (!p) { + lex_error("there is no place '%1'", $1); + YYABORT; + } + $$ = *p; + free($1); + } + | nth_primitive + { $$.obj = $1; } + | label '.' LABEL + { + path pth($3); + if (!pth.follow($1, & $$)) + YYABORT; + } + ; + +ordinal: + ORDINAL + { $$ = $1; } + | '`' any_expr TH + { + // XXX Check for overflow (and non-integers?). + $$ = (int)$2; + } + ; + +optional_ordinal_last: + LAST + { $$ = 1; } + | ordinal LAST + { $$ = $1; } + ; + +nth_primitive: + ordinal object_type + { + int count = 0; + object *p; + for (p = olist.head; p != 0; p = p->next) + if (p->type() == $2 && ++count == $1) { + $$ = p; + break; + } + if (p == 0) { + lex_error("there is no %1%2 %3", $1, ordinal_postfix($1), + object_type_name($2)); + YYABORT; + } + } + | optional_ordinal_last object_type + { + int count = 0; + object *p; + for (p = olist.tail; p != 0; p = p->prev) + if (p->type() == $2 && ++count == $1) { + $$ = p; + break; + } + if (p == 0) { + lex_error("there is no %1%2 last %3", $1, + ordinal_postfix($1), object_type_name($2)); + YYABORT; + } + } + ; + +object_type: + BOX + { $$ = BOX_OBJECT; } + | CIRCLE + { $$ = CIRCLE_OBJECT; } + | ELLIPSE + { $$ = ELLIPSE_OBJECT; } + | ARC + { $$ = ARC_OBJECT; } + | LINE + { $$ = LINE_OBJECT; } + | ARROW + { $$ = ARROW_OBJECT; } + | SPLINE + { $$ = SPLINE_OBJECT; } + | '[' ']' + { $$ = BLOCK_OBJECT; } + | TEXT + { $$ = TEXT_OBJECT; } + ; + +label_path: + '.' LABEL + { $$ = new path($2); } + | label_path '.' LABEL + { + $$ = $1; + $$->append($3); + } + ; + +relative_path: + corner %prec CHOP + { $$ = new path($1); } + /* give this a lower precedence than LEFT and RIGHT so that + [A: box] with .A left == [A: box] with (.A left) */ + | label_path %prec TEXT + { $$ = $1; } + | label_path corner + { + $$ = $1; + $$->append($2); + } + ; + +path: + relative_path + { $$ = $1; } + | '(' relative_path ',' relative_path ')' + { + $$ = $2; + $$->set_ypath($4); + } + /* The rest of these rules are a compatibility sop. */ + | ORDINAL LAST object_type relative_path + { + lex_warning("'%1%2 last %3' in 'with' argument ignored", + $1, ordinal_postfix($1), object_type_name($3)); + $$ = $4; + } + | LAST object_type relative_path + { + lex_warning("'last %1' in 'with' argument ignored", + object_type_name($2)); + $$ = $3; + } + | ORDINAL object_type relative_path + { + lex_warning("'%1%2 %3' in 'with' argument ignored", + $1, ordinal_postfix($1), object_type_name($2)); + $$ = $3; + } + | LABEL relative_path + { + lex_warning("initial '%1' in 'with' argument ignored", $1); + delete[] $1; + $$ = $2; + } + ; + +corner: + DOT_N + { $$ = &object::north; } + | DOT_E + { $$ = &object::east; } + | DOT_W + { $$ = &object::west; } + | DOT_S + { $$ = &object::south; } + | DOT_NE + { $$ = &object::north_east; } + | DOT_SE + { $$ = &object:: south_east; } + | DOT_NW + { $$ = &object::north_west; } + | DOT_SW + { $$ = &object::south_west; } + | DOT_C + { $$ = &object::center; } + | DOT_START + { $$ = &object::start; } + | DOT_END + { $$ = &object::end; } + | TOP + { $$ = &object::north; } + | BOTTOM + { $$ = &object::south; } + | LEFT + { $$ = &object::west; } + | RIGHT + { $$ = &object::east; } + | UPPER LEFT + { $$ = &object::north_west; } + | LOWER LEFT + { $$ = &object::south_west; } + | UPPER RIGHT + { $$ = &object::north_east; } + | LOWER RIGHT + { $$ = &object::south_east; } + | LEFT_CORNER + { $$ = &object::west; } + | RIGHT_CORNER + { $$ = &object::east; } + | UPPER LEFT_CORNER + { $$ = &object::north_west; } + | LOWER LEFT_CORNER + { $$ = &object::south_west; } + | UPPER RIGHT_CORNER + { $$ = &object::north_east; } + | LOWER RIGHT_CORNER + { $$ = &object::south_east; } + | NORTH + { $$ = &object::north; } + | SOUTH + { $$ = &object::south; } + | EAST + { $$ = &object::east; } + | WEST + { $$ = &object::west; } + | CENTER + { $$ = &object::center; } + | START + { $$ = &object::start; } + | END + { $$ = &object::end; } + ; + +expr: + expr_lower_than + { $$ = $1; } + | expr_not_lower_than + { $$ = $1; } + ; + +expr_lower_than: + expr '<' expr + { $$ = ($1 < $3); } + ; + +expr_not_lower_than: + VARIABLE + { + if (!lookup_variable($1, & $$)) { + lex_error("there is no variable '%1'", $1); + YYABORT; + } + free($1); + } + | NUMBER + { $$ = $1; } + | place DOT_X + { + if ($1.obj != 0) + $$ = $1.obj->origin().x; + else + $$ = $1.x; + } + | place DOT_Y + { + if ($1.obj != 0) + $$ = $1.obj->origin().y; + else + $$ = $1.y; + } + | place DOT_HT + { + if ($1.obj != 0) + $$ = $1.obj->height(); + else + $$ = 0.0; + } + | place DOT_WID + { + if ($1.obj != 0) + $$ = $1.obj->width(); + else + $$ = 0.0; + } + | place DOT_RAD + { + if ($1.obj != 0) + $$ = $1.obj->radius(); + else + $$ = 0.0; + } + | expr '+' expr + { $$ = $1 + $3; } + | expr '-' expr + { $$ = $1 - $3; } + | expr '*' expr + { $$ = $1 * $3; } + | expr '/' expr + { + if ($3 == 0.0) { + lex_error("division by zero"); + YYABORT; + } + $$ = $1/$3; + } + | expr '%' expr + { + if ($3 == 0.0) { + lex_error("modulus by zero"); + YYABORT; + } + $$ = fmod($1, $3); + } + | expr '^' expr + { + errno = 0; + $$ = pow($1, $3); + if (errno == EDOM) { + lex_error("arguments to '^' operator out of domain"); + YYABORT; + } + if (errno == ERANGE) { + lex_error("result of '^' operator out of range"); + YYABORT; + } + } + | '-' expr %prec '!' + { $$ = -$2; } + | '(' any_expr ')' + { $$ = $2; } + | SIN '(' any_expr ')' + { + errno = 0; + $$ = sin($3); + if (errno == ERANGE) { + lex_error("sin result out of range"); + YYABORT; + } + } + | COS '(' any_expr ')' + { + errno = 0; + $$ = cos($3); + if (errno == ERANGE) { + lex_error("cos result out of range"); + YYABORT; + } + } + | ATAN2 '(' any_expr ',' any_expr ')' + { + errno = 0; + $$ = atan2($3, $5); + if (errno == EDOM) { + lex_error("atan2 argument out of domain"); + YYABORT; + } + if (errno == ERANGE) { + lex_error("atan2 result out of range"); + YYABORT; + } + } + | LOG '(' any_expr ')' + { + errno = 0; + $$ = log10($3); + if (errno == ERANGE) { + lex_error("log result out of range"); + YYABORT; + } + } + | EXP '(' any_expr ')' + { + errno = 0; + $$ = pow(10.0, $3); + if (errno == ERANGE) { + lex_error("exp result out of range"); + YYABORT; + } + } + | SQRT '(' any_expr ')' + { + errno = 0; + $$ = sqrt($3); + if (errno == EDOM) { + lex_error("sqrt argument out of domain"); + YYABORT; + } + } + | K_MAX '(' any_expr ',' any_expr ')' + { $$ = $3 > $5 ? $3 : $5; } + | K_MIN '(' any_expr ',' any_expr ')' + { $$ = $3 < $5 ? $3 : $5; } + | INT '(' any_expr ')' + { $$ = $3 < 0 ? -floor(-$3) : floor($3); } + | RAND '(' any_expr ')' + { + lex_error("use of 'rand' with an argument is" + " deprecated; shift and scale 'rand()' with" + " arithmetic instead"); + $$ = 1.0 + floor(((rand()&0x7fff)/double(0x7fff))*$3); + } + | RAND '(' ')' + { + /* return a random number in the range [0,1) */ + /* portable, but not very random */ + $$ = (rand() & 0x7fff) / double(0x8000); + } + | SRAND '(' any_expr ')' + { + $$ = 0; + srand((unsigned int)$3); + } + | expr LESSEQUAL expr + { $$ = ($1 <= $3); } + | expr '>' expr + { $$ = ($1 > $3); } + | expr GREATEREQUAL expr + { $$ = ($1 >= $3); } + | expr EQUALEQUAL expr + { $$ = ($1 == $3); } + | expr NOTEQUAL expr + { $$ = ($1 != $3); } + | expr ANDAND expr + { $$ = ($1 != 0.0 && $3 != 0.0); } + | expr OROR expr + { $$ = ($1 != 0.0 || $3 != 0.0); } + | '!' expr + { $$ = ($2 == 0.0); } + + ; + +%% + +/* bison defines const to be empty unless __STDC__ is defined, which it +isn't under cfront */ + +#ifdef const +#undef const +#endif + +static struct { + const char *name; + double val; + int scaled; // non-zero if val should be multiplied by scale +} defaults_table[] = { + { "arcrad", .25, 1 }, + { "arrowht", .1, 1 }, + { "arrowwid", .05, 1 }, + { "circlerad", .25, 1 }, + { "boxht", .5, 1 }, + { "boxwid", .75, 1 }, + { "boxrad", 0.0, 1 }, + { "dashwid", .05, 1 }, + { "ellipseht", .5, 1 }, + { "ellipsewid", .75, 1 }, + { "moveht", .5, 1 }, + { "movewid", .5, 1 }, + { "lineht", .5, 1 }, + { "linewid", .5, 1 }, + { "textht", 0.0, 1 }, + { "textwid", 0.0, 1 }, + { "scale", 1.0, 0 }, + { "linethick", -1.0, 0 }, // in points + { "fillval", .5, 0 }, + { "arrowhead", 1.0, 0 }, + { "maxpswid", 8.5, 0 }, + { "maxpsht", 11.0, 0 }, +}; + +place *lookup_label(const char *label) +{ + saved_state *state = current_saved_state; + PTABLE(place) *tbl = current_table; + for (;;) { + place *pl = tbl->lookup(label); + if (pl) + return pl; + if (!state) + return 0; + tbl = state->tbl; + state = state->prev; + } +} + +void define_label(const char *label, const place *pl) +{ + place *p = new place[1]; + *p = *pl; + current_table->define(label, p); +} + +int lookup_variable(const char *name, double *val) +{ + place *pl = lookup_label(name); + if (pl) { + *val = pl->x; + return 1; + } + return 0; +} + +void define_variable(const char *name, double val) +{ + place *p = new place[1]; + p->obj = 0; + p->x = val; + p->y = 0.0; + current_table->define(name, p); + if (strcmp(name, "scale") == 0) { + // When the scale changes, reset all scaled predefined variables to + // their default values. + for (unsigned int i = 0; + i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++) + if (defaults_table[i].scaled) + define_variable(defaults_table[i].name, val*defaults_table[i].val); + } +} + +// called once only (not once per parse) + +void parse_init() +{ + current_direction = RIGHT_DIRECTION; + current_position.x = 0.0; + current_position.y = 0.0; + // This resets everything to its default value. + reset_all(); +} + +void reset(const char *nm) +{ + for (unsigned int i = 0; + i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++) + if (strcmp(nm, defaults_table[i].name) == 0) { + double val = defaults_table[i].val; + if (defaults_table[i].scaled) { + double scale; + lookup_variable("scale", &scale); + val *= scale; + } + define_variable(defaults_table[i].name, val); + return; + } + lex_error("'%1' is not a predefined variable", nm); +} + +void reset_all() +{ + // We only have to explicitly reset the predefined variables that + // aren't scaled because 'scale' is not scaled, and changing the + // value of 'scale' will reset all the predefined variables that + // are scaled. + for (unsigned int i = 0; + i < sizeof(defaults_table)/sizeof(defaults_table[0]); i++) + if (!defaults_table[i].scaled) + define_variable(defaults_table[i].name, defaults_table[i].val); +} + +// called after each parse + +void parse_cleanup() +{ + while (current_saved_state != 0) { + delete current_table; + current_table = current_saved_state->tbl; + saved_state *tem = current_saved_state; + current_saved_state = current_saved_state->prev; + delete tem; + } + assert(current_table == &top_table); + PTABLE_ITERATOR(place) iter(current_table); + const char *key; + place *pl; + while (iter.next(&key, &pl)) + if (pl->obj != 0) { + position pos = pl->obj->origin(); + pl->obj = 0; + pl->x = pos.x; + pl->y = pos.y; + } + while (olist.head != 0) { + object *tem = olist.head; + olist.head = olist.head->next; + delete tem; + } + olist.tail = 0; + current_direction = RIGHT_DIRECTION; + current_position.x = 0.0; + current_position.y = 0.0; +} + +const char *ordinal_postfix(int n) +{ + if (n < 10 || n > 20) + switch (n % 10) { + case 1: + return "st"; + case 2: + return "nd"; + case 3: + return "rd"; + } + return "th"; +} + +const char *object_type_name(object_type type) +{ + switch (type) { + case BOX_OBJECT: + return "box"; + case CIRCLE_OBJECT: + return "circle"; + case ELLIPSE_OBJECT: + return "ellipse"; + case ARC_OBJECT: + return "arc"; + case SPLINE_OBJECT: + return "spline"; + case LINE_OBJECT: + return "line"; + case ARROW_OBJECT: + return "arrow"; + case MOVE_OBJECT: + return "move"; + case TEXT_OBJECT: + return "\"\""; + case BLOCK_OBJECT: + return "[]"; + case OTHER_OBJECT: + case MARK_OBJECT: + default: + break; + } + return "object"; +} + +static char sprintf_buf[1024]; + +char *format_number(const char *fmt, double n) +{ + if (0 /* nullptr */ == fmt) + fmt = "%g"; + return do_sprintf(fmt, &n, 1); +} + +char *do_sprintf(const char *fmt, const double *v, int nv) +{ + // Define valid conversion specifiers and modifiers. + static const char spcs[] = "eEfgG%"; + static const char mods[] = "#-+ 0123456789."; + string result; + int i = 0; + string one_format; + while (*fmt) { + if ('%' == *fmt) { + one_format += *fmt++; + for (; *fmt != '\0' && strchr(mods, *fmt) != 0; fmt++) + one_format += *fmt; + if ('\0' == *fmt || strchr(spcs, *fmt) == 0) { + lex_error("invalid sprintf conversion specifier '%1'", *fmt); + result += one_format; + result += fmt; + break; + } + if ('%' == *fmt) { + fmt++; + snprintf(sprintf_buf, sizeof(sprintf_buf), "%%"); + } + else { + if (i >= nv) { + lex_error("too few arguments to sprintf"); + result += one_format; + result += fmt; + break; + } + one_format += *fmt++; + one_format += '\0'; +// We validated the format string above. Most conversion specifiers are +// rejected, including `n`. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + snprintf(sprintf_buf, sizeof(sprintf_buf), + one_format.contents(), v[i++]); +#pragma GCC diagnostic pop + } + one_format.clear(); + result += sprintf_buf; + } + else + result += *fmt++; + } + result += '\0'; + return strsave(result.contents()); +} + +// Local Variables: +// fill-column: 72 +// mode: C++ +// End: +// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: |