/* RCS file parser */
/* $Id: rcsfile.y,v 1.33 2003/09/06 15:11:17 pasky Exp $ */

/*
 * Copyright (c) 2003  Petr Baudis.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *   * Neither the name of the author nor the names of the product's
 *     contributors may be used to endorse or promote products
 *     derived from this software without specific prior written
 *     permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* FIXME: The parser is quite fragile, and it is probably trivial to break up
 * by some invalid input. It is rather a proof-of-concept implementation and
 * you should not feed it untrusted input! A lot of work needs to go into it.
 * First, some hand-made parser should be done, at least remotely effective
 * ;-). --pasky */

%{
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <librcs/delta.h>
#include <librcs/file.h>
#include <librcs/librcs.h>
#include <librcs/revision.h>
#include <librcs/revnum.h>

#include <librev/data_text.h>
#include <librev/datetime.h>
#include <librev/inode.h>
#include <librev/revision.h>
#include <librev/symbol.h>

void yyerror(char *);
int yylex();

char *yymkrev (char *revision);

struct rcs_file *yyrcsfile;
struct rev_revision *yyrevision;
struct rcs_revision *yyrcsrev;
char *yyheadrev = NULL;

#define YYERROR_VERBOSE
#define YYDEBUG 1
%}

%union {
  char *string;
}

%token	HEAD
%token	BRANCH
%token	ACCESS
%token	SYMBOLS
%token	LOCKS
%token	STRICT
%token	COMMENT
%token	EXPAND

%token	DATE
%token	AUTHOR
%token	STATE
%token	BRANCHES
%token	NEXT

%token	DESC

%token	LOG
%token	TEXT

%token	NUM
%token	ID
/* SYM is like ID */
%token	STRING

%token	PAIRSEP
%token	STATEMENT_END

%type	<string>	STRING
%type	<string>	ID
%type	<string>	NUM
%type	<string>	maybe_num
%type	<string>	maybe_id

%%

rcstext: admin deltas desc deltatexts ;



admin:	admin admin_item | admin_item

admin_item:	head | branch | access | symbols | locks | comment | expand


head:	HEAD maybe_num STATEMENT_END
	{
		if (yyheadrev) free (yyheadrev);
		yyheadrev = strdup ($2);
		/* printf("head %s\n", $2); */
	}


/* TODO */

branch:	BRANCH maybe_num STATEMENT_END
	{ /* printf("branch %s\n", $2); */ }


/* TODO */

access:	ACCESS maybe_ids STATEMENT_END
	{ /* printf("access ...\n"); */ }


symbols:SYMBOLS symnum_pairs STATEMENT_END
	{ /* printf("symbols ...\n"); */ }

symnum_pairs:	| symnum_pairs symnum_pair | symnum_pair

symnum_pair:	ID PAIRSEP NUM
	{
		struct rev_symbol *symbol;
		struct rev_objid *r;
		char *rstr = strdup ($3), *rptr;

		/* Stupid CVS inserts .0. into the branch revision numbers.
		 * Clueless twit! */
		/* TODO: Probably much simpler test would suffice, but I'm not
		 * sure of the perverse rules CVS is using now. --pasky */
		for (rptr = rstr; *rptr; rptr++) {
			if (rptr[0] == '.' && rptr[1] == '0'
			    && rptr[2] == '.') {
				memmove (rptr + 0, rptr + 2,
					 strlen (rptr + 2) + 1);
				rptr--; /* Check this char again. */
			}
		}

		r = rcs_revnum_dig (yyrcsfile->inode, rstr);
		free (rstr);
		if (! r)
			yyerror ("rcs_revnum_dig() error");
		symbol = rev_symbol_init ($1, r);
		rev_list_add (yyrcsfile->inode->symbols, *symbol, symbol);
		rev_symbol_associate (symbol);
		rev_objid_untie (r);
	}


/* TODO */

locks:	LOCKS idnum_pairs STATEMENT_END maybe_strict
	{ /* printf("locks ...\n"); */ }

idnum_pairs:	| idnum_pairs idnum_pair | idnum_pair

idnum_pair:	ID PAIRSEP NUM
	{ /* Just something dummy to silence bison. */ }

maybe_strict:	| STRICT STATEMENT_END


comment:COMMENT STRING STATEMENT_END
	{ rcs_file_set_comment (yyrcsfile, $2); }


/* TODO */

expand:	EXPAND STRING STATEMENT_END
	{ /* printf("expand %s\n", $2); */ }



deltas:	deltas delta | delta

