/*  :ts=8 bk=0
 *
 * tree.c:	Demonstrater for the hypothetical HTXT form.  A simple
 *		hypertext like structure to demo the more complex 
 *		possibilities for parsing.
 *		Alpha Concept Prototype.
 *
 * Usage: tree -i/o/r <file>
 *
 * Writes and parses HTXT trees between memory and file.  The in-memory
 * structure gets written to the file and the file gets read directly
 * into the same structure using custom handlers.
 *
 *	-i reads the file using custom handlers.
 *	-r reads the file using a recursive-descent parser.
 *	-o writes the demo file.
 *
 * Stuart Ferguson			8810.18
 * ewhac (adjusted a tiny bit)		8811.02
 * shf (adjusted for Dec 88 revision)	8812.18
 */
#include <libraries/dos.h>
#include <iff/iffparse.h>

#define	ID_HTXT	MAKE_ID('H','T','X','T')
#define	ID_CHRS	MAKE_ID('C','H','R','S')

extern void	*OpenLibrary(), *AllocMem();
extern LONG	Open();
LONG		WriteTree(), ReadTree(), TreeHandler(), TextHandler(),
		ReadTreeR(), PurgeVHNode();
void		PrintTree(), FreeTree(), InitVHNode();
char		*ReadTextChunk();
struct HNode	*ReadTreeForm();

/*
 * Tree data structure consists of these types of nodes strung out
 * into trees.  Type is either HN_NODE or HN_LEAF.
 */
struct HNode {
	struct HNode	*next;
	void 		*val;
	long		type;
};

#define HN_NODE		0
#define HN_LEAF		1

/*
 * vHNode's will hold temporary parse states in the local context.
 */
struct vHNode {
	struct HNode		*val;
};
#define LCI_HNODE	MAKE_ID('h','n','o','d')

/*
 * Demo text tree structure for output.
 */
struct HNode dtree[] = {
	{ NULL, &dtree[1], HN_NODE },
	  { &dtree[2], "This is the text body.", HN_LEAF },
	  { &dtree[5], &dtree[3], HN_NODE },
	    { &dtree[4], "More stuff.", HN_LEAF },
	    { NULL, "Even more stuff.", HN_LEAF },
	  { NULL, &dtree[6], HN_NODE },
	    { NULL, "Nested text ...", HN_LEAF }
};


/*
 * Buffer for text from IFF file.  Could do this dynamically, but
 * this makes things easier for this simple case.
 */
char	tbuf[500];
int	bufpos = 0;

struct Library	*IFFParseBase;
LONG		GenericStub();
#define CALL_ME	GenericStub


main (argc, argv)
int	argc;
char	**argv;
{
	struct IFF_File		*iff = NULL;
	long			error;
	long			filemode;
	int			recur;

	if (argc != 3) {
		printf ("usage: %s -i/o/r <file>\n", argv[0]);
		goto die;
	}

	if (!(IFFParseBase = OpenLibrary ("iffparse.library", 0L))) {
		puts ("Cannot open library.");
		goto die;
	}

	/*
	 * Get file i/o direction - 'o' = NEWFILE, 'i/r' = OLDFILE
	 */
	filemode = MODE_OLDFILE;
	if (argv[1][1] == 'o')
		filemode = MODE_NEWFILE;

	recur = (argv[1][1] == 'r');

	/*
	 * Standard IFF open sequence:
	 *	AllocIFF()
	 *	assign stream pointer and setup stream handler vectors
	 *	OpenIFF()
	 */
	if (!(iff = AllocIFF())) {
		puts ("AllocIFF() failed.");
		goto die;
	}

	if (!(iff -> iff_Stream = Open (argv[2], filemode))) {
		puts ("File open failed.");
		goto die;
	}
	InitIFFasDOS (iff, filemode==MODE_OLDFILE ? IFFM_READ : IFFM_WRITE);

	OpenIFF (iff);

	if (filemode == MODE_NEWFILE) {
		puts ("writing tree structure...");
		PrintTree (dtree, 1);
		error = WriteTree (iff, dtree);
	} else if (recur)
		error = ReadTreeR (iff);
	else
		error = ReadTree (iff);

	if (error)
		printf ("Operation aborted (%ld)\n", error);

die:
	/*
	 * Standard close sequence (reverse of open):
	 *	CloseIFF()
	 *	close stream
	 *	FreeIFF()
	 */
	if (iff) {
		CloseIFF (iff);
		if (iff -> iff_Stream)
			Close (iff -> iff_Stream);
		FreeIFF (iff);
	}
	if (IFFParseBase)	CloseLibrary (IFFParseBase);
}


