/*--------------------------------------------------------------------*//*:Ignore this sentence.
Copyright (C) 1999, 2001 SIL International. All rights reserved.

Distributable under the terms of either the Common Public License or the
GNU Lesser General Public License, as specified in the LICENSING.txt file.

File: Compiler.cpp
Responsibility: Sharon Correll
Last reviewed: Not yet.

Description:
    Methods to implement the compiler, which generates the final tables and writes them to
	the output file.
-------------------------------------------------------------------------------*//*:End Ignore*/

/***********************************************************************************************
	Include files
***********************************************************************************************/
#include "main.h"

#ifdef _MSC_VER
#pragma hdrstop
#endif

#undef THIS_FILE
DEFINE_THIS_FILE

/***********************************************************************************************
	Forward declarations
***********************************************************************************************/

/***********************************************************************************************
	Local Constants and static variables
***********************************************************************************************/

/***********************************************************************************************
	Methods
***********************************************************************************************/

/*----------------------------------------------------------------------------------------------
	Do the pre-compilation tasks for each of the main chunks of data. Return false if
	compilation cannot continue due to an unrecoverable error.
----------------------------------------------------------------------------------------------*/
bool GrcManager::PreCompile(GrcFont * pfont)
{
	if (!PreCompileFeatures(pfont))
		return false;

	if (!PreCompileLanguages(pfont)) // do after features
		return false;

	if (!PreCompileClassesAndGlyphs(pfont))
		return false;

	//	Unscale the extra ascent and extra descent.
	GdlExpression * pexp = m_prndr->ExtraAscent();
	if (pexp)
		pexp->SimplifyAndUnscale(0xFFFF, pfont);
	pexp = m_prndr->ExtraDescent();
	if (pexp)
		pexp->SimplifyAndUnscale(0xFFFF, pfont);

	if (!PreCompileRules(pfont))
		return false;

	return true;
}


bool GrcManager::Compile(GrcFont * /*pfont*/)
{
	GenerateFsms();
	CalculateContextOffsets(); // after max-rule-context has been set
	return false;
}


/*----------------------------------------------------------------------------------------------
	Generate the engine code for the constraints and actions of a rule.
----------------------------------------------------------------------------------------------*/
void GdlRule::GenerateEngineCode(GrcManager * pcman, int fxdRuleVersion,
	std::vector<gr::byte> & vbActions, std::vector<gr::byte> & vbConstraints)
{
	GenerateConstraintEngineCode(pcman, fxdRuleVersion, vbConstraints);
	//	Save the size of the rule constraints from the -if- statements.
	// int cbGenConstraint = vbConstraints.size();

	//	Count the number of unmodified items at the end of the rule; these do not need to
	//	be processed as far as actions go, and the default scan advance position is just
	//	before these.
	size_t iritLimMod = m_vprit.size();
	while (iritLimMod > 0 && !dynamic_cast<GdlSetAttrItem *>(m_vprit[iritLimMod - 1]))
		iritLimMod--;

	//	Now iritLimMod is the first item that will not be modified.

	//	Also note the the first item that needs to be processed as far as constraints go is
	//	m_critPrependedAnys.
	//	The first item to be processed as far as actions go is
	//	m_critPrependedAnys + m_critPreModContext.

	bool fSetInsertToFalse = false;
	bool fBackUpOneMore = false;
	int iritFirstModItem = m_critPrependedAnys + m_critPreModContext;
	int irit;
	for (irit = m_critPrependedAnys; irit < signed(m_vprit.size()); irit++)
	{
		if (iritFirstModItem <= irit && irit < signed(iritLimMod))
		{
			m_vprit[irit]->GenerateActionEngineCode(pcman, fxdRuleVersion, vbActions, this, irit,
				&fSetInsertToFalse);
		}

		m_vprit[irit]->GenerateConstraintEngineCode(pcman, fxdRuleVersion, vbConstraints,
			irit, m_viritInput, iritFirstModItem);
	}
	if (fSetInsertToFalse)
	{
		//	Have to modify the first item beyond the scan advance position, in order to
		//	set insert = false (due to some attachment). So here we create the code to
		//	set the attribute, and then back up one to get the scan advance position back
		//	to where it should be.
		m_vprit[iritLimMod]->GenerateActionEngineCode(pcman, fxdRuleVersion, vbActions, this, irit,
			&fSetInsertToFalse);
		Assert(!fSetInsertToFalse);
		m_vprit[iritLimMod]->GenerateConstraintEngineCode(pcman, fxdRuleVersion, vbConstraints,
			irit, m_viritInput, iritFirstModItem);
		fBackUpOneMore = true;
	}

	if (vbConstraints.size() == 0)
	{ }	// vbConstraints.Push(kopRetTrue); -- no, leave empty
	else
		vbConstraints.push_back(kopPopRet);

	if (m_nOutputAdvance == -1)
	{
		if (fBackUpOneMore)
		{
			//	Return -1.
			vbActions.push_back(kopPushByte);
			vbActions.push_back(0xFF);
			vbActions.push_back(kopPopRet);
		}
		else
			//	Push return-zero, meaning don't adjust the scan position.
			vbActions.push_back(kopRetZero);
	}
	else
	{
		//	Push a command to return the amount to adjust the scan position--
		//	forward or backward.

		Assert(m_nDefaultAdvance != -1);	// calculated set in GdlRule::HasReprocessing()

		int nAdvanceOffset = m_nOutputAdvance - m_nDefaultAdvance;
		Assert((abs(nAdvanceOffset) & 0xFFFFFF00) == 0);	// check for trucation error

		if (fBackUpOneMore)
			nAdvanceOffset--;

		vbActions.push_back(kopPushByte);
		vbActions.push_back((char)nAdvanceOffset);
		vbActions.push_back(kopPopRet);
	}
}

/*----------------------------------------------------------------------------------------------
	Generate engine code for the constraints of a given rule that were in -if- statements,
	minus the final pop-and-return command.
----------------------------------------------------------------------------------------------*/
void GdlRule::GenerateConstraintEngineCode(GrcManager * /*pcman*/, int fxdRuleVersion,
	std::vector<gr::byte> & vbOutput)
{
	if (m_vpexpConstraints.size() == 0)
	{
		return;
	}

	//	'and' all the constraints together; the separate constraints come from separate
	//	-if- or -elseif- statements.
	int nBogus;
	m_vpexpConstraints[0]->GenerateEngineCode(fxdRuleVersion, vbOutput,
		-1, NULL, -1, false, -1, &nBogus);
	for (size_t ipexp = 1; ipexp < m_vpexpConstraints.size(); ipexp++)
	{
		m_vpexpConstraints[ipexp]->GenerateEngineCode(fxdRuleVersion, vbOutput,
			-1, NULL, -1, false, -1, &nBogus);
		vbOutput.push_back(kopAnd);
	}
}


/*----------------------------------------------------------------------------------------------
	Generate engine code for the constraints of a given rule item.
	Arguments:
		vbOutput			- buffer containing engine code already generated from -if-
								statements, minus the final pop-and-return command.
		viritInput			- input indices for items of this rule
		irit				- index of item
----------------------------------------------------------------------------------------------*/
void GdlRuleItem::GenerateConstraintEngineCode(GrcManager * /*pcman*/, int fxdRuleVersion,
	std::vector<gr::byte> & vbOutput,
	int irit, std::vector<int> & viritInput, int iritFirstModItem)
{
	if (!m_pexpConstraint)
	{
		return;
	}

	bool fNeedAnd = (vbOutput.size() > 0);	// need to 'and' rule item constraints with
											// -if- condition(s)

	bool fInserting = (m_psymInput->FitsSymbolType(ksymtSpecialUnderscore));
	Assert(!fInserting || dynamic_cast<GdlSubstitutionItem *>(this));

	char iritByte = viritInput[irit];
	Assert((int)iritByte == viritInput[irit]);	// no truncation error
	Assert(viritInput[irit] >= 0);	// not an inserted item

	vbOutput.push_back(kopCntxtItem);
	vbOutput.push_back(iritByte - iritFirstModItem);
	vbOutput.push_back(0); // place holder
	int ibSkipLoc = vbOutput.size();
	int nBogus;
	m_pexpConstraint->GenerateEngineCode(fxdRuleVersion, vbOutput, irit, &viritInput, irit,
		fInserting, -1, &nBogus);

	//	Go back and fill in number of bytes to skip if we are not at the 
	//	appropriate context item.
	vbOutput[ibSkipLoc - 1] = vbOutput.size() - ibSkipLoc;

	if (fNeedAnd)
		vbOutput.push_back(kopAnd);
}

/*----------------------------------------------------------------------------------------------
	Generate the engine code for the constraints of a pass.
----------------------------------------------------------------------------------------------*/
void GdlPass::GenerateEngineCode(GrcManager * /*pcman*/, int fxdRuleVersion,
	std::vector<gr::byte> & vbOutput)
{
	if (m_vpexpConstraints.size() == 0)
	{
		return;
	}

	//	'and' all the constraints together; multiple constraints result from an -else if-
	//	structure.
	int nBogus;
	m_vpexpConstraints[0]->GenerateEngineCode(fxdRuleVersion, vbOutput,
		-1, NULL, -1, false, -1, &nBogus);
	for (size_t ipexp = 1; ipexp < m_vpexpConstraints.size(); ipexp++)
	{
		m_vpexpConstraints[ipexp]->GenerateEngineCode(fxdRuleVersion, vbOutput,
			-1, NULL, -1, false, -1, &nBogus);
		vbOutput.push_back(kopAnd);
	}
	vbOutput.push_back(kopPopRet);
}

/*----------------------------------------------------------------------------------------------
	Generate engine code to perform the actions for a given item.
----------------------------------------------------------------------------------------------*/
void GdlRuleItem::GenerateActionEngineCode(GrcManager * /*pcman*/, int /*fxdRuleVersion*/,
	std::vector<gr::byte> & vbOutput,
	GdlRule * /*prule*/, int /*irit*/, bool * pfSetInsertToFalse)
{
	if (*pfSetInsertToFalse)
	{
		vbOutput.push_back(kopPutCopy);
		vbOutput.push_back(0);
		GenerateInsertEqualsFalse(vbOutput);
		*pfSetInsertToFalse = false;
		vbOutput.push_back(kopNext);
	}
	else
		//	Nothing special is happening; just pass the item through unchanged.
		vbOutput.push_back(kopCopyNext);
}

/*--------------------------------------------------------------------------------------------*/
void GdlSetAttrItem::GenerateActionEngineCode(GrcManager * pcman, int fxdRuleVersion,
	std::vector<gr::byte> & vbOutput,
	GdlRule * /*prule*/, int irit, bool * pfSetInsertToFalse)
{
	if (m_vpavs.size() == 0 && !*pfSetInsertToFalse)
		vbOutput.push_back(kopCopyNext);
	else
	{
		int nIIndex = m_nInputIndex;
		nIIndex = (nIIndex < 0) ? (nIIndex + 1) * -1 : nIIndex;

		vbOutput.push_back(kopPutCopy);
		vbOutput.push_back(0);
		if (*pfSetInsertToFalse)
			GenerateInsertEqualsFalse(vbOutput);
		*pfSetInsertToFalse = GenerateAttrSettingCode(pcman, fxdRuleVersion, vbOutput,
			irit, nIIndex);
		vbOutput.push_back(kopNext);
	}
}

