/* 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 . */ %{ #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 LABEL %token VARIABLE %token NUMBER %token TEXT %token COMMAND_LINE %token DELIMITED %token 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 expr expr_lower_than expr_not_lower_than any_expr text_expr %type optional_by %type expr_pair position_not_place %type simple_if %type nth_primitive %type corner %type path label_path relative_path %type place label element element_list middle_element_list %type object_spec %type position %type object_type %type optional_ordinal_last ordinal %type macro_name until %type sprintf_args %type 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); } | '{' { $$.x = current_position.x; $$.y = current_position.y; $$.dir = current_direction; } element_list '}' { current_position.x = $2.x; current_position.y = $2.y; current_direction = $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; $$ = 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 = $2->x; current_position.y = $2->y; current_direction = $2->dir; $$ = new object_spec(BLOCK_OBJECT); olist.wrap_up_block(& $$->oblist); $$->tbl = current_table; current_table = $2->tbl; current_saved_state = $2->prev; delete $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: