]> git.saurik.com Git - bison.git/blobdiff - tests/existing.at
Document 1.875b.
[bison.git] / tests / existing.at
index bad9858e2c5d73d951bdfa058b4580352123fbf1..fcb1af048df3242b0b0653bdf64b68b7e7b03659 100644 (file)
@@ -1,5 +1,7 @@
 # Exercising Bison on actual grammars.                   -*- Autotest -*-
 # Exercising Bison on actual grammars.                   -*- Autotest -*-
-# Copyright (C) 2002 Free Software Foundation, Inc.
+
+# Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003
+# Free Software Foundation, Inc.
 
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -1191,19 +1193,1561 @@ ARGUMENT_LIST   :       EXPRESSION              { mout(MENDSEP);
 # don't actually check the output.  But SEGV is watching us, and
 # so might do dmalloc.
 AT_CHECK([[bison --verbose --defines input.y]], 0, [],
 # don't actually check the output.  But SEGV is watching us, and
 # so might do dmalloc.
 AT_CHECK([[bison --verbose --defines input.y]], 0, [],
-[[input.y: warning: 78 shift/reduce conflicts and 10 reduce/reduce conflicts
+[[input.y: conflicts: 78 shift/reduce, 10 reduce/reduce
+]])
+
+AT_CHECK([[grep '^State.*conflicts:' input.output]], 0,
+[[State 64 conflicts: 14 shift/reduce
+State 164 conflicts: 1 shift/reduce
+State 201 conflicts: 33 shift/reduce, 4 reduce/reduce
+State 206 conflicts: 1 shift/reduce
+State 240 conflicts: 1 shift/reduce
+State 335 conflicts: 9 shift/reduce, 2 reduce/reduce
+State 356 conflicts: 1 shift/reduce
+State 360 conflicts: 9 shift/reduce, 2 reduce/reduce
+State 427 conflicts: 9 shift/reduce, 2 reduce/reduce
 ]])
 
 ]])
 
-AT_CHECK([[grep '^State.*contains' input.output]], 0,
-[[State 64 contains 14 shift/reduce conflicts.
-State 164 contains 1 shift/reduce conflict.
-State 201 contains 33 shift/reduce conflicts and 4 reduce/reduce conflicts.
-State 206 contains 1 shift/reduce conflict.
-State 240 contains 1 shift/reduce conflict.
-State 335 contains 9 shift/reduce conflicts and 2 reduce/reduce conflicts.
-State 356 contains 1 shift/reduce conflict.
-State 360 contains 9 shift/reduce conflicts and 2 reduce/reduce conflicts.
-State 427 contains 9 shift/reduce conflicts and 2 reduce/reduce conflicts.
+AT_CLEANUP
+
+
+## ----------------- ##
+## GNU pic Grammar.  ##
+## ----------------- ##
+
+AT_SETUP([GNU pic Grammar])
+
+# GNU pic, part of groff.
+
+# Bison once reported shift/reduce conflicts that it shouldn't have.
+
+AT_DATA([[input.y]],
+[[%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 UP
+%token DOWN
+%token LEFT
+%token RIGHT
+%token BOX
+%token CIRCLE
+%token ELLIPSE
+%token ARC
+%token LINE
+%token ARROW
+%token MOVE
+%token SPLINE
+%token HEIGHT
+%token RADIUS
+%token WIDTH
+%token DIAMETER
+%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 ALIGNED
+%token SPRINTF
+%token COMMAND
+
+%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 eg `line chop left'
+parses properly. */
+%left CHOP SOLID DASHED DOTTED UP DOWN FILL COLORED OUTLINED
+%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 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> 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:
+       VARIABLE '=' any_expr
+               {
+                 define_variable($1, $3);
+                 a_delete $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;
+                 a_delete $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);
+                 a_delete $2.str;
+                 fflush(stderr);
+               }
+       | SH
+               { delim_flag = 1; }
+         DELIMITED
+               {
+                 delim_flag = 0;
+                 if (safer_flag)
+                   lex_error("unsafe to run command `%1'", $3);
+                 else
+                   system($3);
+                 a_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
+                 a_delete $5;
+                 a_delete $7;
+               }
+       | COPY THRU
+               { delim_flag = 2; }
+         DELIMITED
+               { delim_flag = 0; }
+         until
+               {
+                 if (yychar < 0)
+                   do_lookahead();
+                 copy_rest_thru($4, $6);
+                 a_delete $4;
+                 a_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);
+                 a_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);
+                 a_delete $1.body;
+                 a_delete $4;
+               }
+       | reset_variables
+       | RESET
+               { define_variable("scale", 1.0); }
+       ;
+
+reset_variables:
+       RESET VARIABLE
+               {
+                 reset($2);
+                 a_delete $2;
+               }
+       | reset_variables VARIABLE
+               {
+                 reset($2);
+                 a_delete $2;
+               }
+       | reset_variables ',' VARIABLE
+               {
+                 reset($3);
+                 a_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);
+                 a_delete $1.str;
+                 a_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;
+                 a_delete $1.str;
+                 a_delete $3.str;
+               }
+       | text NOTEQUAL text
+               {
+                 $$ = strcmp($1.str, $3.str) != 0;
+                 a_delete $1.str;
+                 a_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(&current_position,
+                                          &current_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, & $$);
+                 a_delete $1;
+               }
+       | LABEL ':' optional_separator position_not_place
+               {
+                 $$.obj = 0;
+                 $$.x = $4.x;
+                 $$.y = $4.y;
+                 define_label($1, & $$);
+                 a_delete $1;
+               }
+       | LABEL ':' optional_separator place
+               {
+                 $$ = $4;
+                 define_label($1, & $$);
+                 a_delete $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
+               {
+                 $$ = 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);
+                 a_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) {
+                   $$->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 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);
+                 a_delete $4.v;
+                 a_delete $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;
+                     $$.v = new double[$$.maxv];
+                     memcpy($$.v, oldv, $$.nv*sizeof(double));
+                     a_delete oldv;
+                   }
+                 }
+                 $$.v[$$.nv] = $3;
+                 $$.nv += 1;
+               }
+       ;
+
+position:
+       position_not_place
+               { $$ = $1; }
+       | place
+               {
+                 position pos = $1;
+                 $$.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 = $1.x - $3.x;
+                 $$.y = $1.y - $3.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 '<' position ',' position '>'
+               {
+                 $$.x = (1.0 - $1)*$3.x + $1*$5.x;
+                 $$.y = (1.0 - $1)*$3.y + $1*$5.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;
+                 a_delete $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);
+                 a_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:
+       VARIABLE
+               {
+                 if (!lookup_variable($1, & $$)) {
+                   lex_error("there is no variable `%1'", $1);
+                   YYABORT;
+                 }
+                 a_delete $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 ')'
+               { $$ = floor($3); }
+       | RAND '(' any_expr ')'
+               { $$ = 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 '<' expr
+               { $$ = ($1 < $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); }
+
+       ;
 ]])
 
 ]])
 
+# Pass plenty of options, to exercise plenty of code, even if we
+# don't actually check the output.  But SEGV is watching us, and
+# so might do dmalloc.
+AT_CHECK([[bison --verbose --defines input.y]], 0, [], [])
+
 AT_CLEANUP
 AT_CLEANUP