/*--------------------------------------------------------------------------------------------*/
void GdlSubstitutionItem::GenerateActionEngineCode(GrcManager * pcman, int fxdRuleVersion,
	std::vector<gr::byte> & vbOutput,
	GdlRule * prule, int irit, bool * pfSetInsertToFalse)
{
	bool fInserting = (m_psymInput->FitsSymbolType(ksymtSpecialUnderscore));
	bool fDeleting = (m_psymOutput->FitsSymbolType(ksymtSpecialUnderscore));
	Assert(!fInserting || !fDeleting);

	int nIIndex = m_nInputIndex;
	nIIndex = (nIIndex < 0) ? (nIIndex + 1) * -1 : nIIndex;
	if (fInserting)
		//	Because we haven't "got" the current slot yet (when inserting we do a peek,
		//	not a "get").
		nIIndex--;

	//	Generate the code to insert, delete, replace, etc.
	if (fDeleting)
		//	Note that it's kind of strange to be setting attributes or associations for 
		//	deleted objects, but it's not an error and we've already given a warning.
		vbOutput.push_back(kopDelete);
	else
	{
		if (fInserting)
			vbOutput.push_back(kopInsert);

		if (m_psymOutput->FitsSymbolType(ksymtSpecialAt))
		{
			//	Direct copy.
			int bOffset = (m_nSelector == -1) ? 0 : m_nSelector - nIIndex;
			Assert((abs(bOffset) & 0xFFFFFF00) == 0);	// check for truncation error
			vbOutput.push_back(kopPutCopy);
			vbOutput.push_back((char)bOffset);
		}
		else
		{
			Assert(m_psymOutput->FitsSymbolType(ksymtClass));
			GdlGlyphClassDefn * pglfcOutput = m_psymOutput->GlyphClassDefnData();
			Assert(pglfcOutput);

			int op;
			int nSel = (m_pexpSelector) ? m_pexpSelector->SlotNumber() - 1 : irit;
			GdlRuleItem * pritSel = prule->Item(nSel);
			Symbol psymSel = pritSel->m_psymInput;
			GdlGlyphClassDefn * pglfcSel = psymSel->GlyphClassDefnData();
			//	We're not doing a substitution based on correspondences within classes, but
			//	rather a simple replacement, under the following circumstances:
			if (psymSel->FitsSymbolType(ksymtSpecialUnderscore))
				//	(a) there is no selector class
				op = kopPutGlyph;
			else if (pglfcSel && pglfcSel->GlyphIDCount() == 0)
				//	(b) the selector class has no glyphs
				op = kopPutGlyph;
			else if (pglfcOutput && pglfcOutput->GlyphIDCount() <= 1)
				//	(c) there is only one glyph in the output class
				op = kopPutGlyph;
			else
			{
				//	Otherwise we're doing a replacement of a glyph from the selector class
				//	with the corresponding glyph from the output class.
				Assert(pglfcSel);
				op = kopPutSubs;
			}
			if (fxdRuleVersion <= 0x00020000)
			{
				// Use old 8-bit versions of these commands.
				switch (op)
				{
				case kopPutGlyph:	op = kopPutGlyphV1_2;	break;
				case kopPutSubs:	op = kopPutSubsV1_2;	break;
				default: break;
				}
			}
			vbOutput.push_back(op);

			int nOutputID = pglfcOutput->ReplcmtOutputID();
			Assert(nOutputID >= 0);

			switch (op)
			{
			case kopPutGlyph:
				vbOutput.push_back(nOutputID >> 8);
				vbOutput.push_back(nOutputID & 0x000000FF);
				break;
			case kopPutGlyphV1_2:
				vbOutput.push_back(nOutputID);
				break;
			case kopPutSubs:
			case kopPutSubsV1_2:
				{
					int nSelIO = (m_nSelector == -1) ? nIIndex : m_nSelector;
					int bSelOffset = nSelIO - nIIndex;
					Assert((abs(bSelOffset) & 0xFFFF0000) == 0);	// check for truncation error

					Assert(pglfcSel->ReplcmtInputID() >= 0);

					vbOutput.push_back((char)bSelOffset);

					int nInputID = pglfcSel->ReplcmtInputID();
					if (op == kopPutSubsV1_2)
					{
						vbOutput.push_back(nInputID);
						vbOutput.push_back(nOutputID);
					}
					else
					{
						vbOutput.push_back(nInputID >> 8);
						vbOutput.push_back(nInputID & 0x000000FF);
						vbOutput.push_back(nOutputID >> 8);
						vbOutput.push_back(nOutputID & 0x000000FF);
					}
					break;
				}
			default:
				Assert(false);
				break;
			}
		}
	}

	//	Generate the code to set the associations.
	if (m_vnAssocs.size() > 0)
	{
		vbOutput.push_back(kopAssoc);
		vbOutput.push_back(m_vnAssocs.size());
		for (size_t in = 0; in < m_vnAssocs.size(); in++)
		{
			Assert(m_vnAssocs[in] >= 0);	// can't associate with an inserted item
			int bAssocOffset = m_vnAssocs[in] - nIIndex;
			Assert((abs(bAssocOffset) & 0xFFFFFF00) == 0);	// check for truncation error

			vbOutput.push_back((char)bAssocOffset);
		}
	}

	//	Generate the code to set the attributes.
	if (*pfSetInsertToFalse)
		GenerateInsertEqualsFalse(vbOutput);

	*pfSetInsertToFalse = GenerateAttrSettingCode(pcman, fxdRuleVersion, vbOutput, irit, nIIndex);

	//	Go on to the next slot.
	vbOutput.push_back(kopNext);
}


/*----------------------------------------------------------------------------------------------
	Generate engine code to set slot attributes. Return true if we need to set
	insert = false on the following item (because this item makes a forward attachment).
----------------------------------------------------------------------------------------------*/
bool GdlSetAttrItem::GenerateAttrSettingCode(GrcManager * pcman, int fxdRuleVersion,
	std::vector<gr::byte> & vbOutput,
	int irit, int nIIndex)
{
	bool fAttachForward = false;
	for (size_t ipavs = 0; ipavs < m_vpavs.size(); ipavs++)
	{
		if (m_vpavs[ipavs]->GenerateAttrSettingCode(pcman, fxdRuleVersion, vbOutput,
			irit, nIIndex, AttachTo()))
		{
			fAttachForward = true;
		}
	}
	return fAttachForward;
}

/*--------------------------------------------------------------------------------------------*/
bool GdlAttrValueSpec::GenerateAttrSettingCode(GrcManager * pcman, int fxdRuleVersion,
	std::vector<gr::byte> & vbOutput,
	int irit, int nIIndex, int iritAttachTo)
{
	bool fAttachForward = false;

	int nBogus;

	Assert(m_psymName->FitsSymbolType(ksymtSlotAttr));
	Assert(m_pexpValue);
	ExpressionType expt = m_psymName->ExpType();
	Assert(expt == kexptSlotRef || expt == kexptNumber ||
		expt == kexptMeas || expt == kexptBoolean);
	std::string staOp = m_psymOperator->FullName();
	int slat = m_psymName->SlotAttrEngineCodeOp();

	if (m_psymName->IsIndexedSlotAttr())	// eg, component.XXX.ref, user1
	{
		m_pexpValue->GenerateEngineCode(fxdRuleVersion, vbOutput,
			irit, NULL, nIIndex, false, -1, &nBogus);

		if (m_psymName->IsComponentRef() || pcman->VersionForTable(ktiSilf) < 0x00020000)
		{
			Assert(staOp == "=");
			vbOutput.push_back(kopIAttrSetSlot);
		}
		else if (m_psymName->IsUserDefinableSlotAttr())
		{
			if (staOp == "=")
				vbOutput.push_back(kopIAttrSet);
			else if (staOp == "+=")
				vbOutput.push_back(kopIAttrAdd);
			else if (staOp == "-=")
				vbOutput.push_back(kopIAttrSub);
			else
			{
				Assert(false);
			}
		}
		vbOutput.push_back(slat);
		vbOutput.push_back(pcman->SlotAttributeIndex(m_psymName));
	}
	else if (expt == kexptSlotRef)
	{
		Assert(staOp == "=");
		int nValue;
		if (slat == kslatAttTo && iritAttachTo == -1)
		{
			// attach.to = @0 means no attachment
		}
		else
		{
			m_pexpValue->GenerateEngineCode(fxdRuleVersion, vbOutput, irit, NULL, nIIndex,
				false, iritAttachTo, &nValue);

			vbOutput.push_back(kopAttrSetSlot);
			vbOutput.push_back(slat);

			if (slat == kslatAttTo)
			{
				if (nValue < 0)
					GdlRuleItem::GenerateInsertEqualsFalse(vbOutput);	// for this slot
				else if (nValue > 0)
					fAttachForward = true;	// generate insert = false for next slot
			}
		}
	}
	else
	{
		bool fAttachAt =
			(slat == kslatAttAtX || slat == kslatAttAtY || slat == kslatAttAtGpt ||
				slat == kslatAttAtXoff || slat == kslatAttAtYoff);

		int op = kopNop;
		if (staOp == "=")
			op = kopAttrSet;
		else if (staOp == "+=")
			op = kopAttrAdd;
		else if (staOp == "-=")
			op = kopAttrSub;
		else
		{
			Assert(false);
		}

		m_pexpValue->GenerateEngineCode(fxdRuleVersion, vbOutput, irit, NULL, nIIndex,
			fAttachAt, iritAttachTo, &nBogus);

		vbOutput.push_back(op);
		vbOutput.push_back(slat);

	}

	return fAttachForward;
}

/*----------------------------------------------------------------------------------------------
	Generate the extra "insert = false" for attachments.
----------------------------------------------------------------------------------------------*/
void GdlRuleItem::GenerateInsertEqualsFalse(std::vector<gr::byte> & vbOutput)
{
	vbOutput.push_back(kopPushByte);
	vbOutput.push_back(0);	// false;
	vbOutput.push_back(kopAttrSet);
	vbOutput.push_back(kslatInsert);
}


/*----------------------------------------------------------------------------------------------
	Return the component ID for a given ligature component symbol, or the number cooresponding
	to the index of the user-definable slot attribute.
----------------------------------------------------------------------------------------------*/
int GrcManager::SlotAttributeIndex(Symbol psym)
{
	if (psym->IsComponentRef())
	{
		Symbol psymBase = psym->BaseLigComponent();
		if (!psymBase->IsGeneric())
			psymBase = psymBase->Generic();
		Assert(psymBase->IsGeneric());

		int nIDRet = psymBase->InternalID();
		Assert(nIDRet > -1);
		return nIDRet;
	}
	else if (psym->IsUserDefinableSlotAttr())
	{
		return psym->UserDefinableSlotAttrIndex();
	}
	else
	{
		//	No other kinds of indexed attributes so far.
		Assert(false);
	}
	return 0;
}


/*----------------------------------------------------------------------------------------------
	Analyze the passes to calculate the maximum number of characters before and after
	the official range of a segment that could cause the segment to become invalid.
	Also set a simple flag if any line-break items occur in any rule.
----------------------------------------------------------------------------------------------*/
void GrcManager::CalculateContextOffsets()
{
	m_prndr->CalculateContextOffsets();
}

/*--------------------------------------------------------------------------------------------*/
void GdlRenderer::CalculateContextOffsets()
{
	m_fLineBreak = false;
	m_fLineBreakB4Just = false;
	m_critPreXlbContext = 0;
	m_critPostXlbContext = 0;

	GdlRuleTable * prultblSub = FindRuleTable("substitution");
	GdlRuleTable * prultblJust= FindRuleTable("justification");
	GdlRuleTable * prultblPos = FindRuleTable("positioning");

	//	Don't need to do this for the linebreak table, since conceptually it occurs
	//	before the linebreaks have been made.

	if (prultblSub)
		prultblSub->CalculateContextOffsets(&m_critPreXlbContext, &m_critPostXlbContext,
			&m_fLineBreak, false, NULL, NULL);

	m_fLineBreakB4Just = m_fLineBreak;

	if (prultblJust)
		prultblJust->CalculateContextOffsets(&m_critPreXlbContext, &m_critPostXlbContext,
			&m_fLineBreak, false, prultblSub, NULL);

	if (prultblPos)
		prultblPos->CalculateContextOffsets(&m_critPreXlbContext, &m_critPostXlbContext,
			&m_fLineBreak, true, prultblSub, prultblJust);
}

/*--------------------------------------------------------------------------------------------*/
void GdlRuleTable::CalculateContextOffsets(int * pcPreXlbContext, int * pcPostXlbContext,
	bool * pfLineBreak, bool fPos, GdlRuleTable * prultbl1, GdlRuleTable * prultbl2)
{
	if (*pcPreXlbContext == kInfiniteXlbContext && *pcPostXlbContext == kInfiniteXlbContext)
	{
		Assert(*pfLineBreak);
		*pfLineBreak = true;
		return;
	}

	for (size_t ipass = 0; ipass < m_vppass.size(); ipass++)
	{
		GdlPass * ppass = m_vppass[ipass];

		if (ppass->HasLineBreaks())
			*pfLineBreak = true;

		//	If no cross-line-boundary rules in this pass, ignore it.
		if (!ppass->HasCrossLineContext())
			continue;

		if (ppass->HasReprocessing())
		{
			//	This pass has reprocessing occurring: return values indicating that we
			//	can't determine a context limit.
			*pcPreXlbContext = kInfiniteXlbContext;
			*pcPostXlbContext = kInfiniteXlbContext;
			return;
		}

		int cPreTmp = ppass->MaxPreLBSlots();
		int cPostTmp = ppass->MaxPostLBSlots();

		//	Loop backwards through all the passes in this table, calculating the ranges.
		for (int ipassPrev = ipass; ipassPrev-- > 0; )
		{
			GdlPass * ppassPrev = m_vppass[ipassPrev];
			if (fPos)
			{
				cPreTmp = std::max(cPreTmp, ppassPrev->MaxPreLBSlots());
				cPostTmp = std::max(cPostTmp, ppassPrev->MaxPostLBSlots());
			}
			else
			{
				if (ppassPrev->HasReprocessing())
				{
					//	Previous pass has reprocessing occurring: return values indicating that
					//	we can't determine a context limit.
					*pcPreXlbContext = kInfiniteXlbContext;
					*pcPostXlbContext = kInfiniteXlbContext;
					return;
				}
				//	For the substitution table, multiply the range by the max number of context
				//	items in the pass.
				cPreTmp = cPreTmp * ppassPrev->MaxRuleContext();
				cPostTmp = cPostTmp * ppassPrev->MaxRuleContext();
			}
		}

		//	Loop backwards through the previous table(s) also.
		for (int itbl = 2; itbl > 0; itbl--)
		{
			GdlRuleTable * prultblPrev = ((itbl == 2) ? prultbl2 : prultbl1);
			if (prultblPrev)
			{
				for (int ipassPrev = prultblPrev->NumberOfPasses(); ipassPrev-- > 0; )
				{
					GdlPass * ppassPrev = prultblPrev->m_vppass[ipassPrev];

					if (ppassPrev->HasReprocessing())
					{
						//	Previous pass has reprocessing occurring:
						//	return values indicating that
						//	we can't determine a context limit.
						*pcPreXlbContext = kInfiniteXlbContext;
						*pcPostXlbContext = kInfiniteXlbContext;
						return;
					}
					cPreTmp = cPreTmp * ppassPrev->MaxRuleContext();
					cPostTmp = cPostTmp * ppassPrev->MaxRuleContext();
				}
			}
		}

		*pcPreXlbContext = max(*pcPreXlbContext, cPreTmp);
		*pcPostXlbContext = max(*pcPostXlbContext, cPostTmp);
	}
}


