[comp.compilers] Avoiding right-recursion

Mike.Spivey@prg.oxford.ac.uk (Mike Spivey) (01/23/91)

This is partly a stylistic and partly a practical question about lists
and yacc parsers.  If I want to recognise a long (and non-empty) list
of items and build a linked list from them, the obvious approach ...

	list	:	item		{ $$ = cons($1, nil); }
		|	item ',' list	{ $$ = cons($1, $3); }
		;

... is weak because the parser's stack will grow linearly with the
length of the list.  (Is it one state stacked per item or two?). This
is a general problem with right recursion in LR parsers.

A better approach in some ways is to use left recursion but do extra
work to build the list:

	list	:	item		{ $$ = cons($1, nil); }
		|	list ',' item	{ $$ = snoc($1, $3); }
		;

with snoc defined, perhaps, by (a well-typed version of)

	/* snoc -- destructively add an item to the end of a list */
	list snoc(list a, val y)
	{
		if (a == nil)
			return cons(y, nil);
		else {
			list b = a;
			while (tail(b) != nil)
				b = tail(b);
			tail(b) = cons(y, nil);
			return a;
		}
	}

But this approach uses time quadratic in the length of the list.

I also stumbled on the following yacc-hack:

	list	:	head body	{ $$ = $1; }
		;
	head	:	item		{ $$ = cons($1, nil); }
		;
	body	:	/* empty */	{ $$ = $0; }
		|	body ',' item	{ $$ = tail($1) = cons($3, nil); }
		;

The trick here is that the value of a 'body' is the last item in the
list, and the hideous $0 reaches over and grabs the first item as the
last when the body is empty.

Other approaches suggest themselves: build the list backwards then
nreverse it, for example.  My question is this: what do you, dear
reader, do in these circumstances?  And, misguided though the
yacc-hack may be, is it portable?

-- Mike Spivey, Programming Research Group, Oxford, England

[The hack is probably portable, but it's hardly a good idea since there are
easier ways to do the job.  I make the list a structure with two pointers,
one to the head and one to the tail, so updates can be hooked directly to the
tail without scanning the list.  Another approach is to create the list
linked backwards, pushing each new item on to the list, and then when the
whole thing is made reverse the pointers in one pass down the list.  -John]
-- 
Send compilers articles to compilers@iecc.cambridge.ma.us or
{ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.

mareb@lux.sait.edu.au (Buckley) (01/30/91)

>From: Mike.Spivey@prg.oxford.ac.uk (Mike Spivey)
>A better approach in some ways is to use left recursion but do extra
>work to build the list:

I like to use a cyclically linked list - the tail of the list points
back to the head. The list pointer points to the tail (or nil for
an empty list). It is very easy to append to this list and when you are
done it is very easy to convert it to a conventional list. And you still
only need one pointer.

I guess this would look something like this:

	list	: CLlist	{ $$ = tail($1); tail($1) = nil; } ;
	CLlist	: item		{ $$ = tail($1) = $1; }
		| CLlist ',' item { tail($3) = tail($1); $$ = tail($1) = $3; }
		;


	b++
__
Bob Buckley                                  Phone: (08) 343 3465
School of Mathematics & Computer Studies     Fax:   (08) 349 4367
University of South Australia
The Levels SA 5095                         email: mareb@lux.sait.edu.au
Australia
-- 
Send compilers articles to compilers@iecc.cambridge.ma.us or
{ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.