/*
 * Write a tree structure to the given IFF stream as a FORM HTXT.
 * Nested trees dealt with recursivly as embedded FORM HTXT's.
 */
LONG
WriteTree (iff, tree)
struct IFF_File *iff;
struct HNode	*tree;
{
	register struct HNode	*hn;
	register LONG		error, size;

	if (!tree)
		return (0);

	/*
	 * A node structure corresponds to a FORM HTXT chunk with the
	 * internal parts of the tree as chunks within this chunk.
	 */
	if (tree -> type == HN_NODE) {
		if (error = PushChunk
			     (iff, ID_HTXT, ID_FORM, IFFSIZE_UNKNOWN))
			return (error);

		for (hn = tree -> val; hn; hn = hn -> next)
			if (error = WriteTree (iff, hn))
				return (error);
	/*
	 * Leaf nodes correspond to CHRS chunks within their bounding
	 * HTXT FORM.
	 */
	} else {
		if (error = PushChunk (iff, 0L, ID_CHRS, IFFSIZE_UNKNOWN))
			return (error);

		size = strlen (tree -> val);
		if (WriteChunkRecords (iff, tree -> val, 1L, size) != size)
			return (IFFERR_WRITE);
	}

	return (PopChunk (iff));
}


/*
 * Reading uses a custom handler for the HTXT.FORM chunk.  The handler
 * installs a handler for HTXT.CHRS chunks and provides a place for the
 * result of the parse to accumulate.  The end result finishes in the 
 * node at the root level of the structure.
 */
LONG
ReadTree (iff)
struct IFF_File	*iff;
{
	register struct LocalContextItem *lci;
	register struct HNode		*tree, *hn;
	register struct vHNode		*vhn;
	register struct ContextNode	*top;
	long			 error;

	/*
	 * Install custom handler for HTXT.FORM chunks and 
	 * set up to stop at the end of same.
	 */
	if (error = EntryHandler (iff, ID_HTXT, ID_FORM, TreeHandler,
			CALL_ME, IFFSLI_ROOT))
		return (error);

	if (error = StopOnExit (iff, ID_HTXT, ID_FORM))
		return (error);

	/*
	 * Parse that file and let the handlers do their work.
	 * Also process end of context conditions for exiting
	 * HTXT FORM's.
	 */
	while (1) {
		/*
		 * SCAN parse will return either EOF at the end of file
		 * or EOC for the end of HTXT FORM context or error.
		 * Since we'll "break" when exiting the last FORM, even
		 * the EOF case can be considered an error.
		 */
		error = ParseIFF (iff, IFFPARSE_STEP);
		if (!error) continue;
		if (error != IFFERR_EOC)
			return (error);

		top = CurrentChunk (iff);
		if (top -> cn_ID != ID_FORM || top -> cn_Type != ID_HTXT)
			continue;

		/*
		 * We're about to exit a FORM HTXT, so look for the
		 * tree data extracted for this FORM.
		 */
		lci = FindLocalItem (iff, LCI_HNODE, 0L, 0L);

		if (!lci) {
			puts ("Error looking for result of FORM traversal.");
			tree = NULL;
			break;
		}

		/*
		 * We're usurping this tree so the purge vector won't
		 * try to deallocate it out from under us.
		 */
		vhn = (struct vHNode *) LocalItemData (lci);
		tree = vhn -> val;
		vhn -> val = NULL;

		/*
		 * Here's the kludgy part.
		 *
		 * We've got the tree for this FORM, so now we need to
		 * propogate it backwards in the context stack and 
		 * attach it to the tree for the enclosing FORM.
		 * So, take the LCI we just found and change the ID
		 * field so a subsequent FindLocalItem() will find
		 * the enclosing one.  (Need a better way to do this!)
		 */
		lci -> lci_ID = 1;
		lci = FindLocalItem (iff, LCI_HNODE, 0L, 0L);

		/*
		 * If there is no enclosing FORM to be found, we're done.
		 */
		if (!lci)
			break;

		/*
		 * Take the tree found for this current FORM and make
		 * make it a sub-tree of the enclosing one.
		 */
		if (!(hn = AllocMem ((long) sizeof (*hn), 0L)))
			return (100);

		hn -> type = HN_NODE;
		hn -> next = NULL;
		hn -> val = tree;
		AddEnd (LocalItemData (lci), hn);

		/*
		 * Done processing end of FORM context -- keep parsing.
		 */
	}