/***********************************************************************************************
	Debuggers
***********************************************************************************************/
/*----------------------------------------------------------------------------------------------
	Output a list of rules ordered by precedence.
----------------------------------------------------------------------------------------------*/
void GrcManager::DebugRulePrecedence()
{
	std::ofstream strmOut;
	strmOut.open("dbg_ruleprec.txt");
	if (strmOut.fail())
	{
		g_errorList.AddError(6101, NULL,
			"Error in writing to file ",
			"dbg_ruleprec.txt");
		return;
	}

	if (g_errorList.AnyFatalErrors())
		strmOut << "Fatal errors--compilation aborted";
	else
	{
		strmOut << "RULE PRECEDENCE\n\n";
		m_prndr->DebugRulePrecedence(this, strmOut);
	}

	strmOut.close();
}
/*--------------------------------------------------------------------------------------------*/
void GdlRenderer::DebugRulePrecedence(GrcManager * pcman, std::ostream & strmOut)
{
	GdlRuleTable * prultbl;

	if ((prultbl = FindRuleTable("linebreak")) != NULL)
		prultbl->DebugRulePrecedence(pcman, strmOut);

	if ((prultbl = FindRuleTable("substitution")) != NULL)
		prultbl->DebugRulePrecedence(pcman, strmOut);

	if (m_iPassBidi > -1)
		strmOut << "\nPASS " << m_iPassBidi + 1 << ": bidi\n";

	if ((prultbl = FindRuleTable("justification")) != NULL)
		prultbl->DebugRulePrecedence(pcman, strmOut);

	if ((prultbl = FindRuleTable("positioning")) != NULL)
		prultbl->DebugRulePrecedence(pcman, strmOut);
}

void GdlRuleTable::DebugRulePrecedence(GrcManager * pcman, std::ostream & strmOut)
{
	strmOut << "\nTABLE: " << m_psymName->FullName() << "\n";
	for (size_t ippass = 0; ippass < m_vppass.size(); ippass++)
	{
		m_vppass[ippass]->DebugRulePrecedence(pcman, strmOut);
	}
}

/*--------------------------------------------------------------------------------------------*/
void GdlPass::DebugRulePrecedence(GrcManager * pcman, std::ostream & strmOut)
{
	if (m_vprule.size() == 0)
		return;

	Assert(PassDebuggerNumber() != 0);

	strmOut << "\nPASS: " << PassDebuggerNumber() << "\n";

	// Sort rules by their precedence: primarily by the number of items matched (largest first),
	// and secondarily by their location in the file (rule number--smallest first).
	std::vector<int> viruleSorted;
	std::vector<int> vnKeys;
	for (size_t irule1 = 0; irule1 < m_vprule.size(); irule1++)
	{
		int nSortKey1 = m_vprule[irule1]->SortKey();
		size_t iirule2;
		for (iirule2 = 0; iirule2 < viruleSorted.size(); iirule2++)
		{
			int nSortKey2 = vnKeys[iirule2];
			if (nSortKey1 > nSortKey2 ||
				(nSortKey1 == nSortKey2 && signed(irule1) < viruleSorted[iirule2]))
			{
				// Insert it.
				viruleSorted.insert(viruleSorted.begin() + iirule2, irule1);
				vnKeys.insert(vnKeys.begin() + iirule2, nSortKey1);
				break;
			}
		}
		if (iirule2 >= viruleSorted.size())
		{
			viruleSorted.push_back(irule1);
			vnKeys.push_back(nSortKey1);
		}

		Assert(viruleSorted.size() == irule1 + 1);
	}

	int nPassNum = PassDebuggerNumber();
	for (size_t iirule = 0; iirule < m_vprule.size(); iirule++)
	{
		strmOut << "\n" << iirule << " - RULE " << nPassNum << "." << viruleSorted[iirule] << ", ";
		m_vprule[viruleSorted[iirule]]->LineAndFile().WriteToStream(strmOut, true);
		strmOut << ":  ";

		m_vprule[viruleSorted[iirule]]->RulePrettyPrint(pcman, strmOut, false);
		strmOut << "\n\n";
	}
}

/*----------------------------------------------------------------------------------------------
	Output a text version of the engine code to the stream.
----------------------------------------------------------------------------------------------*/
void GrcManager::DebugEngineCode()
{
	std::ofstream strmOut;
	strmOut.open("dbg_enginecode.txt");
	if (strmOut.fail())
	{
		g_errorList.AddError(6102, NULL,
			"Error in writing to file ",
			"dbg_enginecode.txt");
		return;
	}

	if (g_errorList.AnyFatalErrors())
		strmOut << "Fatal errors--compilation aborted";
	else
	{
		strmOut << "ENGINE CODE FOR RULES\n\n";
		m_prndr->DebugEngineCode(this, strmOut);
	}

	strmOut.close();
}

/*--------------------------------------------------------------------------------------------*/
void GdlRenderer::DebugEngineCode(GrcManager * pcman, std::ostream & strmOut)
{
	GdlRuleTable * prultbl;

	int fxdRuleVersion = pcman->VersionForRules();

	if ((prultbl = FindRuleTable("linebreak")) != NULL)
		prultbl->DebugEngineCode(pcman, fxdRuleVersion, strmOut);

	if ((prultbl = FindRuleTable("substitution")) != NULL)
		prultbl->DebugEngineCode(pcman, fxdRuleVersion, strmOut);

	if (m_iPassBidi > -1)
		strmOut << "\nPASS " << m_iPassBidi + 1 << ": bidi\n";

	if ((prultbl = FindRuleTable("justification")) != NULL)
		prultbl->DebugEngineCode(pcman, fxdRuleVersion, strmOut);

	if ((prultbl = FindRuleTable("positioning")) != NULL)
		prultbl->DebugEngineCode(pcman, fxdRuleVersion, strmOut);
}

/*--------------------------------------------------------------------------------------------*/
void GdlRuleTable::DebugEngineCode(GrcManager * pcman, int fxdRuleVersion, std::ostream & strmOut)
{
	strmOut << "\nTABLE: " << m_psymName->FullName() << "\n";
	for (size_t ippass = 0; ippass < m_vppass.size(); ippass++)
	{
		m_vppass[ippass]->DebugEngineCode(pcman, fxdRuleVersion, strmOut);
	}
}

/*--------------------------------------------------------------------------------------------*/
void GdlPass::DebugEngineCode(GrcManager * pcman, int fxdRuleVersion, std::ostream & strmOut)
{
	int nPassNum = PassDebuggerNumber();
	strmOut << "\nPASS: " << nPassNum << "\n";

	std::vector<gr::byte> vbPassConstraints;
	GenerateEngineCode(pcman, fxdRuleVersion, vbPassConstraints);
	if (vbPassConstraints.size() == 0)
	{
		strmOut << "\nPASS CONSTRAINTS: none\n";
	}
	else
	{
		strmOut << "\nPASS CONSTRAINTS:\n";
		GdlRule::DebugEngineCode(vbPassConstraints, fxdRuleVersion, strmOut);
	}

	for (size_t iprul = 0; iprul < m_vprule.size(); iprul++)
	{
		strmOut << "\nRULE " << nPassNum << "." << iprul << ", ";
		m_vprule[iprul]->LineAndFile().WriteToStream(strmOut, true);
		strmOut << ":  ";

		m_vprule[iprul]->RulePrettyPrint(pcman, strmOut, false);
		strmOut << "\n";
		m_vprule[iprul]->DebugEngineCode(pcman, fxdRuleVersion, strmOut);
	}
}

/*--------------------------------------------------------------------------------------------*/
void GdlRule::DebugEngineCode(GrcManager * pcman, int fxdRuleVersion, std::ostream & strmOut)
{
	std::vector<gr::byte> vbActions;
	std::vector<gr::byte> vbConstraints;

	GenerateEngineCode(pcman, fxdRuleVersion, vbActions, vbConstraints);

	strmOut << "\nACTIONS:\n";
	DebugEngineCode(vbActions, fxdRuleVersion, strmOut);

	if (vbConstraints.size() == 0)
	{
		strmOut << "\nCONSTRAINTS: none\n";
	}
	else
	{
		strmOut << "\nCONSTRAINTS:\n";
		DebugEngineCode(vbConstraints, fxdRuleVersion, strmOut);
	}
}

void GdlRule::DebugEngineCode(std::vector<gr::byte> & vb, int /*fxdRuleVersion*/, std::ostream & strmOut)
{
	int ib = 0;
	while (ib < signed(vb.size()))
	{
		int op = vb[ib++];
		strmOut << EngineCodeDebugString(op);

		int cbArgs = 0;
		int slat;
		unsigned int nUnsigned;
		int nSigned;
		signed short int nSignedShort;
		int gmet;
		int pstat;
		switch (op)
		{
		case kopNop:				cbArgs = 0;		break;
		case kopPushByte:			cbArgs = 1;		break;
		case kopPushByteU:
			nUnsigned = (unsigned int)vb[ib++];
			strmOut << " " << nUnsigned;
			break;
		case kopPushShort:
			nSignedShort = (signed short int)vb[ib++];
			nSignedShort = (nSignedShort << 8) + vb[ib++];
			nSigned = (signed int)nSignedShort;
			strmOut << " " << nSigned;
			break;
		case kopPushShortU:
			nUnsigned = (unsigned int)vb[ib++];
			nUnsigned = (nUnsigned << 8) + vb[ib++];
			strmOut << " " << nUnsigned;
			break;
		case kopPushLong:
			nUnsigned = (int)vb[ib++];
			nUnsigned = (nUnsigned << 8) + vb[ib++];
			nUnsigned = (nUnsigned << 8) + vb[ib++];
			nUnsigned = (nUnsigned << 8) + vb[ib++];
			nSigned = (signed int)nUnsigned;
			strmOut << " " << nSigned;
			break;
		case kopAdd:				cbArgs = 0;		break;
		case kopSub:				cbArgs = 0;		break;
		case kopMul:				cbArgs = 0;		break;
		case kopDiv:				cbArgs = 0;		break;
		case kopMin:				cbArgs = 0;		break;
		case kopMax:				cbArgs = 0;		break;
		case kopNeg:				cbArgs = 0;		break;
		case kopTrunc8:				cbArgs = 0;		break;
		case kopTrunc16:			cbArgs = 0;		break;
		case kopCond:				cbArgs = 0;		break;
		case kopAnd:				cbArgs = 0;		break;
		case kopOr:					cbArgs = 0;		break;
		case kopNot:				cbArgs = 0;		break;
		case kopEqual:				cbArgs = 0;		break;
		case kopNotEq:				cbArgs = 0;		break;
		case kopLess:				cbArgs = 0;		break;
		case kopGtr:				cbArgs = 0;		break;
		case kopLessEq:				cbArgs = 0;		break;
		case kopGtrEq:				cbArgs = 0;		break;
		case kopNext:				cbArgs = 0;		break;
		case kopNextN:				cbArgs = 1;		break;	// N
		case kopCopyNext:			cbArgs = 0;		break;
		case kopPutGlyphV1_2:
			nUnsigned = (unsigned int)vb[ib++];	// output class
			strmOut << " " << nUnsigned;
			cbArgs = 0;
			break;
		case kopPutSubsV1_2:
			nSigned = (signed int)vb[ib++];		// selector
			strmOut << " " << nSigned;
			nUnsigned = (unsigned int)vb[ib++];	// input class
			strmOut << " " << nUnsigned;
			nUnsigned = (unsigned int)vb[ib++];	// output class
			strmOut << " " << nUnsigned;
			cbArgs = 0;
			break;
		case kopPutCopy:			cbArgs = 1;		break;	// selector
		case kopInsert:				cbArgs = 0;		break;
		case kopDelete:				cbArgs = 0;		break;

		case kopPutGlyph:
			nSignedShort = (signed short int)vb[ib++];
			nSignedShort = (nSignedShort << 8) + vb[ib++];
			nSigned = (signed int)nSignedShort;
			strmOut << " " << nSigned;	// output class
			break;

		case kopPutSubs3:
			nSigned = (int)vb[ib++];
			strmOut << " " << nSigned;	// slot offset
			nSignedShort = (signed short int)vb[ib++];
			nSignedShort = (nSignedShort << 8) + vb[ib++];
			nSigned = (signed int)nSignedShort;
			strmOut << " " << nSigned;	// input class
			// fall through
		case kopPutSubs2:
			nSigned = (int)vb[ib++];
			strmOut << " " << nSigned;	// slot offset
			nSignedShort = (signed short int)vb[ib++];
			nSignedShort = (nSignedShort << 8) + vb[ib++];
			nSigned = (signed int)nSignedShort;
			strmOut << " " << nSigned;	// input class
			// fall through
		case kopPutSubs:
			nSigned = (int)vb[ib++];
			strmOut << " " << nSigned;	// slot offset
			nSignedShort = (signed short int)vb[ib++];
			nSignedShort = (nSignedShort << 8) + vb[ib++];
			nSigned = (signed int)nSignedShort;
			strmOut << " " << nSigned;	// input class
			nSignedShort = (signed short int)vb[ib++];
			nSignedShort = (nSignedShort << 8) + vb[ib++];
			nSigned = (signed int)nSignedShort;
			strmOut << " " << nSigned;	// output class
			break;

		case kopAssoc:
			cbArgs = vb[ib++];
			strmOut << " " << cbArgs;
			break;
		case kopCntxtItem:			cbArgs = 2;		break;

		case kopAttrSet:
		case kopAttrAdd:
		case kopAttrSub:
		case kopAttrSetSlot:
			slat = vb[ib++];
			strmOut << " " << SlotAttributeDebugString(slat);
			cbArgs = 0;
			break;
		case kopIAttrSet:
		case kopIAttrAdd:
		case kopIAttrSub:
		case kopIAttrSetSlot:
			slat = vb[ib++];
			strmOut << " " << SlotAttributeDebugString(slat);
			cbArgs = 1;
			break;
		case kopPushSlotAttr:
			slat = vb[ib++];
			strmOut << " " << SlotAttributeDebugString(slat);
			cbArgs = 1;	// selector
			break;
		case kopPushISlotAttr:
			slat = vb[ib++];
			strmOut << " " << SlotAttributeDebugString(slat);
			cbArgs = 2;	// selector, index
			break;
		case kopPushGlyphAttr:
		case kopPushAttToGlyphAttr:
			nSignedShort = (signed short int)vb[ib++];
			nSignedShort = (nSignedShort << 8) + vb[ib++];
			nSigned = (signed int)nSignedShort;
			strmOut << " " << nSigned;	// glyph attribute
			cbArgs = 1;					// selector
			break;
		case kopPushGlyphAttrV1_2:
		case kopPushAttToGAttrV1_2:
			nUnsigned = (unsigned int)vb[ib++]; // glyph attribute
			strmOut << " " << nUnsigned;
			cbArgs = 1;							// selector
			break;
		case kopPushGlyphMetric:
		case kopPushAttToGlyphMetric:
			gmet = vb[ib++];
			strmOut << " " << GlyphMetricDebugString(gmet);
			cbArgs = 2;	// selector, cluster
			break;
		case kopPushFeat:			cbArgs = 2;		break;	// feature, selector
		//case kopPushIGlyphAttr:	cbArgs = 2;		break;	// glyph attr, index
		case kopPushProcState:
			pstat = vb[ib++];
			strmOut << " " << ProcessStateDebugString(pstat);
			cbArgs = 0;
			break;
		case kopPushVersion:		cbArgs = 0;		break;
		case kopPopRet:				cbArgs = 0;		break;
		case kopRetZero:			cbArgs = 0;		break;
		case kopRetTrue:			cbArgs = 0;		break;
		default:
			Assert(false);
			cbArgs = 0;
		}

		// This loop handles only 8-bit signed values.
		for (int iTmp = 0; iTmp < cbArgs; iTmp++)
		{
			int n = (char)vb[ib++];
			strmOut << " " << n;
		}
		strmOut << "\n";
	}
}