delta:	NUM
	{
		{
			struct rev_objid *r;

			r = rcs_revnum_dig (yyrcsfile->inode, $1);
			if (! r || r->type != ROIT_REVISION)
				yyerror ("Invalid revision number?");
			yyrevision = r->data.revision;
			rev_revision_associate (yyrevision);
			rev_objid_untie (r);
			free (r);
		}

		/* FIXME: Handle allocation failures. --pasky */
		if (yyrevision->data)
			yyrcsrev = yyrevision->data;
		else
			yyrcsrev = rcs_revision_init (yyrevision);

		if (! strcmp ($1, yyheadrev))
			rcs_revision_set_head (yyrcsrev, 1);

		/* printf("delta %s\n", $1); */
	}
	delta_items

delta_items:	delta_items delta_item | delta_item

delta_item:	date | author | state | branches | next


/* TODO */

date:	DATE NUM STATEMENT_END
	{
		char *date = $2;
		struct rev_datetime *dt;

		dt = rev_datetime_init ();
		if (! dt) break;

		if (sscanf (date, "%d.%d.%d.%d.%d.%d",
				&dt->year, &dt->month, &dt->day,
				&dt->hour, &dt->minute, &dt->second)
		    < 6) {
			rev_datetime_done (dt);
			break;
		}

		rev_revision_set_ctime (yyrevision, dt);

		/* printf("date %s\n", $2); */
	}


author:	AUTHOR ID STATEMENT_END
	{
		rev_revision_set_author (yyrevision, $2);
		/* printf("author %s\n", $2); */
	}


state:	STATE maybe_id STATEMENT_END
	{
		rcs_revision_set_state (yyrcsrev, $2);
		/* printf("state %s\n", $2); */
	}


/* TODO */

branches:BRANCHES maybe_nums STATEMENT_END
	{ /* printf("branches ...\n"); */ }


next:	NEXT maybe_num STATEMENT_END
	{
		/* XXX: Do we need to care about this? --pasky */
		/* printf("next %s\n", $2); */
	}



desc:	DESC STRING
	{ rcs_file_set_desc (yyrcsfile, $2); }



deltatexts:	deltatexts deltatext | deltatext

deltatext:	NUM
	{
		{
			struct rev_objid *r;

			r = rcs_revnum_lookup (yyrcsfile->inode, $1);
			if (! r || r->type != ROIT_REVISION)
				yyerror ("Invalid revision number?");
			yyrevision = r->data.revision;
			rev_revision_associate (yyrevision);
			rev_objid_untie (r);
			free (r);
		}

		yyrcsrev = yyrevision->data;
		assert (yyrcsrev);

		/* printf("deltatext %s\n", $1); */
	}
		deltatext_items

deltatext_items:deltatext_items deltatext_item | deltatext_item

deltatext_item:	log | text


log:	LOG STRING
	{
		rev_revision_set_comment (yyrevision, $2);
		/* printf("log %s\n", $2); */
	}


text:	TEXT STRING
	{
		if (rcs_revision_get_head (yyrcsrev)) {
			yyrevision->dataobj = rev_dataobj_init ();
			/* FIXME: Handle errors. --pasky */

			/* Set the data object filename accordingly to the RCS
			 * file name. */
			{
				char *fname = strdup (yyrcsfile->filename);
				int fnl = strlen (fname);

				/* Remove the trailing ,v garbage. */
				if (fname [fnl - 2] == ',' &&
				    fname [fnl - 1] == 'v')
				    fname [fnl - 2] = 0;
				rev_dataobj_set_filename (yyrevision->dataobj,
							  fname);
				free (fname);
			}

			rev_dataobj_text_init (yyrevision->dataobj);
			rev_dataobj_text_import (yyrevision->dataobj, $2);
		} else {
			struct rev_delta *delta = rev_delta_import_rcs ($2);

			if (rev_revision_is_dummy (yyrevision->branch->parent)) {
				yyrevision->delta_rev = delta;
			} else {
				yyrevision->delta_forw = delta;
			}
		}
		/* printf("[%d] text %s\n", rev_revision_get_number (yyrevision), $2); */
	}



maybe_num:
	{ $$ = NULL; }
		| NUM
	{ $$ = $1; }

maybe_nums:	| maybe_nums maybe_num

maybe_id:
	{ $$ = NULL; }
		| ID
	{ $$ = $1; }

maybe_ids:	| maybe_ids maybe_id


%%

void yyerror(char *s) {
  fprintf(stderr, "RCS parser failed: %s\n", s);
}