	/*
	 * Do something interesting with the result.
	 */
	puts ("result of read...");
	PrintTree (tree, 1);
	FreeTree (tree);

	return (0);
}


/*
 * Handler for the HTXT.FORM chunk.  Install a local handler for
 * HTXT.CHRS chunks and provide a place in the current context for
 * result trees to attach themselves.
 */
LONG
TreeHandler (iff, cn, D0, D1)
struct IFF_File		*iff;
struct ContextNode	*cn;
LONG			 D0, D1;
{
	register struct LocalContextItem *lci;
	register struct vHNode		 *vhn;
	register LONG			  error;

	if (error = EntryHandler (iff, ID_HTXT, ID_CHRS, TextHandler,
					CALL_ME, IFFSLI_TOP))
		return (error);

	if (!(lci = AllocLocalItem (LCI_HNODE, 0L, 0L,
					(long) sizeof (struct vHNode))))
		return (IFFERR_NOMEM);

	SetLocalItemPurge (lci, PurgeVHNode, CALL_ME);

	vhn = (struct vHNode *) LocalItemData (lci);
	vhn -> val = NULL;

	if (error = StoreLocalItem (iff, lci, IFFSLI_TOP)) {
		FreeLocalItem (lci);
		return (error);
	}

	return (0);
}


/*
 * Read HTXT.CHRS data into buffer and link into an HNode structure.
 * Attach this HNode to the locally available vHNode in this context.
 */
LONG
TextHandler (iff, cn, D0, D1)
struct IFF_File		*iff;
struct ContextNode	*cn;
LONG			 D0, D1;
{
	register struct vHNode	*vhn;
	register struct HNode	*hn;
	register LONG		 error;

	if (!(hn = AllocMem ((LONG) sizeof (*hn), 0L)))
		return (IFFERR_NOMEM);

	hn -> type = HN_LEAF;
	hn -> next = NULL;
	if (!(hn -> val = ReadTextChunk (iff))) {
		FreeTree (hn);
		return (-600);
	}

	/*
	 * Locate the local vHNode and attach this HNode to it's list.
	 */
	if (!(vhn = (struct vHNode *) LocalItemData (
			FindLocalItem (iff, LCI_HNODE, 0L, 0L)))) {
		FreeTree (hn);
		return (-500);
	}
	AddEnd (vhn, hn);

	return (0);
}


/*
 * Read the contents of a CHRS chunk into a buffer and return pointer.
 */
char *
ReadTextChunk (iff)
struct IFF_File	*iff;
{
	register LONG	siz;
	register char	*t;

	t = &tbuf[bufpos];
	siz = CurrentChunk (iff) -> cn_Size;
	if (ReadChunkBytes (iff, t, siz) != siz)
		return (NULL);

	bufpos += siz;
	tbuf[bufpos++] = 0;

	return (t);
}


/*
 * Attach HNode to the end of list attached to "vhn".
 * List will be reversed otherwise.
 */
AddEnd (vhn, hn)
struct vHNode	*vhn;
struct HNode	*hn;
{
	register struct HNode	*t;