/*----------------------------------------------------------------------------------------------
	Return the text equivalent of the given slot attribute.
----------------------------------------------------------------------------------------------*/
std::string GdlRule::SlotAttributeDebugString(int slat)
{
	std::string sta("bad-slot-attr-");
	switch (slat)
	{
	case kslatAdvX:				return "advance_x";
	case kslatAdvY:				return "advance_y";
	case kslatAttTo:			return "attach_to";
	case kslatAttAtX:			return "attach_at_x";
	case kslatAttAtY:			return "attach_at_y";
	case kslatAttAtGpt:			return "attach_at_gpoint";
	case kslatAttAtXoff:		return "attach_at_xoffset";
	case kslatAttAtYoff:		return "attach_at_yoffset";
	case kslatAttWithX:			return "attach_with_x";
	case kslatAttWithY:			return "attach_with_y";
	case kslatAttWithGpt:		return "attach_with_gpoint";
	case kslatAttWithXoff:		return "attach_with_xoffset";
	case kslatAttWithYoff:		return "attach_with_yoffset";
	case kslatAttLevel:			return "attach_level";
	case kslatBreak:			return "break";
	case kslatCompRef:			return "comp_ref";
	case kslatDir:				return "dir";
	case kslatInsert:			return "insert";
	case kslatPosX:				return "pos_x";
	case kslatPosY:				return "pos_y";
	case kslatShiftX:			return "shift_x";
	case kslatShiftY:			return "shift_y";
	case kslatUserDefnV1:		return "user";
	case kslatUserDefn:			return "user";
	case kslatMeasureSol:		return "measure_startofline";
	case kslatMeasureEol:		return "measure_endofline";
	case kslatJStretch:			return "justify_stretch";
	case kslatJShrink:			return "justify_shrink";
	case kslatJStep:			return "justify_step";
	case kslatJWeight:			return "justify_weight";
	case kslatJWidth:			return "justify_width";
	case kslatSegSplit:			return "segsplit";
	default:
		Assert(false);
		char rgch[20];
		itoa(slat, rgch, 10);
		sta += rgch;
		return sta;
	}
}


/*----------------------------------------------------------------------------------------------
	Return the text equivalent of the given glyph metric.
----------------------------------------------------------------------------------------------*/
std::string GdlRule::GlyphMetricDebugString(int gmet)
{
	std::string sta("bad-glyph-metric-");
	switch (gmet)
	{
	case kgmetLsb:				return "lsb";
	case kgmetRsb:				return "rsb";
	case kgmetBbTop:			return "bb_top";
	case kgmetBbBottom:			return "bb_bottom";
	case kgmetBbLeft:			return "bb_left";
	case kgmetBbRight:			return "bb_right";
	case kgmetBbHeight:			return "bb_height";
	case kgmetBbWidth:			return "bb_width";
	case kgmetAdvWidth:			return "aw";
	case kgmetAdvHeight:		return "ah";
	case kgmetAscent:			return "ascent";
	case kgmetDescent:			return "descent";
	default:
		Assert(false);
		char rgch[20];
		itoa(gmet, rgch, 10);
		sta += rgch;
		return sta;
	}
}


/*----------------------------------------------------------------------------------------------
	Return the text equivalent of the given engine code operator.
----------------------------------------------------------------------------------------------*/
std::string GdlRule::EngineCodeDebugString(int op)
{
	std::string sta("bad-engine-op-");
	switch (op)
	{
	case kopNop:					return "Nop";
	case kopPushByte:				return "PushByte";
	case kopPushByteU:				return "PushByteU";
	case kopPushShort:				return "PushShort";
	case kopPushShortU:				return "PushShortU";
	case kopPushLong:				return "PushLong";
	case kopAdd:					return "Add";
	case kopSub:					return "Sub";
	case kopMul:					return "Mul";
	case kopDiv:					return "Div";
	case kopMin:					return "Min";
	case kopMax:					return "Max";
	case kopNeg:					return "Neg";
	case kopTrunc8:					return "Trunc8";
	case kopTrunc16:				return "Trunc16";
	case kopCond:					return "Cond";
	case kopAnd:					return "And";
	case kopOr:						return "Or";
	case kopNot:					return "Not";
	case kopEqual:					return "Equal";
	case kopNotEq:					return "NotEq";
	case kopLess:					return "Less";
	case kopGtr:					return "Gtr";
	case kopLessEq:					return "LessEq";
	case kopGtrEq:					return "GtrEq";
	case kopNext:					return "Next";
	case kopNextN:					return "NextN";
	case kopCopyNext:				return "CopyNext";
	case kopPutGlyph:				return "PutGlyph";
	case kopPutGlyphV1_2:			return "PutGlyph(V1&2)";
	case kopPutSubsV1_2:			return "PutSubs(V1&2)";
	case kopPutSubs:				return "PutSubs";
	case kopPutSubs2:				return "PutSubs2";
	case kopPutSubs3:				return "PutSubs3";
	case kopPutCopy:				return "PutCopy";
	case kopInsert:					return "Insert";
	case kopDelete:					return "Delete";
	case kopAssoc:					return "Assoc";
	case kopCntxtItem:				return "CntxtItem";
	case kopAttrSet:				return "AttrSet";
	case kopAttrAdd:				return "AttrAdd";
	case kopAttrSub:				return "AttrSub";
	case kopAttrSetSlot:			return "AttrSetSlot";
	case kopIAttrSetSlot:			return "IAttrSetSlot";
	case kopPushSlotAttr:			return "PushSlotAttr";
	case kopPushISlotAttr:			return "PushISlotAttr";
	case kopPushGlyphAttr:			return "PushGlyphAttr";
	case kopPushGlyphAttrV1_2:		return "PushGlyphAttr(V1&2)";
	case kopPushGlyphMetric:		return "PushGlyphMetric";
	case kopPushFeat:				return "PushFeat";
	case kopPushAttToGlyphAttr:		return "PushAttToGlyphAttr";
	case kopPushAttToGAttrV1_2:		return "PushAttToGlyphAttr(V1&2)";
	case kopPushAttToGlyphMetric:	return "PushAttToGlyphMetric";
	case kopPushIGlyphAttr:			return "PushIGlyphAttr";
	case kopPushVersion:			return "PushVersion";
	case kopPopRet:					return "PopRet";
	case kopRetZero:				return "RetZero";
	case kopRetTrue:				return "RetTrue";
	case kopIAttrSet:				return "IAttrSet";
	case kopIAttrAdd:				return "IAttrAdd";
	case kopIAttrSub:				return "IAttrSub";
	case kopPushProcState:			return "PushProcState";
	default:
		Assert(false);
		char rgch[20];
		itoa(op, rgch, 10);
		sta += rgch;
		return sta;
	}
}

/*----------------------------------------------------------------------------------------------
	Return the text equivalent of the given process-state.
----------------------------------------------------------------------------------------------*/
std::string GdlRule::ProcessStateDebugString(int pstat)
{
	std::string sta("bad-process-state-");
	switch (pstat)
	{
	case kpstatJustifyMode:		return "JustifyMode";
	case kpstatJustifyLevel:	return "JustifyLevel";
	default:
		Assert(false);
		char rgch[20];
		itoa(pstat, rgch, 10);
		sta += rgch;
		return sta;
	}
}

/*----------------------------------------------------------------------------------------------
	Output a list of glyph attributes.
----------------------------------------------------------------------------------------------*/
void GrcManager::DebugGlyphAttributes()
{
	std::ofstream strmOut;
	strmOut.open("dbg_glyphattrs.txt");
	if (strmOut.fail())
	{
		g_errorList.AddError(6103, NULL,
			"Error in Griting to file ",
			"dbg_glyphattrs.txt");
		return;
	}

	Symbol psymBw = m_psymtbl->FindSymbol("breakweight");
	int nAttrIdBw = psymBw->InternalID();
	//Symbol psymJStr = m_psymtbl->FindSymbol(GrcStructName("justify", "0", "stretch"));
	Symbol psymJStr = m_psymtbl->FindSymbol(GrcStructName("justify", "stretch"));
	int nAttrIdJStr = psymJStr->InternalID();

	if (g_errorList.AnyFatalErrors())
		strmOut << "Fatal errors--compilation aborted";
	else
	{
		strmOut << "GLYPH ATTRIBUTE IDS\n\n";
		for (size_t nAttrID = 0; nAttrID < m_vpsymGlyphAttrs.size(); nAttrID++)
		{
			if (m_vpsymGlyphAttrs[nAttrID]->InternalID() == static_cast<int>(nAttrID))
			{
				strmOut << nAttrID << ": "
					<< m_vpsymGlyphAttrs[nAttrID]->FullName() << "\n";
			}
			// else we have something like justify.stretch which is unused
		}
		strmOut << "\n\n\nGLYPH ATTRIBUTE VALUES\n\n";

		for (int wGlyphID = 0; wGlyphID < m_cwGlyphIDs; wGlyphID++)
		{
			// Convert breakweight values depending on the table version to output.
			ConvertBwForVersion(wGlyphID, nAttrIdBw);

			//	Split any large stretch values into two 16-bit words.
			SplitLargeStretchValue(wGlyphID, nAttrIdJStr);
		
			bool fAnyNonZero = false;

			for (size_t nAttrID = 0; nAttrID < m_vpsymGlyphAttrs.size(); nAttrID++)
			{
				int nValue = FinalAttrValue(wGlyphID, nAttrID);

				//	Skip undefined and zero-valued attributes.
				if (nValue == 0)
					continue;

				if (fAnyNonZero == false)
				{
					strmOut << wGlyphID << "  [";
					DebugHex(strmOut, wGlyphID);
					strmOut << "]\n";
				}

				fAnyNonZero = true;

				strmOut << "   " << m_vpsymGlyphAttrs[nAttrID]->FullName()
					<< " = ";
				if (m_vpsymGlyphAttrs[nAttrID]->LastFieldIs("gpoint") &&
					nValue == kGpointZero)
				{
					strmOut << "zero" << "\n";
				}
				else
				{
					strmOut  << nValue;
					if (nValue > 9 || nValue < 0)
					{
						strmOut << " [";
						DebugHex(strmOut, nValue);
						strmOut << "]";
					}
					strmOut << "\n";
				}

			}

			if (fAnyNonZero)
				strmOut << "\n\n";
		}
	}

	strmOut.close();
}


