/* 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: