crashanalysercmd/PerfToolsSharedLibraries/Engine/SymBuildParsingLib/Grouper/SymGrouperMastermind.cs
author Matti Laitinen <matti.t.laitinen@nokia.com>
Thu, 11 Feb 2010 15:50:58 +0200
changeset 0 818e61de6cd1
permissions -rw-r--r--
Add initial version of Crash Analyser cmdline under EPL

/*
* Copyright (c) 2004-2008 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/

using System;
using System.Text;
using System.Threading;
using System.Collections;
using SymBuildParsingLib.Token;
using SymBuildParsingLib.Lexer;

namespace SymBuildParsingLib.Grouper
{
	public class SymGrouperMastermind
	{
		#region Enumerations
		public enum TEvent
		{
			EEventGroupTokenReady = 0
		};
		#endregion

		#region Observer interface
		public delegate void MastermindObserver( TEvent aEvent, SymToken aGroupedToken );
		#endregion

		#region Events
		public event MastermindObserver MastermindObservers;
		#endregion

		#region Constructors & destructor
		public SymGrouperMastermind()
		{
		}
		#endregion

		#region Internal enumerations
		private enum TGroupingAction
		{
			ETokenIgnore = -1,
			ETokenMerge = 0,
			ETokenEnqueue,
			ETokenFlushQueue
		};

		[Flags]
		private enum TStateFlag
		{
			EStateFlagUnspecified = 0,
			EStateFlagInQuotation = 1,
			EStateFlagInComment = 2,
			EStateFlagInPreProcessorDirective = 4
		};
		#endregion

		#region API
		public void PerformGrouping()
		{
			SymToken token = NextInputToken();
			//
			while( token != null )
			{
				ProcessToken( token );
				token = NextInputToken();
			}
		}

		public void EnqueueLexedToken( SymToken aToken )
		{
			lock( iLexedTokens )
			{
				iLexedTokens.Enqueue( aToken );
			}
		}
		#endregion

		#region Internal token processors
		private void ProcessToken( SymToken aToken )
		{
			aToken.RefineTokenClass();
			aToken.RefineTokenType();

			if	( InQuotation )
			{
				ProcessTokenDuringQuotation( aToken );
			}
			else if ( InComment )
			{
				ProcessTokenDuringComment( aToken );
			}
			else if ( InPreProcessorDirective )
			{
				ProcessTokenDuringPreProcessorDirective( aToken );
			}
			else
			{
				ProcessTokenDuringNormalOperations( aToken );
			}
		}

		private void ProcessTokenDuringNormalOperations( SymToken aToken )
		{
			// By default we will just add the input token to the
			// pending queue (i.e. no combining/grouping)
			TGroupingAction action = TGroupingAction.ETokenEnqueue;
			//
			if	( iCache.Count == 0 )
			{
				#region The cache is empty - enqueue the token.
				// Starting a new token batch, so just push the token. If
				// its a quotation, then it will be handled during
				// the enqueuing. Pragma symbols must appear as the first
				// item on a line, and will be picked up similarly to quotes.
				if	( aToken.Class == SymToken.TClass.EClassNewLine )
				{
					// If we're adding a new blank line as the first
					// token, we just want to flush it out immediately.
					EnqueueNewOutputToken( aToken );
					action = TGroupingAction.ETokenFlushQueue;
				}
				else
				{
					action = TGroupingAction.ETokenEnqueue;
				}
				#endregion
			}
			else
			{
				#region The cache already has some tokens...
				SymToken previousToken = PreviousOutputToken;
				SymToken.TClass previousTokenClass = previousToken.Class;
				//
				if	( aToken.Class == SymToken.TClass.EClassNewLine )
				{
					#region New line detected...

					// Checking for continuations...
					if	( previousToken.Class == SymToken.TClass.EClassSymbol && previousToken.Value == @"\" )
					{
						// Because of the continuation character, we don't 
						// flush the cache. 

						// Discard new line
						previousToken.Class = SymToken.TClass.EClassContinuation;
						action = TGroupingAction.ETokenIgnore;
					}
					else
					{
						// We never allow new lines to be combined. In fact,
						// they are the signal that we should flush whatever we have
						// cached so far. We must add the new line token
						// first though.
						EnqueueNewOutputToken( aToken );
						action = TGroupingAction.ETokenFlushQueue;
					}
					#endregion
				}
				else if	( previousTokenClass == aToken.Class )
				{
					#region Tokens are the same class - check for combining
					// We group almost all tokens, but some are not permitted
					// to be combined, for example, brackets.
					bool combiningAllowed = previousToken.CombiningAllowed;
					if	( combiningAllowed && aToken.CombiningAllowed )
					{
						// Merge the two tokens
						action = TGroupingAction.ETokenMerge;
					}
					else
					{
						// Treat it as a separate token.
						action = TGroupingAction.ETokenEnqueue;
					}
					#endregion
				}
				else
				{
					#region Handling some other type of token...
					if	( previousTokenClass == SymToken.TClass.EClassSymbol && previousToken.Value == @"\" )
					{
						// If the last token was a single escaped character, and this next
						// character is not an asterisk or another back slash, then
						// we can try to combine the two.
						if	( !(aToken.Value == "*" || aToken.Value == @"\" ) )
						{
							action = TGroupingAction.ETokenMerge;
						}
						else
						{
							System.Diagnostics.Debug.Assert( false );
						}
					}
					else
					{
						action = TGroupingAction.ETokenEnqueue;
					}
					#endregion
				}
				#endregion
			}

			#region Now perform the action
			switch( action )
			{
				case TGroupingAction.ETokenEnqueue:
					EnqueueNewOutputToken( aToken );
					break;
				case TGroupingAction.ETokenMerge:
					MergeWithPreviousToken( aToken );
					break;
				case TGroupingAction.ETokenFlushQueue:
					FlushCache();
					break;
				default:
				case TGroupingAction.ETokenIgnore:
					break;
			}
			#endregion
		}

		private void ProcessTokenDuringQuotation( SymToken aToken )
		{
//			System.Diagnostics.Debug.Write( "[" + aToken.Value + "] " );
			System.Diagnostics.Debug.Assert( iCache.Count > 0 );

			#region Quotation examples
			//	1)	""
			//	2)	"\""
			//	3)	"\"\""
			//	4)	''
			//	5)	'\''
			//	6)	'\'\''
			//	7)	"\'\'\'\"\""
			//	8)	"abc def ghi"
			//
			//	9)	#define WIBBLE " this is a test string \
			//		This too" " - and this!"
			//
			//	10)	#define WIBBLE2 " this is a test string \\ abc \
			//		This too" " - and this!"
			//
			//  11) #pragma message("Quotation with brackets (;') and other \'nasty\' things! inside it__\\");
			//
			#endregion

			if	( aToken.Class == SymToken.TClass.EClassQuotation )
			{
				#region Token is a quotation ...
				// Quotation symbol whilst already in a quotation.
				// We should check whether we have reached
				// the closing quotation symbol, or then whether
				// this is possibly just an escaped character?
				//
				// See examples 2,3,5,6,7,10,11

				SymToken previousToken = PreviousOutputToken;
				if	( previousToken.Class == SymToken.TClass.EClassSymbol && previousToken.Value == @"\" )
				{
					// Combine the \' or \" with any previous token
					previousToken.ForceCombine( aToken );
					System.Diagnostics.Debug.Assert( iCache.Count > 0 );
				}
				else
				{
					// The last token was not an escape marker, so this
					// is a quotation character all on its own. Since
					// we always start a new cache run when we first see
					// a quotation (during "normal" state), then the
					// first token in the cache forms the basis for the
					// search character.
					// 
					// If the number of tokens in the cache with the same
					// type (as the first token) is even, then we have
					// reached the end of a quotation. If its odd, then
					// we're still inside one.

					SymToken initialQuotationToken = iCache.PeekHead;
					System.Diagnostics.Debug.Assert( initialQuotationToken.Value.Length == 1 );
					System.Diagnostics.Debug.Assert( initialQuotationToken.Class == SymToken.TClass.EClassQuotation );
					System.Diagnostics.Debug.Assert( initialQuotationToken.Type == SymToken.TType.ETypeQuotationDouble || initialQuotationToken.Type == SymToken.TType.ETypeQuotationSingle );

					if	( initialQuotationToken.Value == aToken.Value )
					{
						// Need to check for a closing quotation. The count in the cache
						// should be odd (so that adding aToken makes a balanced set of
						// quotation characters). 
						int count = iCache.CountByType( initialQuotationToken );
						int remainder = count % 2;
						if	( remainder == 1 )
						{
							// Odd number which means that the quotation is treated as complete
							System.Diagnostics.Debug.Assert( aToken.Value == initialQuotationToken.Value );
							EnqueueNewOutputToken( aToken );

							#region Try to group all of the text into a logical string

							// No sense in doing this unless we have more than 3 tokens
							count = iCache.Count;
							if	( count > 3 )
							{
								// Assume we have the following string:
								// "marker.h"
								//
								// This is actually represented as 5 tokens:-
								//
								//	0 ["] => EClassQuotation
								//	1 [marker] => EClassQuotation
								//	2 [.] => EClassQuotation
								//	3 [h] => EClassQuotation
								//	4 ["] => EClassQuotation
								//
								// We need to merge tokens at indicies 1, 2 and 3 into a 
								// single token. 

								iCache.MergeAllTokensWithinRange( 1, count - 1, false, true );
							}
							#endregion

							FlushCache();
						}
						else
						{
							EnqueueNewOutputToken( aToken );
						}
					}
					else
					{
						// It wasn't the closing quotation, so just queue it up
						EnqueueNewOutputToken( aToken );
					}
				}
				#endregion
			}
			else
			{
				#region Token is not a quotation...
				// We'll try to combine the tokens as much as is possible.
				if	( aToken.Class == SymToken.TClass.EClassNewLine )
				{
					#region Handle new line during quotation...
					// Checking for continuations...
					//
					// If the last token was not a backshash marker, then
					// we should flush the cache (reset state).
					SymToken previousToken = PreviousOutputToken;
					if	( previousToken.Class == SymToken.TClass.EClassSymbol && previousToken.Value == @"\" )
					{
						// The last token was an backslash. This means we
						// are dealing with a similar case to examples 9 & 10.

						// Discard new line
						previousToken.Class = SymToken.TClass.EClassContinuation;
					}
					else
					{
						// The last token wasn't a continuation character
						// which means this is a "normal" EOL scenario.
						// Just add the token and flush the cache. Mind you, this actually
						// means the content is invalid.
						EnqueueNewOutputToken( aToken );
						FlushCache();
					}
					#endregion
				}
				else if	( aToken.Class == SymToken.TClass.EClassSymbol && aToken.Value == @"\" )
				{
					SymToken previousToken = PreviousOutputToken;
					if	( previousToken.Class == SymToken.TClass.EClassSymbol && previousToken.Value == @"\" )
					{
						// Example 10 - an escaped backslash. Combine the 
						// previous token (a backslash) with the new token
						// then join this new combined token with the previous.
						// Phew.
						MergeWithPreviousToken( aToken );
						PreviousOutputToken.Class = SymToken.TClass.EClassQuotation;
					}
					else
					{
						// This should not be combined until we know
						// what the next character is.
						EnqueueNewOutputToken( aToken );
					}
				}
				else
				{
					// Irrespective of what class the token is
					// currently, we treat it as part of a quotation.
					aToken.Class = SymToken.TClass.EClassQuotation;

					// If the previous character wasn't a quotation, 
					EnqueueNewOutputToken( aToken );
				}
				#endregion
			}
		}

		private void ProcessTokenDuringComment( SymToken aToken )
		{
			#region Comment examples
			//		// this is a comment
			//		/* this is also a comment */
			//		// "This is another comment"
			//		// This is a comment with a continuation \
			//		   and here's the rest.
			#endregion

			System.Diagnostics.Debug.Assert( iCache.Count > 0 );

			if	( aToken.Class == SymToken.TClass.EClassSymbol && aToken.Value == "*" )
			{
				#region Ensure asterisk is not merged with other comments
				// The asterisk character is separated from 
				// the rest of the comment in order that we can
				// ascertain when the end of a block comment has
				// been reached.
				EnqueueNewOutputToken( aToken );
				#endregion
			}
			else if ( aToken.Class == SymToken.TClass.EClassNewLine )
			{
				#region New line during comment...

				// Checking for continuations...
				SymToken previousToken = PreviousOutputToken;
				if	( previousToken.Value == @"\" )
				{
					// Discard new line
					previousToken.Class = SymToken.TClass.EClassContinuation;
				}
				else
				{
					// If we're in a block comment, then we don't flush when we
					// see a new line token.
					SymToken firstToken = iCache.PeekHead;
					EnqueueNewOutputToken( aToken );
					//
					if	( firstToken.Type == SymToken.TType.ETypeCommentFullLine )
					{
						// Flushing the cache resets the flags...
						FlushCache();
					}
					else if ( firstToken.Type == SymToken.TType.ETypeCommentBlock )
					{
						// Don't end the comment until we see the closing block token.
					}
				}
				#endregion
			}
			else if ( aToken.Class == SymToken.TClass.EClassSymbol && aToken.Value == "/" )
			{
				#region Handle Closing Comment Block [ */ ]
				// For ending a comment region, we must have at least one token
				// already in the cache.
				SymToken previousToken = PreviousOutputToken;

				// Check whether previous token was a "*" - we might be closing a block comment
				if	( previousToken.Class == SymToken.TClass.EClassSymbol && previousToken.Value == "*" )
				{
					// Check whether first token was an opening block
					SymToken firstToken = iCache.PeekHead;
					if	( firstToken.Type == SymToken.TType.ETypeCommentBlock && firstToken.Value == "/*" )
					{
						// End of a block reached. Combine the closing "/" with the asterisk we already
						// have in order to form a closing "*/" block token. 
						previousToken.Combine( aToken );
						previousToken.Class = SymToken.TClass.EClassComment;
						previousToken.Type = SymToken.TType.ETypeCommentBlock;

						// No longer in a comment
						InComment = false;
					}
				}
				#endregion
			}
			else if ( aToken.Class == SymToken.TClass.EClassSymbol && aToken.Value == @"\" )
			{
				#region Handle possible continuation during comment
				// We treat the possible continuation character as a comment.
				// If the next character that arrives is really a new line, then we change
				// the class to continuation and handle the situation accordingly...
				aToken.Class = SymToken.TClass.EClassComment;
				EnqueueNewOutputToken( aToken );
				#endregion
			}
			else
			{
				aToken.Class = SymToken.TClass.EClassComment;

				if	( PreviousOutputToken.Class == SymToken.TClass.EClassContinuation )
				{
					// In this scenario, we don't want to try to merge the specified token with the previous
					// new line character, since new lines must be left intact. Just enque it, ensuring
					// that the token class is suitably updated.
					EnqueueNewOutputToken( aToken );
				}
				else if ( iCache.Count == 1 )
				{
					// We don't want to merge this token with the first token in the
					// cache, or else we won't be able to successfully identify closing
					// block comments
					EnqueueNewOutputToken( aToken );
				}
				else
				{
					System.Diagnostics.Debug.Assert( PreviousOutputToken.CombiningAllowed );
					ForceMergeWithPreviousToken( aToken );
				}
			}
		}

		private void ProcessTokenDuringPreProcessorDirective( SymToken aToken )
		{
			#region PreProcessor examples
			// 1)		#_ pragma "This is invalid"
			//			
			// 2)		#\
			//			pragma message("hello")
			//			
			// 3)		#\
			//			define TEST
			//			
			// 4)		# \
			//			 define TEST
			//			
			// 5)		# \\
			//			 define INVALID_DEFINE
			//			
			// 6)		# pragma "This is a valid \
			//							pragma which contains a quotation"
			//
			// 7)		#define LOG_FUNC XLeaveDetector __instrument; \ 
			//											TCleanupItem __cleanupItem(XLeaveDetector::LeaveOccurred, &__instrument); \ 
			//											CleanupStack::PushL(__cleanupItem); 
			#endregion

			// NB. We only stay in "preprocessor mode" until we've identified
			// the preprocessor type,i.e. the first non-whitespace word that
			// appears after the initial hash sign.
			bool validPreProcessorDirective = true;
			int cacheCount = iCache.Count;
			System.Diagnostics.Debug.Assert( cacheCount > 0 );
			System.Diagnostics.Debug.Assert( iCache.PeekHead.Class == SymToken.TClass.EClassPreProcessor && iCache.PeekHead.Value == "#" );
			
			// Handle case 5 first of all. If the previous token was a possible
			// continuation, then this next token must be a new line. If its not,
			// then the PP statement is invalid.
			SymToken previousToken = PreviousOutputToken;
			if	( previousToken.Class == SymToken.TClass.EClassSymbol && previousToken.Value == @"\" )
			{
				#region Handle new line character - checking for continuations
				if	( aToken.Class == SymToken.TClass.EClassNewLine )
				{
					previousToken.Class = SymToken.TClass.EClassContinuation;
				}
				else
				{
					// Borked.
					validPreProcessorDirective = false;
				}
				#endregion
			}
			else
			{
				// The next token HAS to be an alphanumeric or then a whitespace.
				// If its not, we're borked.
				if	( aToken.Class == SymToken.TClass.EClassAlphaNumeric && aToken.Type == SymToken.TType.ETypeAlphaNumericNormal )
				{
					#region Handle identified preprocessor command
					// Token was okay - and we can switch back to normal mode
					// now as we've grabbed our preprocessor command.
					aToken.Class = SymToken.TClass.EClassPreProcessor;
					EnqueueNewOutputToken( aToken );
					InPreProcessorDirective = false;
					#endregion
				}
				else if ( aToken.Class == SymToken.TClass.EClassWhiteSpace )
				{
					// Token is okay, but don't change mode yet. We still need an alphanumeric word.
				}
				else if ( aToken.Class == SymToken.TClass.EClassSymbol && aToken.Value == @"\" )
				{
					#region Handle possible continuation
					// Possibly a valid continuation character prior to seeing the first
					// preprocessor command. For this to be really valid, we must only
					// have seen whitespace between the first token and now.
					bool everythingExceptFirstTokenIsWhiteSpace = iCache.CheckTokensAreOfClass( SymToken.TClass.EClassWhiteSpace, 1 );
					if	( everythingExceptFirstTokenIsWhiteSpace )
					{
						// Could be a continuation character, but only if the next char is a new line
						EnqueueNewOutputToken( aToken );
					}
					else
					{
						// Borked - we've seen non-whitespace. Actually I don't think we can
						// ever come here anyway
						System.Diagnostics.Debug.Assert( false );
						validPreProcessorDirective = false;
					}
					#endregion
				}
				else
				{
					// Something else -> borked.
					validPreProcessorDirective = false;
				}
			}

			#region Handle detection of invalid preprocessor line
			if	( validPreProcessorDirective == false )
			{
				// Token is not valid - this isn't a valid preprocessor directive. 
				// Reset state, update previous character so that its marked as a symbol
				// and bail out.
				InPreProcessorDirective = false;
				iCache.PeekHead.Class = SymToken.TClass.EClassSymbol;
				EnqueueNewOutputToken( aToken );
			}
			#endregion
		}
		#endregion

		#region Internal cache manipulation methods
		private void FlushCache()
		{
#if SHOW_FLUSHED_TOKENS
			StringBuilder debugListing = new StringBuilder();
			foreach( SymToken token in iCache )
			{
				if	( token.Class == SymToken.TClass.EClassNewLine )
				{
					debugListing.Append( "[NL] " );
				}
				else
				{
					debugListing.Append( "[" + token.Value + "] ");
				}
			}
			if	( debugListing.Length > 0 )
			{
				System.Diagnostics.Debug.WriteLine( debugListing.ToString() );
			}
#endif

			foreach( SymToken token in iCache )
			{
				if	( MastermindObservers != null )
				{
					MastermindObservers( TEvent.EEventGroupTokenReady, token );
				}
			}

			iCache.Reset();
			ResetState();
		}

		private SymToken NextInputToken()
		{
			SymToken ret = null;
			//
			lock( iLexedTokens )
			{
				if	( iLexedTokens.Count > 0 )
				{
					ret = iLexedTokens.Dequeue();
				}
			}
			//
			return ret;
		}

		private SymToken PreviousOutputToken
		{
			get
			{
				SymToken ret = SymToken.NullToken();
				if	( iCache.Count > 0 )
				{
					SymToken previousToken = (SymToken) iCache.PeekTail;
					ret = previousToken;
				}
				return ret;
			}
		}

		private void EnqueueNewOutputToken( SymToken aToken )
		{
			if	( CheckIfStateChangeRequiredForEnqueuedToken( aToken ) == false )
			{
				//System.Console.WriteLine( "Enqueue [" + aToken.Value + "]" );
				iCache.Append( aToken );
			}
		}

		private void MergeWithPreviousTwoTokens( SymToken aNewToken, SymToken.TClass aNewClassType )
		{
			System.Diagnostics.Debug.Assert( iCache.Count > 0 );

			SymToken previousToken = iCache.PopTail();

			// Combine it with the new token...
			previousToken.Combine( aNewToken );
			previousToken.Class = aNewClassType;

			// And combine any previous previous token
			MergeWithPreviousToken( previousToken );
		}

		private void MergeWithPreviousToken( SymToken aNewToken )
		{
			if	( iCache.Count > 0 )
			{
				if	( CheckIfStateChangeRequiredForEnqueuedToken( aNewToken ) == false )
				{
					SymToken previousOutputToken = PreviousOutputToken;
					previousOutputToken.Combine( aNewToken );
				}
			}
			else
			{
				EnqueueNewOutputToken( aNewToken );
			}
		}

		private void ForceMergeWithPreviousToken( SymToken aNewToken )
		{
			if	( iCache.Count > 0 )
			{
				if	( CheckIfStateChangeRequiredForEnqueuedToken( aNewToken ) == false )
				{
					SymToken previousOutputToken = PreviousOutputToken;
					previousOutputToken.ForceCombine( aNewToken );
				}
			}
			else
			{
				EnqueueNewOutputToken( aNewToken );
			}
		}
		#endregion

		#region Internal state related methods
		private void ResetState()
		{
			iFlags = TStateFlag.EStateFlagUnspecified;
		}

		private bool CheckIfStateChangeRequiredForEnqueuedToken( SymToken aToken )
		{
			// NB. This method is called before aToken has been enqueued
			// or in the case of combining, before the token has been combined
			// with any prior token.
			bool tokenProcessed = false;

			if	( InQuotation )
			{
			}
			else if ( InComment )
			{
			}
			else if ( InPreProcessorDirective )
			{
			}
			else
			{
				if	( aToken.Class == SymToken.TClass.EClassQuotation )
				{
					#region Handle start of quotation
					if	( iCache.Count > 0 )
					{
						// Check whether the previous symbol was a backslash. If it was
						// then this must be an escaped " or ' character, in which case
						// we don't change state.
						SymToken previousToken = PreviousOutputToken;

						if	( previousToken.Class == SymToken.TClass.EClassSymbol && previousToken.Value == @"\" )
						{
							// Last character was an escape marker. Combine it
							// with the quotation
							previousToken.Combine( aToken );

							// Already handled the token
							tokenProcessed = true;
						}
						else
						{
							// Really are starting a quotation.
							FlushCache();
							InQuotation = true;
						}
					}
					#endregion
				}
				else if ( aToken.Class == SymToken.TClass.EClassSymbol )
				{
					if ( aToken.Value == "*" ) 
					{
						#region Handle Opening comment block [ /* ]
						if	( iCache.Count > 0 )
						{
							SymToken previousToken = PreviousOutputToken;
							//
							if	( previousToken.Class == SymToken.TClass.EClassSymbol && previousToken.Value == "/" ) 
							{
								// "/*" case
								//
								// In this scenario, in order to ensure that we do not
								// flush the first character of our comment marker, we must
								// dequeue the tail item, then flush, then enqueue. 
								SymToken tailToken = iCache.PopTail(); // -> this is the initial "/" that we pop...
								FlushCache();

								// Forward slash and asterisk are combined
								tailToken.Combine( aToken );

								// Mark the token as a full line comment
								tailToken.Class = SymToken.TClass.EClassComment;
								tailToken.Type = SymToken.TType.ETypeCommentBlock;

								// ...and re-added to the cache
								iCache.Append( tailToken );

								// aToken was already combined so we don't want the caller
								// to add it twice.
								tokenProcessed = true;

								// We're now in a full line comment. 
								InComment = true;
							}
						}
						#endregion
					}
					else if ( aToken.Value == "/" )
					{
						#region Handle Full-Line comment [ // ]
						if	( iCache.Count > 0 )
						{
							SymToken previousToken = PreviousOutputToken;
							//
							if	( previousToken.Value == aToken.Value ) 
							{
								// "//" case
								//
								// In this scenario, in order to ensure that we do not
								// flush the first character of our comment marker, we must
								// dequeue the tail item, then flush, then enqueue. 
								SymToken tailToken = iCache.PopTail(); // -> this is the initial "/" that we pop...
								FlushCache();

								// Two forward slashes are combined into one.
								tailToken.Combine( aToken );

								// Mark the token as a full line comment
								tailToken.Class = SymToken.TClass.EClassComment;
								tailToken.Type = SymToken.TType.ETypeCommentFullLine;

								// ...and re-added to the cache
								iCache.Append( tailToken );

								// aToken was already combined so we don't want the caller
								// to add it twice.
								tokenProcessed = true;

								// We're now in a full line comment. 
								InComment = true;
							}
						}
						#endregion
					}
				}
				else if ( aToken.Class == SymToken.TClass.EClassPreProcessor )
				{
					#region Handle start of preprocessor directive
					// Preprocessor directives must only appear on a line
					// after whitespace. If there was any non-whitespace
					// characters before the preprocessor directive, then its illegal.
					bool tokensAreAllWhiteSpace = iCache.CheckTokensAreOfEitherClass( SymToken.TClass.EClassWhiteSpace, SymToken.TClass.EClassNewLine );
					if	( aToken.Value == "#" && tokensAreAllWhiteSpace )
					{
						// Starting a preprocess directive
						FlushCache();
						InPreProcessorDirective = true;
					}
					#endregion
				}
			}

			return tokenProcessed;
		}

		#endregion

		#region Internal state properties
		private bool InQuotation
		{
			get
			{
				bool ret = ( ( iFlags & TStateFlag.EStateFlagInQuotation ) == TStateFlag.EStateFlagInQuotation );
				return ret;
			}
			set
			{
				if	( value )
				{
					iFlags |= TStateFlag.EStateFlagInQuotation;
				}
				else
				{
					iFlags &= ~TStateFlag.EStateFlagInQuotation;
				}
			}
		}

		private bool InComment
		{
			get
			{
				bool ret = ( ( iFlags & TStateFlag.EStateFlagInComment ) == TStateFlag.EStateFlagInComment );
				return ret;
			}
			set
			{
				if	( value )
				{
					iFlags |= TStateFlag.EStateFlagInComment;
				}
				else
				{
					iFlags &= ~TStateFlag.EStateFlagInComment;
				}
			}
		}

		private bool InPreProcessorDirective
		{
			get
			{
				bool ret = ( ( iFlags & TStateFlag.EStateFlagInPreProcessorDirective ) == TStateFlag.EStateFlagInPreProcessorDirective );
				return ret;
			}
			set
			{
				if	( value )
				{
					iFlags |= TStateFlag.EStateFlagInPreProcessorDirective;
				}
				else
				{
					iFlags &= ~TStateFlag.EStateFlagInPreProcessorDirective;
				}
			}
		}
		#endregion

		#region Data members
		private SymLexedTokens iLexedTokens = new SymLexedTokens();
		private SymGrouperMastermindCache iCache = new SymGrouperMastermindCache();
		private TStateFlag iFlags = TStateFlag.EStateFlagUnspecified;
		#endregion
	}
}