/*----------------------------------------------------------------------------------------------
	Generate a list of glyph attributes (whose indices in the vector match their internal IDs).
----------------------------------------------------------------------------------------------*/
void GrcSymbolTable::GlyphAttrList(std::vector<Symbol> & vpsym)
{
	for (SymbolTableMap::iterator it = EntriesBegin();
		it != EntriesEnd();
		++it)
	{
		Symbol psym = it->second; // GetValue();
		//Symbol psym = it.GetValue();

		if (psym->m_psymtblSubTable)
			psym->m_psymtblSubTable->GlyphAttrList(vpsym);

		else if (psym->IsGeneric() && psym->FitsSymbolType(ksymtGlyphAttr))
		{
			if (psym->InternalID() >= 0)
			{
				while (signed(vpsym.size()) <= psym->InternalID())
					vpsym.push_back(NULL);

				vpsym[psym->InternalID()] = psym;
			}
		}
	}
}


/*----------------------------------------------------------------------------------------------
	Generate a pretty-print description of the rule (similar to the original syntax).
----------------------------------------------------------------------------------------------*/
void GdlRule::RulePrettyPrint(GrcManager * pcman, std::ostream & strmOut, bool fXml)
{
	size_t cEndif = 0;
	for (size_t iexp = 0; iexp < m_vpexpConstraints.size(); iexp++)
	{
		strmOut << "if (";
		m_vpexpConstraints[iexp]->PrettyPrint(pcman, strmOut, fXml);
		strmOut << ") ";
		cEndif++;
	}

	//	Loop through all the items to see if we need a LHS or a context.
	bool fLhs = false;
	bool fContext = (m_nScanAdvance != -1);
	int irit;
	for (irit = 0; irit < signed(m_vprit.size()) ; irit++)
	{
		GdlRuleItem * prit = m_vprit[irit];
		GdlSubstitutionItem * pritsub = dynamic_cast<GdlSubstitutionItem *>(prit);
		if (pritsub)
			fLhs = true;

		GdlSetAttrItem * pritset = dynamic_cast<GdlSetAttrItem *>(prit);
		if (!pritset)
			fContext = true;
		else if (prit->m_pexpConstraint)
			fContext = true;
	}

	if (fLhs)
	{
		for (irit = 0; irit < signed(m_vprit.size()) ; irit++)
		{
			m_vprit[irit]->LhsPrettyPrint(pcman, this, irit, strmOut, fXml);
		}
		if (fXml)
			strmOut << "&gt;  ";
		else
			strmOut << ">  ";
	}

	for (irit = 0; irit < signed(m_vprit.size()) ; irit++)
	{
		m_vprit[irit]->RhsPrettyPrint(pcman, this, irit, strmOut, fXml);
	}

	if (fContext)
	{
		strmOut << " /  ";
		for (irit = 0; irit < signed(m_vprit.size()) ; irit++)
		{
			if (m_nScanAdvance == irit)
				strmOut << "^  ";
			m_vprit[irit]->ContextPrettyPrint(pcman, this, irit, strmOut, fXml);
		}
	}

	strmOut << ";";

	for (size_t i = 0; i < cEndif; i++)
		strmOut << " endif; ";
}

/*--------------------------------------------------------------------------------------------*/
void GdlRuleItem::LhsPrettyPrint(GrcManager * /*pcman*/, GdlRule * /*prule*/, int /*irit*/,
	std::ostream & /*strmOut*/, bool /*fXml*/)
{
	//	Do nothing.
}

void GdlSetAttrItem::LhsPrettyPrint(GrcManager * /*pcman*/, GdlRule * /*prule*/, int /*irit*/,
	std::ostream & strmOut, bool /*fXml*/)
{
	strmOut << m_psymInput->FullAbbrev();
	strmOut << "  ";
}

void GdlSubstitutionItem::LhsPrettyPrint(GrcManager * /*pcman*/, GdlRule * /*prule*/, int /*irit*/,
	std::ostream & strmOut, bool /*fXml*/)
{
	strmOut << m_psymInput->FullAbbrev();
	strmOut << "  ";
}

/*--------------------------------------------------------------------------------------------*/
void GdlRuleItem::RhsPrettyPrint(GrcManager * /*pcman*/, GdlRule * /*prule*/, int /*irit*/,
	std::ostream & /*strmOut*/, bool /*fXml*/)
{
	//	Do nothing.
}

void GdlSetAttrItem::RhsPrettyPrint(GrcManager * pcman, GdlRule * prule, int irit,
	std::ostream & strmOut, bool fXml)
{
	strmOut << m_psymInput->FullAbbrev();
	AttrSetterPrettyPrint(pcman, prule, irit, strmOut, fXml);
	strmOut << "  ";
}

void GdlSubstitutionItem::RhsPrettyPrint(GrcManager * pcman, GdlRule * prule, int irit,
	std::ostream & strmOut, bool fXml)
{
	strmOut << m_psymOutput->FullAbbrev();

	if (m_pexpSelector)
	{
		if (m_psymOutput->Data())
			strmOut << "$";
		strmOut << m_pexpSelector->SlotNumber();
	}

	if (m_vpexpAssocs.size() > 0)
	{
		strmOut << ":";
		if (m_vpexpAssocs.size() > 1)
			strmOut << "(";
		int iexp;
		for (iexp = 0; iexp < signed(m_vpexpAssocs.size()) - 1; iexp++)
			strmOut << m_vpexpAssocs[iexp]->SlotNumber() << " ";
		strmOut << m_vpexpAssocs[iexp]->SlotNumber();
		if (m_vpexpAssocs.size() > 1)
			strmOut << ")";
	}
	AttrSetterPrettyPrint(pcman, prule, irit, strmOut, fXml);
	strmOut << "  ";
}

/*--------------------------------------------------------------------------------------------*/
void GdlRuleItem::ContextPrettyPrint(GrcManager * pcman, GdlRule * /*prule*/, int /*irit*/,
	std::ostream & strmOut, bool fXml)
{
	strmOut << m_psymInput->FullAbbrev();
	ConstraintPrettyPrint(pcman, strmOut, true, fXml);
	strmOut << "  ";
}

void GdlLineBreakItem::ContextPrettyPrint(GrcManager * pcman, GdlRule * /*prule*/, int /*irit*/,
	std::ostream & strmOut, bool fXml)
{
	strmOut << "#";
	ConstraintPrettyPrint(pcman, strmOut, true, fXml);
	strmOut << "  ";
}

void GdlSetAttrItem::ContextPrettyPrint(GrcManager * pcman, GdlRule * /*prule*/, int /*irit*/,
	std::ostream & strmOut, bool fXml)
{
	strmOut << "_";
	ConstraintPrettyPrint(pcman, strmOut, true, fXml);
	strmOut << "  ";
}

/*--------------------------------------------------------------------------------------------*/
void GdlRuleItem::ConstraintPrettyPrint(GrcManager * pcman, std::ostream & strmOut, bool fXml,
	bool fSpace)
{
	if (m_pexpConstraint)
	{
		if (fSpace) strmOut << " ";
		strmOut << "{ ";
		m_pexpConstraint->PrettyPrint(pcman, strmOut, fXml);
		strmOut << " }";
	}
}

/*--------------------------------------------------------------------------------------------*/
void GdlSetAttrItem::AttrSetterPrettyPrint(GrcManager * pcman, GdlRule * /*prule*/, int /*irit*/,
	std::ostream & strmOut, bool fXml)
{
	if (m_vpavs.size() > 0)
	{
		bool fAtt = false;
		bool fAttAt = false;
		bool fAttWith = false;
		strmOut << " { ";

		// Do attach attributes first. Use the {} structure to take up less room.
		int ciavsAttach = 0;
		for (size_t iavs = 0; iavs < m_vpavs.size(); iavs++)
		{
			if (m_vpavs[iavs]->m_psymName->IsAttachment())
				ciavsAttach++;
		}
		if (ciavsAttach)
		{
			strmOut << " attach {";
			for (size_t iavs = 0; iavs < m_vpavs.size(); iavs++)
			{
				if (m_vpavs[iavs]->m_psymName->IsAttachment())
					m_vpavs[iavs]->PrettyPrintAttach(pcman, strmOut, fXml);
			}
			strmOut << "} ";
		}

		// Now do everything else.
		for (size_t iavs = 0; iavs < m_vpavs.size(); iavs++)
		{
			if (!m_vpavs[iavs]->m_psymName->IsAttachment())
				m_vpavs[iavs]->PrettyPrint(pcman, strmOut, fXml, &fAtt, &fAttAt, &fAttWith, m_vpavs.size());
		}
		strmOut << " }";
	}
}

void GdlAttrValueSpec::PrettyPrintAttach(GrcManager * pcman, std::ostream & strmOut, bool fXml)
{
	if (m_fFlattened
		&& (m_psymName->IsAttachAtField() || m_psymName->IsAttachWithField()))
	{
		// A single statement like "attach.at = apt" has been translated into
		// "attach.at.x = apt.x, attach.at.y = apt.y, attach.at.xoffset = apt.xoffset,
		// attach.at.yoffset = apt.yoffset". Just print out one of these, say, the x.
		if (m_psymName->IsAttachXField())
		{
			if (m_psymName->IsAttachAtField())
				strmOut << "at = ";
			else
				strmOut << "with = ";
			GdlLookupExpression * pexpLookup = dynamic_cast<GdlLookupExpression *>(m_pexpValue);
			if (pexpLookup)
			{
				Symbol psym = pexpLookup->Name()->ParentSymbol();
				strmOut << psym->FullAbbrevOmit("attach");
			}
			else
				// strange...
				m_pexpValue->PrettyPrint(pcman, strmOut, fXml);
			strmOut << "; ";
		}
		return;
	}
	
	if (m_psymName->IsAttachOffsetField())
	{
		int nValue;
		if (m_pexpValue->ResolveToInteger(&nValue, false) && nValue == 0)
			// Don't bother putting out something like attach.at.xoffset = 0.
			return;
	}

	strmOut << m_psymName->FullAbbrevOmit("attach");
	strmOut << " " << m_psymOperator->FullAbbrev() << " ";
	m_pexpValue->PrettyPrint(pcman, strmOut, fXml);
	strmOut << "; ";
}

void GdlAttrValueSpec::PrettyPrint(GrcManager * pcman, std::ostream & strmOut, bool fXml,
	bool * /*pfAtt*/, bool * /*pfAttAt*/, bool * /*pfAttWith*/, int /*cpavs*/)
{
	//if (cpavs > 6 && m_psymName->IsAttachment())
	//{
	//	if (*pfAtt)
	//		return;
	//	*pfAtt = true;
	//	strmOut << "attach {...} ";
	//	return;
	//}

	//if (m_psymName->IsAttachAtField() && m_fFlattened)
	//{
	//	if (*pfAttAt)
	//		return;
	//	*pfAttAt = true;
	//	strmOut << "attach.at=...";
	//	return;
	//}
	//else if (m_psymName->IsAttachWithField() && m_fFlattened)
	//{
	//	if (*pfAttWith)
	//		return;
	//	*pfAttWith = true;
	//	strmOut << "attach.with=...";
	//	return;
	//}
	//else
	//	strmOut << m_psymName->FullAbbrev();
	
	strmOut << m_psymName->FullAbbrev();
	strmOut << " " << m_psymOperator->FullAbbrev() << " ";
	m_pexpValue->PrettyPrint(pcman, strmOut, fXml);
	strmOut << "; ";
}


/*----------------------------------------------------------------------------------------------
	Output a list of all the classes and their members.
----------------------------------------------------------------------------------------------*/
void GrcManager::DebugClasses()
{
	std::ofstream strmOut;
	strmOut.open("dbg_classes.txt");
	if (strmOut.fail())
	{
		g_errorList.AddError(6104, NULL,
			"Error in writing to file ",
			"dbg_classes.txt");
		return;
	}

	if (g_errorList.AnyFatalErrors())
		strmOut << "Fatal errors--compilation aborted";
	else
	{
		m_prndr->DebugClasses(strmOut, m_vpglfcReplcmtClasses, m_cpglfcLinear);
	}

	strmOut.close();
}
		
void GdlRenderer::DebugClasses(std::ostream & strmOut,
	std::vector<GdlGlyphClassDefn *> & vpglfcReplcmt, int cpglfcLinear)
{
	strmOut << "LINEAR (OUTPUT) CLASSES";

	//	linear classes (output)
	int cTmp = 0;
	int ipglfc;
	for (ipglfc = 0; ipglfc < cpglfcLinear; ipglfc++)
	{
		GdlGlyphClassDefn * pglfc = vpglfcReplcmt[ipglfc];

		Assert(pglfc->ReplcmtOutputClass() || pglfc->GlyphIDCount() <= 1);
		//Assert(pglfc->ReplcmtOutputID() == cTmp);

		strmOut << "\n\n";
		strmOut << "Class #" << ipglfc << ": ";
		strmOut << pglfc->Name();

		std::vector<utf16> vwGlyphs;
		pglfc->GenerateOutputGlyphList(vwGlyphs);

		//	glyph list
		for (size_t iw = 0; iw < vwGlyphs.size(); iw++)
		{
			if (iw % 10 == 0)
			{
				strmOut << "\n" << iw << ":";
			}
			strmOut << "   ";
			GrcManager::DebugHex(strmOut, vwGlyphs[iw]);
		}

		cTmp++;
	}

	strmOut << "\n\n\nINDEXED (INPUT) CLASSES";

	//	indexed classes (input)
	for (ipglfc = cpglfcLinear; ipglfc < signed(vpglfcReplcmt.size()); ipglfc++)
	{
		GdlGlyphClassDefn * pglfc = vpglfcReplcmt[ipglfc];

		Assert(pglfc->ReplcmtInputClass());
		Assert(pglfc->ReplcmtInputID() == cTmp);

		strmOut << "\n\n";
		strmOut << "Class #" << ipglfc << ": ";
		strmOut << pglfc->Name();

		std::vector<utf16> vwGlyphs;
		std::vector<int> vnIndices;
		pglfc->GenerateInputGlyphList(vwGlyphs, vnIndices);
		//	glyph list
		for (size_t iw = 0; iw < vwGlyphs.size(); iw++)
		{
			if (iw % 5 == 0)
			{
				strmOut << "\n";
			}
			GrcManager::DebugHex(strmOut, vwGlyphs[iw]);
			strmOut << " :";
			if (vnIndices[iw] < 1000) strmOut << " ";
			if (vnIndices[iw] < 100)  strmOut << " ";
			if (vnIndices[iw] < 10)   strmOut << " ";
			strmOut << vnIndices[iw];
			strmOut << "    ";
		}

		cTmp++;
	}
}