	if (!vhn -> val) {
		vhn -> val = hn;
		hn -> next = NULL;
	} else {
		/*
		 * Find end of list (next == NULL).
		 */
		for (t = vhn -> val; t -> next; t = t -> next) ;
		t -> next = hn;
		hn -> next = NULL;
	}
}


/*
 * If everything goes right, we don't really need to use a special
 * purge vector for vHNodes since the tree will have been removed.
 * If something goes wrong and parsing is inturrupted, however, 
 * CloseIFF() will try to purge all the local context items and
 * this item should be able to clean itself up if it's in an
 * intermediate state.
 */
LONG
PurgeVHNode (iff, lci, D0, D1)
struct IFF_File		*iff;
struct LocalContextItem *lci;
LONG			 D0, D1;
{
	register struct vHNode	*vhn;

	vhn = (struct vHNode *) LocalItemData (lci);
	if (vhn -> val)
		FreeTree (vhn -> val);
	FreeLocalItem (lci);
}


/*
 * Recursive-descent parser for the HTXT tree structure.
 */
LONG
ReadTreeR (iff)
struct IFF_File	*iff;
{
	register LONG		error;
	register struct HNode	*tree;

	if (error = StopChunk (iff, ID_HTXT, ID_FORM))
		return (error);

	if (error = ParseIFF (iff, IFFPARSE_SCAN))
		return (error);

	tree = ReadTreeForm (iff);
	if (!tree)
		return (-427);

	/*
	 * Something interesting ...
	 */
	puts ("result of recursive-descent parse ...");
	PrintTree (tree, 1);
	FreeTree (tree);
	return (0);
}


/*
 * With the iff file positioned just inside a FORM TREE, parse
 * the contents and return as a single HNode.
 */
struct HNode *
ReadTreeForm (iff)
struct IFF_File	*iff;
{
	register struct HNode		*hn, *sub;
	register struct ContextNode	*top;
	register LONG			error;

	if (!(hn = AllocMem ((LONG) sizeof (*hn), 0L)))
		return (NULL);

	hn -> type = HN_NODE;
	hn -> next = NULL;
	hn -> val = NULL;

	while (1) {
		error = ParseIFF (iff, IFFPARSE_STEP);
		if (error == IFFERR_EOC || error == IFFERR_EOF)
			return (hn);
		if (error)
			goto escape;

		top = CurrentChunk (iff);
		if (top -> cn_Type != ID_HTXT)
			continue;

		switch (top -> cn_ID) {
		    case ID_FORM:
			if (!(sub = ReadTreeForm (iff)))
				goto escape;
			AddEnd (&hn -> val, sub);
			break;

		    case ID_CHRS:
			if (!(sub = AllocMem ((LONG) sizeof (*sub), 0L)))
				goto escape;
			sub -> type = HN_LEAF;
			sub -> next = NULL;
			if (!(sub -> val = ReadTextChunk (iff))) {
				FreeTree (sub);
				goto escape;
			}
			AddEnd (&hn -> val, sub);

			/*
			 * Pop CHRS chunk.
			 */
			if (ParseIFF (iff, IFFPARSE_STEP) != IFFERR_EOC)
				goto escape;
			break;
		}
	}

escape:
	FreeTree (hn);
	return (NULL);
}


void
PrintTree (tree, depth)
struct HNode	*tree;
int		depth;
{
	int i;

	if (!tree)
		return;

	for (i = 0; i < depth; i++)
		printf (". ");

	if (tree -> type == HN_NODE) {
		printf ("node\n");
		PrintTree (tree -> val, depth + 1);
	} else {
		printf ("leaf: %s\n", tree -> val);
	}
	PrintTree (tree -> next, depth);
}


void
FreeTree (tree)
struct HNode	*tree;
{
	if (!tree)
		return;

	if (tree -> type == HN_NODE)
		FreeTree (tree -> val);
	FreeTree (tree -> next);
	FreeMem (tree, (LONG) sizeof (*tree));
}