/*----------------------------------------------------------------------------------------------
	Output the contents of the -cmap-, the mapping from unicode-to-glyph ID and vice-versa.
	Also include any pseudo-glyphs.
----------------------------------------------------------------------------------------------*/
void GrcManager::DebugCmap(GrcFont * pfont)
{
	bool fSuppPlaneChars = pfont->AnySupplementaryPlaneChars();

	std::ofstream strmOut;
	strmOut.open("dbg_cmap.txt");
	if (strmOut.fail())
	{
		g_errorList.AddError(6105, NULL,
			"Error in writing to file ",
			"dbg_cmap.txt");
		return;
	}

	int nFirstPseudo = 0x10000;
	for (size_t iw = 0; iw < m_vwPseudoForUnicode.size(); iw++)
		nFirstPseudo = min(nFirstPseudo, static_cast<int>(m_vwPseudoForUnicode[iw]));

	if (g_errorList.AnyFatalErrors())
	{
		strmOut << "Fatal errors--compilation aborted";
	}
	else
	{
		int cnUni = pfont->NumUnicode();
		utf16 * rgchwUniToGlyphID = new utf16[cnUni];
		unsigned int * rgnGlyphIDToUni = new unsigned int[0x10000];

		std::vector<unsigned int> vnXUniForPsd;
		std::vector<utf16> vwXPsdForUni;

		CmapAndInverse(pfont, cnUni, rgchwUniToGlyphID, rgnGlyphIDToUni,
			vnXUniForPsd, vwXPsdForUni);

		unsigned int nUni;
		utf16 wGlyphID;

		strmOut << "UNICODE => GLYPH ID MAPPINGS\n\n";

		int iXPsd = 0; // extra pseudos
		GrcFont::iterator fit(pfont);
		int iUni;
		for (iUni = 0, fit = pfont->Begin();
			fit != pfont->End();
			++fit, ++iUni)
		{
			nUni = *fit;
			wGlyphID = rgchwUniToGlyphID[iUni];
			Assert(wGlyphID != 0);

			while (iXPsd < signed(vnXUniForPsd.size()) && vnXUniForPsd[iXPsd] < nUni)
			{
				// insert extra pseudos that are not in the cmap
				WriteCmapItem(strmOut, vnXUniForPsd[iXPsd], fSuppPlaneChars, vwXPsdForUni[iXPsd], 
					true, true, false);
				iXPsd++;
			}
			WriteCmapItem(strmOut, nUni, fSuppPlaneChars, wGlyphID, true,
				wGlyphID >= nFirstPseudo, true);
		}

		// Sort the extra pseudos by glyph ID.
		for (int i1 = 0; i1 < signed(vwXPsdForUni.size()) - 1; i1++)
			for (size_t i2 = i1 + 1; i2 < vwXPsdForUni.size(); i2++)
				if (vwXPsdForUni[i1] > vwXPsdForUni[i2])
				{
					// Swap
					utf16 wTmp = vwXPsdForUni[i1];
					vwXPsdForUni[i1] = vwXPsdForUni[i2];
					vwXPsdForUni[i2] = wTmp;
					unsigned int nTmp = vnXUniForPsd[i1];
					vnXUniForPsd[i1] = vnXUniForPsd[i2];
					vnXUniForPsd[i2] = nTmp;
				}

		strmOut << "\n\n\nGLYPH ID => UNICODE MAPPINGS\n\n";

		iXPsd = 0;
		for (wGlyphID = 0; wGlyphID < 0xFFFF; wGlyphID++)
		{
			if (wGlyphID == m_wLineBreak)
			{
				strmOut << wGlyphID;
				if (fSuppPlaneChars) strmOut << "    ";
				strmOut << "                 [line-break]\n";
			}
			else if (wGlyphID == m_wPhantom)
			{
				strmOut << wGlyphID;
				if (fSuppPlaneChars) strmOut << "    ";
				strmOut << "                 [phantom]\n";
			}
			else if (iXPsd < signed(vwXPsdForUni.size()) && vwXPsdForUni[iXPsd] == wGlyphID)
			{
				// Pseudo-glyph where the Unicode value is not in the cmap.
				if (vnXUniForPsd[iXPsd] != 0)
					WriteCmapItem(strmOut, vnXUniForPsd[iXPsd], fSuppPlaneChars,
						wGlyphID, false, true, false);
				iXPsd++;
			}
			else
			{
				nUni = rgnGlyphIDToUni[wGlyphID];
				if (nUni != 0)
					WriteCmapItem(strmOut, nUni, fSuppPlaneChars,
						wGlyphID, false, wGlyphID >= nFirstPseudo, true);
			}
		}

		delete[] rgchwUniToGlyphID;
		delete[] rgnGlyphIDToUni;
	}

	strmOut.close();
}

void GrcManager::WriteCmapItem(std::ofstream & strmOut,
	unsigned int nUnicode, bool fSuppPlaneChars, utf16 wGlyphID, bool fUnicodeToGlyph,
	bool fPseudo, bool fInCmap)
{
	if (fUnicodeToGlyph)
	{
		DebugUnicode(strmOut, nUnicode, fSuppPlaneChars);
//		if (wGlyphID < 0x0100)
//			strmOut << " '" << (char)wGlyphID << "'";
//		else
//			strmOut << "    ";

		strmOut << "  => ";

		if (wGlyphID < 10)		strmOut << " ";
		if (wGlyphID < 100)		strmOut << " ";
		if (wGlyphID < 1000)	strmOut << " ";
		if (wGlyphID < 10000)	strmOut << " ";
		strmOut << wGlyphID << "  (";
		DebugHex(strmOut, wGlyphID);
		strmOut << ")";

		if (fPseudo)
		{
			if (fInCmap)
			{
				if (wGlyphID >= m_wFirstAutoPseudo)
					strmOut << "  [auto-pseudo]";
				else
					strmOut << "  [pseudo]";
			}
			else
				strmOut << "  [pseudo; not in cmap]";
		}

		strmOut << "\n";
	}
	else	// glyph to Unicode
	{
		strmOut << wGlyphID;	//DebugHex(strmOut, wGlyphID);
		if (wGlyphID < 10)		strmOut << " ";
		if (wGlyphID < 100)		strmOut << " ";
		if (wGlyphID < 1000)	strmOut << " ";
		if (wGlyphID < 10000)	strmOut << " ";
		strmOut << " =>  ";

		DebugUnicode(strmOut, nUnicode, fSuppPlaneChars);
		if (nUnicode < 0x0100)
			strmOut << "  '" << (char)nUnicode << "'";
		else
			strmOut << "     ";

		if (fPseudo)
		{
			if (fInCmap)
			{
				if (wGlyphID >= m_wFirstAutoPseudo)
					strmOut << "  [auto-pseudo]";
				else
					strmOut << "  [pseudo]";
			}
			else
				strmOut << "  [pseudo; not in cmap]";
		}

		strmOut << "\n";
	}
}


void GdlRenderer::DebugCmap(GrcFont * pfont, utf16 * rgchwUniToGlyphID, unsigned int * rgnGlyphIDToUni)
{
	for (size_t ipglfc = 0; ipglfc < m_vpglfc.size(); ipglfc++)
		m_vpglfc[ipglfc]->DebugCmap(pfont, rgchwUniToGlyphID, rgnGlyphIDToUni);
}


void GdlGlyphClassDefn::DebugCmap(GrcFont * pfont,
	utf16 * rgchwUniToGlyphID, unsigned int * rgnGlyphIDToUni)
{
	for (size_t iglfd = 0; iglfd < m_vpglfdMembers.size(); iglfd++)
		m_vpglfdMembers[iglfd]->DebugCmapForMember(pfont, rgchwUniToGlyphID, rgnGlyphIDToUni);
}


void GdlGlyphClassDefn::DebugCmapForMember(GrcFont * /*pfont*/,
	utf16 * /*rgchwUniToGlyphID*/, unsigned int * /*rgnGlyphIDToUni*/)
{
	//	Do nothing; this class will be handled separately at the top level.
}

void GdlGlyphDefn::DebugCmapForMember(GrcFont * pfont,
	utf16 * rgchwUniToGlyphID, unsigned int * rgnGlyphIDToUni)
{
	Assert(m_vwGlyphIDs.size() > 0);

	// TODO: check for kBadGlyph values

	//unsigned int n;
	//unsigned int nUnicode;
	//utf16 w;
	//utf16 wGlyphID;
	//utf16 wFirst; // wLast;

	switch (m_glft)
	{
	case kglftGlyphID:
		break;

	case kglftUnicode:
//		Assert(m_nFirst <= m_nLast);
//		for (n = m_nFirst; n <= m_nLast; ++n)
//		{
//			wGlyphID = pfont->GlyphFromCmap(n, this);
//			if (wGlyphID != 0)
//			{
//				rgchwUniToGlyphID[n] = wGlyphID;
//				rgnGlyphIDToUni[wGlyphID] = n;
//			}
//
//			// Just in case, since incrementing 0xFFFFFFFF will produce zero.
//			if (n == 0xFFFFFFFF)
//				break;
//		}
		
		break;

	case kglftPostscript:
		break;

	case kglftCodepoint:
//		char rgchCdPg[20];
//		itoa(m_wCodePage, rgchCdPg, 10);
//		wFirst = (utf16)m_nFirst;
//		wLast = (utf16)m_nLast;
//		if (wFirst == 0 && wLast == 0)
//		{
//			for (int ich = 0; ich < m_sta.Length(); ich++)
//			{
//				char rgchCdPt[2];
//				rgchCdPt[0] = m_sta.GetAt(ich);
//				nUnicode = pfont->UnicodeFromCodePage(m_wCodePage, m_sta[ich], this);
//				Assert(nUnicode != 0);
//				if ((wGlyphID = g_cman.PseudoForUnicode(nUnicode)) == 0)
//					wGlyphID = pfont->GlyphFromCmap(nUnicode, this);
//				Assert(wGlyphID != 0);
//				rgchwUniToGlyphID[nUnicode] = wGlyphID;
//				rgnGlyphIDToUni[wGlyphID] = nUnicode;
//			}
//		}
//		else
//		{
//			Assert(wFirst <= wLast);
//			for (w = wFirst; w <= wLast; w++)
//			{
//				nUnicode = pfont->UnicodeFromCodePage(m_wCodePage, w, this);
//				Assert(nUnicode != 0);
//				if ((wGlyphID = g_cman.PseudoForUnicode(nUnicode)) == 0)
//					wGlyphID = pfont->GlyphFromCmap(nUnicode, this);
//				Assert(wGlyphID != 0);
//				rgchwUniToGlyphID[nUnicode] = wGlyphID;
//				rgnGlyphIDToUni[wGlyphID] = nUnicode;
//
//				// Just in case, since incrementing 0xFFFF will produce zero.
//				if (w == 0xFFFF)
//					break;
//			}
//		}
		break;

	case kglftPseudo:
		Assert(m_nFirst == 0);
		Assert(m_nLast == 0);
		Assert(m_pglfOutput);
		//	While we're at it, handle the output glyph ID.
		m_pglfOutput->DebugCmapForMember(pfont, rgchwUniToGlyphID, rgnGlyphIDToUni);

		if (m_nUnicodeInput != 0)
		{
			//	It is the assigned glyph ID which is the 'contents' of this glyph defn.
//			rgchwUniToGlyphID[m_nUnicodeInput - pfont->MinUnicode()] = m_wPseudo;
			//rgnGlyphIDToUni[m_wPseudo] = m_nUnicodeInput;
		}
		break;
		
	default:
		Assert(false);
	}
}

/*----------------------------------------------------------------------------------------------
	Generate the cmap and inverse cmap. Used by the debugger output routines.
----------------------------------------------------------------------------------------------*/
void GrcManager::CmapAndInverse(GrcFont * pfont, 
	int cnUni, utf16 * rgchwUniToGlyphID, unsigned int * rgnGlyphIDToUni,
	std::vector<unsigned int> & vnXUniForPsd, std::vector<utf16> & vwXPsdForUni)
{
	memset(rgchwUniToGlyphID, 0, (cnUni * isizeof(utf16)));
	memset(rgnGlyphIDToUni, 0, (0x10000 * isizeof(int)));

	pfont->GetGlyphsFromCmap(rgchwUniToGlyphID);

	// Generate the inverse cmap. Also overwrite the glyph IDs for any pseudos.
	int iUni;
	GrcFont::iterator fit(pfont);
	int iUniPsd = 0;
	for (iUni = 0, fit = pfont->Begin();
		fit != pfont->End();
		++fit, ++iUni)
	{
		unsigned int nUni = *fit;

		// Handle pseudos.
		while (iUniPsd < signed(m_vnUnicodeForPseudo.size()) && nUni > m_vnUnicodeForPseudo[iUniPsd])
		{
			// Put any Unicode -> pseudo mappings where the Unicode is not in the cmap into 
			// a separate list.
			vnXUniForPsd.push_back(m_vnUnicodeForPseudo[iUniPsd]);
			vwXPsdForUni.push_back(m_vwPseudoForUnicode[iUniPsd]);
			iUniPsd++;
		}
		if (iUniPsd < signed(m_vnUnicodeForPseudo.size()) && m_vnUnicodeForPseudo[iUniPsd] == nUni)
		{
			// Pseudo: overwrite glyph ID.
			rgchwUniToGlyphID[iUni] = m_vwPseudoForUnicode[iUniPsd];
			iUniPsd++;
		}
		utf16 wGlyph = rgchwUniToGlyphID[iUni];
		rgnGlyphIDToUni[wGlyph] = nUni;
	}
	Assert(iUni == cnUni);
}

/*----------------------------------------------------------------------------------------------
	Output XML to be used by the engine debugger.
----------------------------------------------------------------------------------------------*/
void GrcManager::DebugXml(GrcFont * pfont, char * pchOutputFilename)
{
	// Calculate the name of the debugger-xml file. It is the name of the font file, but with
	// a .gdx extension.
	char * pchOut = pchOutputFilename;
	char rgchDbgXmlFile[128];
	char * pchXml = rgchDbgXmlFile;
	while (*pchOut != '.' && *pchOut != 0)
		*pchXml++ = *pchOut++;

	*pchXml++ = '.';
	*pchXml++ = 'g';
	*pchXml++ = 'd';
	*pchXml++ = 'x';
	*pchXml = 0;

	std::ofstream strmOut;
	strmOut.open(rgchDbgXmlFile);
	if (strmOut.fail())
	{
		g_errorList.AddError(6101, NULL,
			"Error in writing to file ",
			"dbg.xml");
		return;
	}

	if (g_errorList.AnyFatalErrors())
		strmOut << "Fatal errors--compilation aborted";
	else
	{
		strmOut << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
			<< "<graphite>\n\n";

		// Glyphs and glyph attributes
		this->DebugXmlGlyphs(pfont, strmOut);

		// Classes and members
		this->m_prndr->DebugXmlClasses(strmOut);

		// Features
		m_prndr->DebugXmlFeatures(strmOut);
		
		// Rules
		m_prndr->DebugXmlRules(this, strmOut);

		strmOut << "</graphite>\n";
	}

	strmOut.close();
}

/*--------------------------------------------------------------------------------------------*/
void GrcManager::DebugXmlGlyphs(GrcFont * pfont, std::ofstream & strmOut)
{
	// Glyph attribute definitions

	strmOut << "  <glyphAttrs>\n";

	Symbol psymActual = m_psymtbl->FindSymbol("*actualForPseudo*");
	unsigned int nAttrIdActual = psymActual->InternalID();
	Symbol psymBw = m_psymtbl->FindSymbol("breakweight");
	unsigned int nAttrIdBw = psymBw->InternalID();
	Symbol psymDir = m_psymtbl->FindSymbol("directionality");
	unsigned int nAttrIdDir = psymDir->InternalID();

	//Symbol psymJStr = m_psymtbl->FindSymbol(GrcStructName("justify", "0", "stretch"));
	Symbol psymJStr = m_psymtbl->FindSymbol(GrcStructName("justify", "stretch"));
	int nAttrIdJStr = psymJStr->InternalID();

	for (size_t nAttrID = 0; nAttrID < m_vpsymGlyphAttrs.size(); nAttrID++)
	{
		Symbol psymGlyphAttr = m_vpsymGlyphAttrs[nAttrID];

		//if ((psymGlyphAttr->IsMirrorAttr() || nAttrID == nAttrIdDir)
		//		&& !m_prndr->Bidi())
		//	continue;

		if (psymGlyphAttr->InternalID() == static_cast<int>(nAttrID))
		{
			std::string staExpType = ExpressionDebugString(psymGlyphAttr->ExpType());
			if (nAttrID == nAttrIdBw)
				staExpType = "bw";
			else if (nAttrID == nAttrIdDir)
				staExpType = "dircode";
			else if (nAttrID == nAttrIdActual)
				staExpType = "gid";
			else if (psymGlyphAttr->IsPointField())
				staExpType = "point";
			else if (psymGlyphAttr->IsComponentBoxField())
				staExpType = "comp";

			strmOut << "    <glyphAttr name=\"" << psymGlyphAttr->FullName() 
				<< "\" attrId=\"" << nAttrID 
				<< "\" type=\"" << staExpType
				<< "\" />\n";
		}
		// else we have something like justify.stretch which is unused
	}

	strmOut << "  </glyphAttrs>\n\n";

	// Glyphs and their attribute values

	// Generate the cmap.
	int cnUni = pfont->NumUnicode();
	utf16 * rgchwUniToGlyphID = new utf16[cnUni];
	unsigned int * rgnGlyphIDToUni = new unsigned int[0x10000];
	std::vector<unsigned int> vnXUniForPsd;
	std::vector<utf16> vwXPsdForUni;
	CmapAndInverse(pfont, cnUni, rgchwUniToGlyphID, rgnGlyphIDToUni,
		vnXUniForPsd, vwXPsdForUni);

	strmOut << "  <glyphs>\n";

	std::vector<std::string> vstaSingleMemberClasses;
	m_prndr->RecordSingleMemberClasses(vstaSingleMemberClasses);

	for (int wGlyphID = 0; wGlyphID < m_cwGlyphIDs; wGlyphID++)
	{
		// Convert breakweight values depending on the table version to output.
		////ConvertBwForVersion(wGlyphID, nAttrIdBw);

		//	Split any large stretch values into two 16-bit words.
		////SplitLargeStretchValue(wGlyphID, nAttrIdJStr);

		strmOut << "    <glyph glyphid=\"" << wGlyphID;
		if (vstaSingleMemberClasses.size() > (unsigned)wGlyphID && vstaSingleMemberClasses[wGlyphID] != "")
			strmOut << "\" className=\"" << vstaSingleMemberClasses[wGlyphID];
		if (rgnGlyphIDToUni[wGlyphID] != 0)
		{
			strmOut << "\" usv=\""; 
			DebugUnicode(strmOut, rgnGlyphIDToUni[wGlyphID], false);
		}
		strmOut<< "\"" << ">\n";
	
		for (size_t nAttrID = 0; nAttrID < m_vpsymGlyphAttrs.size(); nAttrID++)
		{
			int nValue = FinalAttrValue(wGlyphID, nAttrID);

			if (nAttrID == nAttrIdActual && nValue == 0)
				continue;

			if ((m_vpsymGlyphAttrs[nAttrID]->IsMirrorAttr() || nAttrID == nAttrIdDir)
					&& !m_prndr->Bidi())
				continue;

			// Get the original expression where this attribute was set.
			GdlExpression * pexp;
			int nPR;
			int munitPR;
			bool fOverride, fShadow;
			GrpLineAndFile lnf;
			m_pgax->Get(wGlyphID, nAttrID,
				&pexp, &nPR, &munitPR, &fOverride, &fShadow, &lnf);

			if (m_vpsymGlyphAttrs[nAttrID]->IsUserDefined() && !m_pgax->Defined(wGlyphID, nAttrID))
				// attribute not defined for this glyph
				continue;

			strmOut << "      <glyphAttrValue name=\"" << m_vpsymGlyphAttrs[nAttrID]->FullName()
				<< "\" value=\"";
			if (m_vpsymGlyphAttrs[nAttrID]->LastFieldIs("gpoint") && nValue == kGpointZero)
				strmOut << "zero";
			else
				strmOut  << nValue;

			if (!lnf.NotSet())
				strmOut << "\" inFile=\"" << lnf.File() << "\" atLine=\"" << lnf.OriginalLine();

			strmOut << "\" />\n";

		}

		strmOut << "    </glyph>\n";
	}

	strmOut << "  </glyphs>\n\n";
}

/*--------------------------------------------------------------------------------------------*/
void GdlRenderer::RecordSingleMemberClasses(std::vector<std::string> & vstaSingleMemberClasses)
{
	for (size_t ipglfc = 0; ipglfc < m_vpglfc.size(); ipglfc++)
	{
		m_vpglfc[ipglfc]->RecordSingleMemberClasses(vstaSingleMemberClasses);
	}
}

void GdlGlyphClassDefn::RecordSingleMemberClasses(std::vector<std::string> & vstaSingleMemberClasses)
{
	FlattenMyGlyphList();
	if (m_vgidFlattened.size() == 1)
	{
		utf16 gid = m_vgidFlattened[0];
		while (vstaSingleMemberClasses.size() <= gid)
			vstaSingleMemberClasses.push_back("");
		if (vstaSingleMemberClasses[gid] == "")
			vstaSingleMemberClasses[gid] = this->Name();
	}
}

/*--------------------------------------------------------------------------------------------*/
void GdlRenderer::DebugXmlClasses(std::ofstream & strmOut)		
{
	strmOut << "  <classes>\n";

	int cwGlyphIDs;
	for (size_t ipglfc = 0; ipglfc < m_vpglfc.size(); ipglfc++)
	{
		if (m_vpglfc[ipglfc]->Name() == "ANY")
			continue;

		cwGlyphIDs = 0;
		m_vpglfc[ipglfc]->DebugXmlClasses(strmOut, cwGlyphIDs);
	}

	strmOut << "  </classes>\n\n";
}


void GdlGlyphClassDefn::DebugXmlClasses(std::ofstream & strmOut, int & cwGlyphIDs)
{
	strmOut << "    <class name=\"" << this->Name();
	if (m_fReplcmtIn)
		strmOut << "\" subClassIndexLhs=\"" << m_nReplcmtInID;
	if (m_fReplcmtOut)
		strmOut << "\" subClassIndexRhs=\"" << m_nReplcmtOutID;
	strmOut << "\">\n";

	for (size_t iglfd = 0; iglfd < m_vpglfdMembers.size(); iglfd++)
	{
		m_vpglfdMembers[iglfd]->DebugXmlClassMembers(strmOut, this, LineAndFileForMember(iglfd),
			cwGlyphIDs);
	}
	strmOut << "    </class>\n";
}


void GdlGlyphClassDefn::DebugXmlClassMembers(std::ofstream & strmOut,
	GdlGlyphClassDefn * pglfdParent, GrpLineAndFile lnf, int & cwGlyphIDs)
{
	for (size_t iglfd = 0; iglfd < m_vpglfdMembers.size(); iglfd++)
	{
		m_vpglfdMembers[iglfd]->DebugXmlClassMembers(strmOut, pglfdParent, lnf,
			cwGlyphIDs);
	}
}

void GdlGlyphDefn::DebugXmlClassMembers(std::ofstream & strmOut, 
	GdlGlyphClassDefn * /*pglfdParent*/, GrpLineAndFile lnf, int & cwGlyphIDs)
{
	for (unsigned int iw = 0; iw < m_vwGlyphIDs.size(); iw++)
	{
		strmOut << "      <member glyphid=\"" << m_vwGlyphIDs[iw] << "\" index=\"" << cwGlyphIDs;

		if (!lnf.NotSet())
			strmOut << "\" inFile=\"" << lnf.File() << "\" atLine=\"" << lnf.OriginalLine();

		strmOut << "\" />\n";
		cwGlyphIDs++;
	}
}

/*--------------------------------------------------------------------------------------------*/
void GdlRenderer::DebugXmlFeatures(std::ofstream & strmOut)
{
	strmOut << "  <features>\n";

	for (size_t ipfeat = 0; ipfeat < m_vpfeat.size(); ipfeat++)
	{
		GdlFeatureDefn * pfeat = m_vpfeat[ipfeat];
		pfeat->DebugXmlFeatures(strmOut);
	}

	strmOut << "  </features>\n\n";
}

void GdlFeatureDefn::DebugXmlFeatures(std::ofstream & strmOut)
{
	GrpLineAndFile lnf = this->LineAndFile();
	unsigned int nID = this->ID();
	strmOut << "    <feature name=\"" << this->Name()
		<< "\" featureID=\"" << nID;
	if (nID > 0xFFFFFF)
	{
		// Output string format as well.

		char rgch[5];
		rgch[4] = 0;
		rgch[3] = (char)(nID & 0x000000FF);
		rgch[2] = (char)((nID & 0x0000FF00) >> 8);
		rgch[1] = (char)((nID & 0x00FF0000) >> 16);
		rgch[0] = (char)((nID & 0xFF000000) >> 24);
		std::string staT(rgch);

		// Why doesn't this work??
		//union {
		//	char rgch[4];
		//	unsigned int n;
		//} featid;
		//featid.n = nID;
		//// Reverse them.
		//char chTmp = featid.rgch[0];
		//featid.rgch[0] = featid.rgch[4];
		//featid.rgch[4] = chTmp;
		//chTmp = featid.rgch[2];
		//featid.rgch[2] = featid.rgch[3];
		//featid.rgch[3] = chTmp;
		//std::string staT(featid.rgch);
		
		strmOut << "\" featureIDstring=\"" << staT;
	}
	strmOut << "\" index=\"" << this->InternalID();

	if (!lnf.NotSet() && this->ID() != kfidStdLang)
		strmOut << "\" inFile=\"" << lnf.File() << "\" atLine=\"" << lnf.OriginalLine();

	strmOut << "\" >\n";

	for (unsigned int ifset = 0; ifset < m_vpfset.size(); ifset++)
	{
		m_vpfset[ifset]->DebugXmlFeatures(strmOut);
	}

	strmOut << "    </feature>\n";
}

void GdlFeatureSetting::DebugXmlFeatures(std::ofstream & strmOut)
{
	GrpLineAndFile lnf = this->LineAndFile();
	strmOut << "      <featureSetting name=\"" << this->Name()
		<< "\" value=\"" << this->Value();

	if (!lnf.NotSet())
		strmOut << "\" inFile=\"" << lnf.File() << "\" atLine=\"" << lnf.OriginalLine();

	strmOut << "\" />\n";
}


/*--------------------------------------------------------------------------------------------*/
void GdlRenderer::DebugXmlRules(GrcManager * pcman, std::ofstream & strmOut)
{
	strmOut << "  <rules>\n";

	GdlRuleTable * prultbl;

	if ((prultbl = FindRuleTable("linebreak")) != NULL)
		prultbl->DebugXmlRules(pcman, strmOut);

	if ((prultbl = FindRuleTable("substitution")) != NULL)
		prultbl->DebugXmlRules(pcman, strmOut);

	if (Bidi())
		strmOut << "    <pass table=\"bidi\" index=\"" << m_iPassBidi + 1 << "\" />\n";

	if ((prultbl = FindRuleTable("justification")) != NULL)
		prultbl->DebugXmlRules(pcman, strmOut);

	if ((prultbl = FindRuleTable("positioning")) != NULL)
		prultbl->DebugXmlRules(pcman, strmOut);

	strmOut << "  </rules>\n\n";
}

void GdlRuleTable::DebugXmlRules(GrcManager * pcman, std::ofstream & strmOut)
{
	for (size_t ippass = 0; ippass < m_vppass.size(); ippass++)
	{
		m_vppass[ippass]->DebugXmlRules(pcman, strmOut, this->NameSymbol());
	}
}

void GdlPass::DebugXmlRules(GrcManager * pcman, std::ofstream & strmOut,
	Symbol psymTableName)
{
	if (m_vprule.size() == 0)
		return;

	Assert(PassDebuggerNumber() != 0);

	strmOut << "    <pass table=\"" << psymTableName->FullName()
		<< "\" index=\"" << PassDebuggerNumber() << "\" >\n";

	if (m_vpexpConstraints.size() > 0)
	{
		strmOut << "        <passConstraints>\n";
		for (size_t iexp = 0; iexp < m_vpexpConstraints.size(); iexp++)
		{
			GrpLineAndFile lnf = m_vpexpConstraints[iexp]->LineAndFile();
			strmOut << "          <passConstraint gdl=\"{ ";
			m_vpexpConstraints[iexp]->PrettyPrint(pcman, strmOut, true);
			strmOut << " }\" inFile=\"" << lnf.File() << "\" atLine=\"" << lnf.OriginalLine() << "\" />\n";
		}
		strmOut << "        </passConstraints>\n";
	}

	for (size_t irule = 0; irule < m_vprule.size(); irule++)
	{
		m_vprule[irule]->DebugXml(pcman, strmOut, PassDebuggerNumber(), irule);
	}

	strmOut << "    </pass>  <!-- pass " << PassDebuggerNumber() 
				<< " (" << psymTableName->FullName() << " table) -->\n\n";
}

void GdlRule::DebugXml(GrcManager * pcman, std::ofstream & strmOut, int nPassNum, int nRuleNum)
{
	strmOut << "      <rule id=\"" << nPassNum << "." << nRuleNum
		<< "\" inFile=\"" << LineAndFile().File()
		<< "\" atLine=\"" << LineAndFile().OriginalLine()
		<< "\"\n            prettyPrint=\"";
	this->RulePrettyPrint(pcman, strmOut, true);
	strmOut << "\" >\n";

	if (m_vpexpConstraints.size() > 0)
	{
		strmOut << "        <ruleConstraints>\n";
		for (size_t iexp = 0; iexp < m_vpexpConstraints.size(); iexp++)
		{
			GrpLineAndFile lnf = m_vpexpConstraints[iexp]->LineAndFile();
			strmOut << "          <ruleConstraint gdl=\"{ ";
			m_vpexpConstraints[iexp]->PrettyPrint(pcman, strmOut, true);
			strmOut << " }\" inFile=\"" << lnf.File() << "\" atLine=\"" << lnf.OriginalLine() << "\" />\n";
		}
		strmOut << "        </ruleConstraints>\n";
	}

	//	Loop through all the items to see if we need a LHS or a context.
	bool fLhs = false;
	bool fContext = (m_nScanAdvance != -1);
	int irit;
	for (irit = 0; irit < signed(m_vprit.size()) ; irit++)
	{
		GdlRuleItem * prit = m_vprit[irit];
		GdlSubstitutionItem * pritsub = dynamic_cast<GdlSubstitutionItem *>(prit);
		if (pritsub)
			fLhs = true;

		GdlSetAttrItem * pritset = dynamic_cast<GdlSetAttrItem *>(prit);
		if (!pritset)
			fContext = true;
		else if (prit->m_pexpConstraint)
			fContext = true;
	}

	// LHS
	if (fLhs)
	{
		strmOut << "        <lhs>\n";
		for (size_t irit = 0; irit < m_vprit.size(); irit++)
		{
			m_vprit[irit]->DebugXmlLhs(pcman, strmOut);
		}
		strmOut << "        </lhs>\n";
	}

	// RHS
	strmOut << "        <rhs>\n";
	for (size_t irit = 0; irit < m_vprit.size(); irit++)
	{
		m_vprit[irit]->DebugXmlRhs(pcman, strmOut);
	}
	strmOut << "        </rhs>\n";

	// Context
	if (fContext)
	{
		strmOut << "        <context>\n";
		int iritRhs = 0;
		for (size_t irit = 0; irit < m_vprit.size(); irit++)
		{
			if (m_nScanAdvance == (signed)irit)
				strmOut << "          <caret />\n";

			m_vprit[irit]->DebugXmlContext(pcman, strmOut, iritRhs);
		}
		strmOut << "        </context>\n";
	}

	strmOut << "      </rule>\n";
}


void GdlRuleItem::DebugXmlLhs(GrcManager * /*pcman*/, std::ofstream & /*strmOut*/)
{
	//	Do nothing.
}

void GdlSetAttrItem::DebugXmlLhs(GrcManager * /*pcman*/, std::ofstream & strmOut)
{
	strmOut << "          <lhsSlot className=\"" << m_psymInput->FullAbbrev()
		<< "\" slotIndex=\"" << m_iritContextPos + 1 << "\" />\n";
}

void GdlSubstitutionItem::DebugXmlLhs(GrcManager * /*pcman*/, std::ofstream & strmOut)
{
	strmOut << "          <lhsSlot className=\"" << m_psymInput->FullAbbrev()
		<< "\" slotIndex=\"" << m_iritContextPos + 1 << "\" />\n";
}

void GdlRuleItem::DebugXmlRhs(GrcManager * /*pcman*/, std::ofstream & /*strmOut*/)
{
	//	Do nothing.
}

void GdlSetAttrItem::DebugXmlRhs(GrcManager * pcman, std::ofstream & strmOut)
{
	strmOut << "          <rhsSlot className=\"" << m_psymInput->FullAbbrev();
	if (m_vpavs.size() > 0)
	{
		strmOut << "\" assignmentGdl=\"";
		AttrSetterPrettyPrint(pcman, NULL, 0, strmOut, true);	// NULL and 0 are bogus but not used
	}
	strmOut << "\" slotIndex=\"" << m_iritContextPos + 1 << "\" />\n";
}

void GdlSubstitutionItem::DebugXmlRhs(GrcManager * pcman, std::ofstream & strmOut)
{
	strmOut << "          <rhsSlot className=\"" << m_psymOutput->FullAbbrev();
	
	if (m_pexpSelector)
		strmOut << "\" selectorIndex=\"" << m_pexpSelector->SlotNumber();

	if (m_vpexpAssocs.size() > 0)
	{
		strmOut << "\" associations=\"";
		int iexp;
		for (iexp = 0; iexp < signed(m_vpexpAssocs.size()) - 1; iexp++)
			strmOut << m_vpexpAssocs[iexp]->SlotNumber() << " ";
		strmOut << m_vpexpAssocs[iexp]->SlotNumber();
	}

	if (m_vpavs.size() > 0)
	{
		strmOut << "\" assignmentGdl=\"";
		AttrSetterPrettyPrint(pcman, NULL, 0, strmOut, true);	// NULL and 0 are bogus but not used
	}
	strmOut << "\" slotIndex=\"" << m_iritContextPos + 1 << "\" />\n";

}

void GdlRuleItem::DebugXmlContext(GrcManager * pcman, std::ofstream & strmOut, int & /*iritRhs*/)
{
	strmOut << "          <contextSlot type=\"class\" className=\"" << m_psymInput->FullAbbrev() 
		<< "\"";
	if (this->m_pexpConstraint)
	{
		strmOut << " >\n            <constraint gdl=\"";
		ConstraintPrettyPrint(pcman, strmOut, true);
		strmOut << "\" />\n          </contextSlot>\n";
	}
	else
		strmOut << " />\n";
}

void GdlSetAttrItem::DebugXmlContext(GrcManager * pcman, std::ofstream & strmOut, int & iritRhs)
{
	iritRhs++;

	strmOut << "          <contextSlot type=\"place-holder\""
		<< " rhsIndex=\"" << iritRhs << "\"";
	if (this->m_pexpConstraint)
	{
		strmOut << " >\n            <constraint gdl=\"";
		ConstraintPrettyPrint(pcman, strmOut, true);
		strmOut << "\" />\n          </contextSlot>\n";
	}
	else
		strmOut << " />\n";
}

void GdlRuleItem::DebugXmlConstraint(GrcManager * pcman, std::ofstream & strmOut)
{
	if (m_pexpConstraint)
	{
		strmOut << "        <constraint gdl=\"";
		m_pexpConstraint->PrettyPrint(pcman, strmOut, true);
		strmOut << "\" />\n";
	}
}

void GdlLineBreakItem::DebugXmlConstraint(GrcManager * pcman, std::ofstream & strmOut)
{
	strmOut << "          <contextSlot className=\"#\"";
	ConstraintPrettyPrint(pcman, strmOut, true);
	strmOut << "\" />\n";

}

/*----------------------------------------------------------------------------------------------
	Output a number in hex format.
----------------------------------------------------------------------------------------------*/
void GrcManager::DebugHex(std::ostream & strmOut, utf16 wGlyphID)
{
	char rgch[20];
	itoa(wGlyphID, rgch, 16);
	strmOut << "0x";
	if (wGlyphID <= 0x0fff) strmOut << "0";
	if (wGlyphID <= 0x00ff) strmOut << "0";
	if (wGlyphID <= 0x000f) strmOut << "0";
	strmOut << rgch;
}

/*----------------------------------------------------------------------------------------------
	Output a Unicode codepoint in hex format.
----------------------------------------------------------------------------------------------*/
void GrcManager::DebugUnicode(std::ostream & strmOut, int nUnicode, bool f32bit)
{
	char rgch[20];
	itoa(nUnicode, rgch, 16);
	for (int ich = 0; ich < 20; ich++)
	{
		if ('a' <= rgch[ich] && rgch[ich] <= 'f')
			rgch[ich] -= 'a' - 'A';	// uppercase
		if (rgch[ich] == 0)
			break;
	}

	strmOut << "U+";
	if (f32bit)
	{
		if (nUnicode <= 0x0FFFFFFF) strmOut << "0";
		if (nUnicode <= 0x00FFFFFF) strmOut << "0";
		if (nUnicode <= 0x000FFFFF) strmOut << "0";
		if (nUnicode <= 0x0000FFFF) strmOut << "0";
	}
	if (nUnicode <= 0x0fff) strmOut << "0";
	if (nUnicode <= 0x00ff) strmOut << "0";
	if (nUnicode <= 0x000f) strmOut << "0";
	strmOut << rgch;
}

/*----------------------------------------------------------------------------------------------
	Return a string version of an expression type
----------------------------------------------------------------------------------------------*/
std::string GrcManager::ExpressionDebugString(ExpressionType expt)
{
	switch (expt)
	{
	default:
	case kexptUnknown:	return "unknown";
	case kexptZero:
	case kexptOne:
	case kexptNumber:	return "integer";
	case kexptBoolean:	return "boolean";
	case kexptMeas:		return "em-units";
	case kexptSlotRef:	return "slot-ref";
	case kexptString:	return "string";
	case kexptPoint:	return "point";
	case kexptGlyphID:	return "gid";
	}
}
