Click here to Skip to main content
15,886,689 members
Articles / Desktop Programming / MFC

Include File Hierarchy Viewer

Rate me:
Please Sign up or sign in to vote.
4.84/5 (108 votes)
29 Jul 2003CPOL8 min read 381K   4.5K   151  
A tool to view the include file hierarchy of your source code.
//====================================================================
// Although great care has gone into developing this software,
// it is provided without any guarantee of reliability, accuracy
// of information, or correctness of operation.  I am not responsible
// for any damages that may occur as a result of using this software.
// Use this software entirely at your own risk.
// Copyright 2003, Chris Richardson
//
// Description: Simple #include parser.
//
//====================================================================

#include "stdafx.h"
#include "Parser.h"

#include "MMFStream.h"
#include "FILEStream.h"
#include "Lexer.h"

#include "IncludeFinder.h"
#include "DSObjects.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//////////////////////////////////////////////////////////////////////
// Operator precedence stuff.
//////////////////////////////////////////////////////////////////////

// Operator precedence definition (one struct per operator).
typedef struct tagOperatorDef
{
   ParseTokens eToken;
   int         iPrec;
   int         iOperands;
}OperatorDef;

// Operator precedence table:
static const OperatorDef s_astOpPrecedence[] =
{
   { TOKEN_EQUAL,                   0, 2 },
   { TOKEN_BAR_BAR,                 1, 2 },
   { TOKEN_AMPERSAND_AMPERSAND,     2, 2 },
   { TOKEN_BAR,                     3, 2 },
   { TOKEN_HAT,                     4, 2 },
   { TOKEN_AMPERSAND,               5, 2 },
   { TOKEN_EQUAL_EQUAL,             6, 2 },
   { TOKEN_EXCLAMATION_EQUALS,      6, 2 },
   { TOKEN_LEFT,                    7, 2 },
   { TOKEN_LEFT_EQUALS,             7, 2 },
   { TOKEN_RIGHT,                   7, 2 },
   { TOKEN_RIGHT_EQUALS,            7, 2 },
   { TOKEN_PLUS,                    8, 2 },
   { TOKEN_MINUS,                   8, 2 },
   { TOKEN_MULTIPLY,                9, 2 },
   { TOKEN_DIVIDE,                  9, 2 },
   { TOKEN_MODULUS,                 9, 2 },
   { TOKEN_EXCLAMATION,             10, 1 },
   { TOKEN_TILDE,                   10, 1 },
   { TOKEN_MINUS,                   10, 1 },
   { TOKEN_PLUS,                    10, 1 },
};

//////////////////////////////////////////////////////////////////////
// CParser Implementation.
//////////////////////////////////////////////////////////////////////

CParser::CParser( CIncludeFinderInfo * p_poInfo ) :
   c_poLexer( NULL ),
   c_poLastFile( NULL ),
   c_poParentFile( NULL ),
   c_poResults( NULL ),
   c_hStopEvent( NULL ),
   c_poFindInfo( p_poInfo ),
   c_lPreprocessorExcluding( 0 )
{
}
//
// ------------------------------------------------------------------
//
CParser::~CParser()
{
   c_poLastFile = NULL;
   c_poParentFile = NULL;
   c_poResults = NULL;
   c_poFindInfo = NULL;
}
//
// ------------------------------------------------------------------
//
long CParser::Precedence( ParseTokens p_eOp, int p_iOps )
{
   // Look up the precedence of the operator p_eOp, taking care that the unary version of the operator
   // may have different precedence than the binary version.
   for( int i = 0; i<sizeof( s_astOpPrecedence ) / sizeof( s_astOpPrecedence[0] ); ++i )
   {
      if( s_astOpPrecedence[i].eToken == p_eOp && s_astOpPrecedence[i].iOperands == p_iOps )
         // Found the operator.
         return s_astOpPrecedence[i].iPrec;
   }

   // We couldn't find the operator.  The error will be handled by the parser later.
   return -1;
}
//
// ------------------------------------------------------------------
//
CParsedFile * CParser::Lookup( const TCHAR * p_pszFile )
{
   // Try to lookup an object in the current file's included file list for the specified file.
   CParsedFile * a_poFile = NULL;
   CParsedFileList::const_iterator a_oIter = c_poResults->begin();
   while( a_oIter != c_poResults->end() )
   {
      a_poFile = *a_oIter;
      if( _tcsicmp( a_poFile->GetFileName().c_str(), p_pszFile ) == 0 )
         return a_poFile;

      a_oIter++;
   }

   return NULL;
}
//
// ------------------------------------------------------------------
//
void CParser::SetSettings( CDSParsedSettings & p_roSettings )
{
   c_oIncludeDirs.clear();
   c_oDefinedVars.clear();

   int i = 0;

   CStdStringArray & a_roIncludes = p_roSettings.GetIncludes();
   for( i = 0; i<a_roIncludes.size(); ++i )
   {
      c_oIncludeDirs.push_back( a_roIncludes[i] );
   }

   CStdStringArray & a_roDefines = p_roSettings.GetDefines();
   for( i = 0; i<a_roDefines.size(); ++i )
   {
      ParseNode a_stNode;
      c_oDefinedVars.insert( CStdMapStringToNode::value_type( a_roDefines[i], a_stNode ) );
   }
}
//
// ------------------------------------------------------------------
//
CParsedFile * CParser::LookupInParent( CParsedFile * p_poParent, const TCHAR * p_pszFile )
{
   bool a_bRecurse = true;
   if( p_poParent->GetAlreadyIncluded() )
   {
      // The file was already checked.  We don't want to recurse because
      // we might infinitely recurse (i.e, "rpc.h" includes "windows.h", which includes "rpc.h").
      a_bRecurse = false;
   }
   p_poParent->SetAlreadyIncluded( true );

   if( a_bRecurse )
   {
      // Look in the parent file's included file list.
      CParsedFileArray & a_roFiles = p_poParent->GetIncludedFiles();
      CParsedFileArray::iterator a_oIter;
      for( a_oIter = a_roFiles.begin(); a_oIter != a_roFiles.end(); ++a_oIter )
      {
         CParsedFile * a_poFile = *a_oIter;
         if( a_poFile->GetFileName().compare( p_pszFile ) == 0 )
            return a_poFile;
         else
         {
            // Down thru the include tree we go...
            a_poFile = LookupInParent( a_poFile, p_pszFile );
            if( a_poFile )
               return a_poFile;
         }
      }
   }

   if( p_poParent->GetFileName().compare( p_pszFile ) == 0 )
      // Last but not least, check the parent.
      return p_poParent;

   return NULL;
}
//
// ------------------------------------------------------------------
//
ParseStatus CParser::Eat( ParseTokens p_eToken )
{
   if( c_oToken.c_eType != p_eToken )
      return PARSE_STATUS_GENERIC_ERROR;
   
   // Eat the current token.
   c_poLexer->Lex( &c_oToken );
   return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
ParseStatus CParser::Parse( const TCHAR *       p_pszFile,
                            CParsedFileList *   p_poResults,
                            HANDLE              p_hStopEvent )
{
   c_hStopEvent   = p_hStopEvent;
   c_poResults    = p_poResults;
   c_poParentFile = NULL;
   c_lPreprocessorExcluding = 0;
   c_oPreprocessorExpressionStack.clear();

   return ParseInternal( p_pszFile );
}
//
// ------------------------------------------------------------------
//
ParseStatus CParser::ParseInternal( const TCHAR * p_pszFile )
{
   if( WaitForSingleObject( c_hStopEvent, 1 ) == WAIT_OBJECT_0 )
      return PARSE_STATUS_OK;

   ParseStatus a_eStatus = PARSE_STATUS_OK;
   CMMFStream a_oStream;
   CLexer      a_oLexer;

   TCHAR       a_szFile[MAX_PATH+1] = {0};
   _tcscpy( a_szFile, p_pszFile );
   _tcslwr( a_szFile );
   
   c_poLastFile = NULL;
   if( !c_poFindInfo->c_bPreprocess )
   {
      // We may have already parsed the file, so let's check.
      // If we haven't parsed it yet, we'll add it to our list of parsed files.
      c_poLastFile = Lookup( p_pszFile );
      if( c_poLastFile )
      {
         // We've already parsed the file.
         return PARSE_STATUS_OK;
      }
   }
   else
   if( c_poParentFile )
   {
      c_poLastFile = LookupInParent( c_poParentFile, p_pszFile );
      c_poParentFile->SetAlreadyIncluded( false );
   }

   if( !c_poLastFile )
   {
      // Create a new entry for the file.
      c_poLastFile = new CParsedFile( p_pszFile );
      c_poResults->push_back( c_poLastFile );
      
   }
   if( !c_poParentFile )
   {
      // The parent file represents the first file for each set (the source file in most cases).
      c_poParentFile = c_poLastFile;
   }

   // Notify that we are starting on a file.
   c_poFindInfo->c_poStatusNotifier->OnFileStart( c_poLastFile );

   // Try to open a stream on the file.
   a_eStatus = a_oStream.Open( p_pszFile );
   if( a_eStatus == PARSE_STATUS_OK )
   {
      a_oLexer.SetStream( &a_oStream );

      // Push our current lexer pointer and store the new one.
      CLexer * a_poPushLexer = c_poLexer;
      c_poLexer = &a_oLexer;
   
      // Push the current parsed file.
      CParsedFile * a_poPushFile = c_poLastFile;
   
      c_oLexerStack.push_back( &a_oLexer );

      // Now parse the file.
      a_eStatus = ParseFile();

      // Set the line count of the file.
      a_poPushFile->SetLineCount( a_oLexer.GetLine() );

      // Pop the current parsed file.
      c_poLastFile = a_poPushFile;

      // Pop the lexer pointer.
      c_poLexer = a_poPushLexer;
      c_oLexerStack.pop_back();
   }
   
   return a_eStatus;
}
//
// ------------------------------------------------------------------
//
ParseStatus CParser::ParseFile()
{
   ParseStatus a_eStatus = PARSE_STATUS_OK;

   if( !c_poLexer )
      return PARSE_STATUS_FILE_NOT_OPEN;

   a_eStatus = c_poLexer->Lex( &c_oToken );
   if( a_eStatus != PARSE_STATUS_OK )
      return a_eStatus;

   // Iterate through all the tokens in the file, looking for "#" characters, which are
   // the only one's we are interested in.
   while( 1 )
   {
      switch( c_oToken.c_eType )
      {
         case TOKEN_POUND:
         {
            // Check that we are at the start of the line, after whitespace.
            if( c_poLexer->GetLinePos() == 0 )
            {
               a_eStatus = ParsePreprocessorStatement();
            }
            else
               Eat( TOKEN_POUND );
            
            break;
         }
         
         default:
            Eat( c_oToken.c_eType );
            break;
      }


      if( a_eStatus != PARSE_STATUS_OK || c_oToken.c_eType == TOKEN_EOF )
         break;
   }

   return a_eStatus;
}
//
// ------------------------------------------------------------------
//
ParseStatus CParser::ParsePreprocessorStatement()
{
   ParseStatus a_eStatus = PARSE_STATUS_OK;
   
   // Eat the pound character.
   a_eStatus = Eat( TOKEN_POUND );
   if( a_eStatus != PARSE_STATUS_OK )
      return a_eStatus;

   long a_lProcessCodeInPreprocessorBlock = 1;
   
   if( !_tcscmp( c_oToken.c_szID, _T("include") ) && !c_lPreprocessorExcluding )
   {
      // Set the lexer to preprocessor mode, so it won't process escape sequences, and will allow <include.h> style strings.
      c_poLexer->SetLexMode( CLexer::LEX_MODE_PREPROCESSOR );
      
      Eat( TOKEN_IDENTIFIER );
      
      // Set the lexer back to regular parser mode.
      c_poLexer->SetLexMode( CLexer::LEX_MODE_PARSER );

      if( c_oToken.c_eType != TOKEN_QUOTED_STRING && c_oToken.c_eType != TOKEN_BRACKET_STRING )
         return PARSE_STATUS_OK;
      
      CToken a_oPushToken = c_oToken;

      // We have to find the full path to the file, and where we start looking depends on whether
      // the string is in brackets or quotes.
      CParsedFile * a_poFile = NULL;
      if( c_poFindInfo->c_bPreprocess )
      {
         a_poFile = LookupInParent( c_poParentFile, c_poLexer->GetName() );
         c_poParentFile->SetAlreadyIncluded( false );
      }
      else
         a_poFile = Lookup( c_poLexer->GetName() );
      
      TCHAR a_szFullPath[MAX_PATH+1] = {0};
      if( FindFullPath( c_oToken.c_szID, a_szFullPath, c_oToken.c_eType == TOKEN_BRACKET_STRING ) )
      {
         // We found the full path to the file.
         
         CParsedFile * a_poIncludedFile = NULL;
         if( c_poFindInfo->c_bPreprocess )
         {
            a_poIncludedFile = LookupInParent( c_poParentFile, a_szFullPath );
            c_poParentFile->SetAlreadyIncluded( false );
            if( !a_poIncludedFile )
            {
               // The file hasn't been included yet, so parse it.
               a_poIncludedFile = new CParsedFile( a_szFullPath );
               c_poResults->push_back( a_poIncludedFile );
               a_poFile->AddIncludedFile( a_poIncludedFile );
               a_poIncludedFile->AddIncludedByFile( a_poFile );

               // Parse the included file.
               a_eStatus = ParseInternal( a_szFullPath );
            }
            else
            {
               // The file has already been included by the parent file (or somewhere inbetween),
               // so don't parse it again, just add it to the list.
               a_poFile->AddIncludedFile( a_poIncludedFile );
               a_poIncludedFile->AddIncludedByFile( a_poFile );
            }
         }
         else
         {
            // Parse the file.
            a_eStatus = ParseInternal( a_szFullPath );
            a_poIncludedFile = Lookup( a_szFullPath );
            if( a_poIncludedFile )
            {
               a_poIncludedFile->AddIncludedByFile( a_poFile );
               a_poFile->AddIncludedFile( a_poIncludedFile );
            }
         }
      }
      else
      {
         // We couldn't find the included file, so we'll add it to the "error" list.
         CParsedFile * a_poIncludedFile = Lookup( a_szFullPath );
         if( !a_poIncludedFile )
         {
            // Create a new entry for the file.
            a_poIncludedFile = new CParsedFile( a_szFullPath );
            c_poResults->push_back( a_poIncludedFile );
         }
         a_poIncludedFile->SetNotFoundFlag();
         a_poFile->AddIncludedFile( a_poIncludedFile );
      }

      c_oToken = a_oPushToken;
   }
   else
   if( c_poFindInfo->c_bPreprocess ) // Only handle macros and such if the user wants us to.
   {
      if( !_tcscmp( c_oToken.c_szID, _T("define") ) && !c_lPreprocessorExcluding )
      {
         // Does not handle complex macro definitions.
         // It only handles simple cases like
         // #define MY_VAR
         // or
         // #define MY_VAR SOME_EXPRESSION

         unsigned long a_ulLine = c_poLexer->GetLine();
         Eat( TOKEN_IDENTIFIER );

         if( c_oToken.c_eType == TOKEN_IDENTIFIER )
         {
            CToken a_oVarName = c_oToken;
            Eat( TOKEN_IDENTIFIER );

            ParseNode a_stResult;
            if( c_poLexer->GetLine() == a_ulLine )
            {
               // This looks like the "#define MY_VAR SOME_EXPRESSION" case, so we'll parse the associated expression.
               a_stResult = ParsePreprocessorExpression( -1 );
            }
         
            // Insert the variable into the name to value map.
            c_oDefinedVars.insert( CStdMapStringToNode::value_type( a_oVarName.c_szID, a_stResult ) );
         }
      }
      else
      if( !_tcscmp( c_oToken.c_szID, _T("undef") ) && !c_lPreprocessorExcluding )
      {
         Eat( TOKEN_IDENTIFIER );
      
         if( c_oToken.c_eType == TOKEN_IDENTIFIER )
         {
            // Look up the variable.
            CStdMapStringToNode::iterator a_oType = c_oDefinedVars.find( c_oToken.c_szID );
         
            if( a_oType != c_oDefinedVars.end() )
            {
               // Remove the variable from the name map.
               c_oDefinedVars.erase( a_oType );
            }

            Eat( TOKEN_IDENTIFIER );
         }
      }
      else
      if( !_tcscmp( c_oToken.c_szID, _T("ifndef") ) || !_tcscmp( c_oToken.c_szID, _T("ifdef") ) )
      {
         // Remember if it was an "ifndef" versus an "ifdef".
         BOOL a_bIfNDef = !_tcscmp( c_oToken.c_szID, _T("ifndef") );

         Eat( TOKEN_IDENTIFIER );

         if( c_oToken.c_eType == TOKEN_IDENTIFIER )
         {
            // Check to see if the variable has been #defined.
            CStdMapStringToNode::iterator a_oType = c_oDefinedVars.find( c_oToken.c_szID );
         
            if( a_bIfNDef )
            {
               // We want to skip the code in the block if the identifier was DEFINED.
               if( a_oType != c_oDefinedVars.end() )
                  a_lProcessCodeInPreprocessorBlock = 0;
            }
            else
            {
               // We want to skip the code in the block if the identifier was NOT DEFINED.
               if( a_oType == c_oDefinedVars.end() )
                  a_lProcessCodeInPreprocessorBlock = 0;
            }

            // Push the latest value onto the top of the expression stack.
            c_oPreprocessorExpressionStack.push_back( a_lProcessCodeInPreprocessorBlock );
         
            if( !a_lProcessCodeInPreprocessorBlock || c_lPreprocessorExcluding )
               c_lPreprocessorExcluding++;

            Eat( TOKEN_IDENTIFIER );
         }
      }
      else
      if( !_tcscmp( c_oToken.c_szID, _T("if") ) )
      {
         Eat( TOKEN_IDENTIFIER );
   
         // Parse the expression following the #if.
         ParseNode a_stResult = ParsePreprocessorExpression( -1 );
      
         if( !a_stResult.lValue )
            a_lProcessCodeInPreprocessorBlock = 0;
      
         // Push the result of the expression onto the top of the expression stack.
         c_oPreprocessorExpressionStack.push_back( a_lProcessCodeInPreprocessorBlock );
         if( !a_lProcessCodeInPreprocessorBlock || c_lPreprocessorExcluding )
            c_lPreprocessorExcluding++;
      }
      else
      if( !_tcscmp( c_oToken.c_szID, _T("else") ) || !_tcscmp( c_oToken.c_szID, _T("elif") ) )
      {
         // Remember if it's an "elif" versus an "else".
         BOOL a_bIsElif = !_tcscmp( c_oToken.c_szID, _T("elif") );

         Eat( TOKEN_IDENTIFIER );

         if( c_oPreprocessorExpressionStack.size() == 0 )
         {
            // We've got a problem (missing #if?).
            return PARSE_STATUS_GENERIC_ERROR;
         }
         else
         {
            // Check the value at the top of the stack to determine if we should go into the "else" type case.
            long a_lLastResult = c_oPreprocessorExpressionStack.back();
         
            if( a_bIsElif )
            {
               Eat( TOKEN_IDENTIFIER );
      
               // Process the expression part of the elif.
               ParseNode a_stResult = ParsePreprocessorExpression( -1 );
            
               // Determine if we are going to be excluding the code block or not.
               if( !a_lLastResult )
               {
                  // The last section was not included, so include the next section if the result of the expression was non-zero.
                  a_lProcessCodeInPreprocessorBlock = a_stResult.lValue;
               
                  if( a_stResult.lValue )
                     c_lPreprocessorExcluding--;
               }
               else
                  // The last section was included, so we don't include the next section.
                  a_lProcessCodeInPreprocessorBlock = 0;
      
               if( !a_lProcessCodeInPreprocessorBlock && !c_lPreprocessorExcluding )
                  c_lPreprocessorExcluding++;
            }
            else
            {
               // Determine if we are going to be excluding the code block or not.
               if( a_lLastResult == 1 )
               {
                  // The last section was included, so don't include the next section.
                  a_lProcessCodeInPreprocessorBlock = 0;
                  if( !c_lPreprocessorExcluding )
                     c_lPreprocessorExcluding++;
               }
               else
               if( a_lLastResult == 0 && c_lPreprocessorExcluding == 1 )
                  // The last section was not included, so include the next section.
                  c_lPreprocessorExcluding--;
            }
         
            if( !a_lLastResult )
            {
               // Only push a new result of the last result was 0.
               c_oPreprocessorExpressionStack.pop_back();
               c_oPreprocessorExpressionStack.push_back( a_lProcessCodeInPreprocessorBlock );
            }
         }
      }
      else
      if( !_tcscmp( c_oToken.c_szID, _T("endif") ) )
      {
         Eat( TOKEN_IDENTIFIER );

         if( c_oPreprocessorExpressionStack.size() == 0 )
         {
            // We've got a problem (missing #if?).
            return PARSE_STATUS_GENERIC_ERROR;
         }
         else
         {
            // Decrement the excluding flag.
            if( c_lPreprocessorExcluding > 0 )
               c_lPreprocessorExcluding--;

            // Pop the last value off the top of the expression stack.
            c_oPreprocessorExpressionStack.pop_back();
         }
      }
   }

   return a_eStatus;
}
//
// ------------------------------------------------------------------
//
ParseNode CParser::ParsePreprocessorUnaryExpression( long p_lPrec )
{
   ParseNode a_stLeftSide;
   switch( c_oToken.c_eType )
   {
      case TOKEN_DECIMAL:
      {
         // Floating point values aren't valid for the preprocessor.

         // Get the next token.
         Eat( TOKEN_DECIMAL );
         
         return a_stLeftSide;
      }

      case TOKEN_INTEGER:
      {
         // Get the value of the integer.
         if( c_oToken.c_ulFlags & TOKEN_FLAG_HEX )
            // Scan the hex value.
            _stscanf( c_oToken.c_szID, _T("%x"), &a_stLeftSide.lValue );
         else
         if( c_oToken.c_ulFlags & TOKEN_FLAG_OCT )
            // Scan the octal value.
            _stscanf( c_oToken.c_szID, _T("%o"), &a_stLeftSide.lValue );
         else
            // It's a regular decimal integer.
            a_stLeftSide.lValue = _ttoi( c_oToken.c_szID );
         
         // Get the next token.
         Eat( TOKEN_INTEGER );
         
         a_stLeftSide.OK();
         break;
      }

      case TOKEN_IDENTIFIER:
      {
         if( !_tcscmp( c_oToken.c_szID, _T("defined") ) )
         {
            // This is a special preprocessor operator.
            Eat( TOKEN_IDENTIFIER );
            
            BOOL a_bIsParenthesizedVersion = FALSE;
            if( c_oToken.c_eType == TOKEN_LPAREN )
            {
               Eat( TOKEN_LPAREN );
               
               // Remember it, so we can eat the right parenthesis later.
               a_bIsParenthesizedVersion = TRUE;
            }

            if( c_oToken.c_eType != TOKEN_IDENTIFIER )
               return a_stLeftSide;
            
            // Attempt to lookup the value in the defined variable map.
            CStdMapStringToNode::iterator a_oType = c_oDefinedVars.find( c_oToken.c_szID );
            if( a_oType == c_oDefinedVars.end() )
               a_stLeftSide.lValue = 0;
            else
               a_stLeftSide.lValue = 1;
            
            Eat( TOKEN_IDENTIFIER );

            if( a_bIsParenthesizedVersion )
               if( Eat( TOKEN_RPAREN ) != PARSE_STATUS_OK )
                  return a_stLeftSide;

            a_stLeftSide.OK();
         }
         else
         {
            // Attempt to lookup the value in the defined variable map.
            CStdMapStringToNode::iterator a_oType = c_oDefinedVars.find( c_oToken.c_szID );

            // Get the next token.
            Eat( TOKEN_IDENTIFIER );
         
            if( a_oType != c_oDefinedVars.end() )
               a_stLeftSide = a_oType->second;
            else
               a_stLeftSide.OK();
         }

         break;
      }
      
      case TOKEN_LPAREN:
      {
         // Skip past the left paren..
         Eat( TOKEN_LPAREN );
         
         // Attempt to parse the interior of the parentheses.
         a_stLeftSide = ParsePreprocessorExpression( -1 );

         // Make sure we are still OK.
         if( !a_stLeftSide.bOK || Eat( TOKEN_RPAREN ) != PARSE_STATUS_OK )
            return a_stLeftSide;
         
         a_stLeftSide.OK();
         break;
      }
      
      case TOKEN_EXCLAMATION:
      case TOKEN_TILDE:
      case TOKEN_MINUS:
      case TOKEN_PLUS:
      {
         // Get the precedence of the current operator.
         long a_lPrec = Precedence( c_oToken.c_eType, 1 );

         ParseTokens a_eToken = c_oToken.c_eType;
 
         // Get the next token.
         Eat( c_oToken.c_eType );
         
         // Parse the operand.
         a_stLeftSide = ParsePreprocessorExpression( a_lPrec );
         if( !a_stLeftSide.bOK )
            return a_stLeftSide;

         // Perform the operator.
         switch( a_eToken )
         {
            case TOKEN_EXCLAMATION:
               a_stLeftSide.lValue = !a_stLeftSide.lValue;
               break;
            case TOKEN_TILDE:
               a_stLeftSide.lValue = ~a_stLeftSide.lValue;
               break;
            case TOKEN_MINUS:
               a_stLeftSide.lValue = -a_stLeftSide.lValue;
               break;
            case TOKEN_PLUS:
               a_stLeftSide.lValue = +a_stLeftSide.lValue;
               break;
         }
         
         a_stLeftSide.OK();
         break;
      }

      default:
         // Something's wrong with the input.
         //Error( ERROR_SYNTAX, &c_oToken );
         Eat( c_oToken.c_eType );
         break;
   }

   return a_stLeftSide;
}
//
// ------------------------------------------------------------------
//
ParseNode CParser::ParsePreprocessorExpression( long p_lPrec )
{
   ParseNode a_stLeftSide;
   ParseNode a_stRightSide;
   while( 1 )
   {
      switch( c_oToken.c_eType )
      {
         case TOKEN_DECIMAL:
         case TOKEN_INTEGER:
         case TOKEN_IDENTIFIER:
         case TOKEN_LPAREN:
            if( !a_stLeftSide.bOK )
            {
               // Parse the unary node.
               a_stLeftSide = ParsePreprocessorUnaryExpression( p_lPrec );
               if( !a_stLeftSide.bOK )
                  return a_stLeftSide;

               // Keep going.
               continue;
            }
            else
               // We've already parsed a left side of the expression, and didn't find any binary operators,
               // so it's possible it's just a unary expression
               return a_stLeftSide;

         case TOKEN_RPAREN:
            // This is potentially the termination of the current expression.
            return a_stLeftSide;

         case TOKEN_EOF:
            // We're done.
            return a_stLeftSide;
         
         default:
         {
            if( !a_stLeftSide.bOK )
            {
               // This looks like a unary expression.
               a_stLeftSide = ParsePreprocessorUnaryExpression( p_lPrec );
               if( !a_stLeftSide.bOK )
                  return a_stLeftSide;

               a_stLeftSide.OK();
            }
            else
            {
               // Get the precedence of the current operator.
               // If it's less than or equal to the precedence of the previous operator, we're done with this portion.
               long a_lPrec = Precedence( c_oToken.c_eType, 2 );
               if( a_lPrec <= p_lPrec )
                  return a_stLeftSide;
            
               // While the current precedence is greater than the previous precedence, keep parsing.
               while( a_lPrec > p_lPrec )
               {
                  ParseTokens a_eToken = c_oToken.c_eType;

                  // Get the next token.
                  Eat( c_oToken.c_eType );
                  
                  if( a_eToken == TOKEN_EQUAL )
                  {
                     // Assignment is not currently handled.
                     // It would be straightforward, but is it necessary for a preprocessor evaluator?
                  }
                  else
                  {
                     // Parse the right side of the operator.
                     a_stRightSide = ParsePreprocessorExpression( a_lPrec );
                  }
                  
                  if( !a_stRightSide.bOK )
                     return a_stRightSide;

                  // Perform the operator.
                  switch( a_eToken )
                  {
                     case TOKEN_BAR:
                        a_stLeftSide.lValue = a_stLeftSide.lValue | a_stRightSide.lValue;
                        break;
                     case TOKEN_HAT:
                        a_stLeftSide.lValue = a_stLeftSide.lValue ^ a_stRightSide.lValue;
                        break;
                     case TOKEN_AMPERSAND:
                        a_stLeftSide.lValue = a_stLeftSide.lValue & a_stRightSide.lValue;
                        break;
                     case TOKEN_EQUAL_EQUAL:
                        a_stLeftSide.lValue = a_stLeftSide.lValue == a_stRightSide.lValue;
                        break;
                     case TOKEN_EXCLAMATION_EQUALS:
                        a_stLeftSide.lValue = a_stLeftSide.lValue != a_stRightSide.lValue;
                        break;
                     case TOKEN_LEFT:
                        a_stLeftSide.lValue = a_stLeftSide.lValue < a_stRightSide.lValue;
                        break;
                     case TOKEN_LEFT_EQUALS:
                        a_stLeftSide.lValue = a_stLeftSide.lValue <= a_stRightSide.lValue;
                        break;
                     case TOKEN_RIGHT:
                        a_stLeftSide.lValue = a_stLeftSide.lValue > a_stRightSide.lValue;
                        break;
                     case TOKEN_RIGHT_EQUALS:
                        a_stLeftSide.lValue = a_stLeftSide.lValue >= a_stRightSide.lValue;
                        break;
                     case TOKEN_AMPERSAND_AMPERSAND:
                        a_stLeftSide.lValue = a_stLeftSide.lValue && a_stRightSide.lValue;
                        break;
                     case TOKEN_BAR_BAR:
                        a_stLeftSide.lValue = a_stLeftSide.lValue || a_stRightSide.lValue;
                        break;
                     case TOKEN_PLUS:
                        a_stLeftSide.lValue = a_stLeftSide.lValue + a_stRightSide.lValue;
                        break;
                     case TOKEN_MINUS:
                        a_stLeftSide.lValue = a_stLeftSide.lValue - a_stRightSide.lValue;
                        break;
                     case TOKEN_MULTIPLY:
                        a_stLeftSide.lValue = a_stLeftSide.lValue * a_stRightSide.lValue;
                        break;
                     case TOKEN_DIVIDE:
                        if( a_stRightSide.lValue )
                           a_stLeftSide.lValue = a_stLeftSide.lValue / a_stRightSide.lValue;
                        break;
                     case TOKEN_MODULUS:
                        if( a_stRightSide.lValue )
                           a_stLeftSide.lValue = a_stLeftSide.lValue % a_stRightSide.lValue;
                        break;
                  }

                  if( c_oToken.c_eType == TOKEN_EOF )
                     return a_stLeftSide;
               
                  // Store the new precedence of the current portion of the expression.
                  a_lPrec = Precedence( c_oToken.c_eType, 2 );
               }
            }
            break;
         }
      }
   }

   return a_stLeftSide;
}
//
// ------------------------------------------------------------------
//
BOOL CParser::FindFullPath( const TCHAR *    p_pszFile,
                            TCHAR *          p_pszFullPath,
                            BOOL             p_bIsBracketed )
{
   TCHAR    a_szPath[MAX_PATH+1]          = {0};
   TCHAR    a_szCurrentPath[MAX_PATH+1]   = {0};
   TCHAR    a_szFileName[MAX_PATH+1]      = {0};
   TCHAR    a_szExtra[MAX_PATH+1]         = {0};
   TCHAR *  a_pszSep                      = NULL;
   TCHAR *  a_pszFile                     = NULL;
   BOOL     a_bFoundPath                  = FALSE;

   // Break the complete file name down into the name of the file, and any extra directories specified.
   _tcscpy( a_szPath, p_pszFile );
   a_pszSep = _tcsrchr( a_szPath, _T('\\') );
   if( !a_pszSep )
   {
      // Search for the alternative separator.
      a_pszSep = _tcsrchr( a_szPath, _T('/') );
   }
   if( a_pszSep )
   {
      *a_pszSep = 0;
      _tcscpy( a_szExtra, a_szPath );
      _tcscpy( a_szFileName, a_pszSep+1 );
   }
   else
      _tcscpy( a_szFileName, a_szPath );

   if( !p_bIsBracketed )
   {
      // See if the file is in the same directory as the file currently being parsed,
      // and if it's not there, try the grandparent file, then the great grandparent file and so on.
      for( int i = c_oLexerStack.size() - 1; i >= 0; --i )
      {
         CLexer * a_poLexer = c_oLexerStack[i];

         _tcscpy( a_szPath, a_poLexer->GetName() );
         a_pszSep = _tcsrchr( a_szPath, _T('\\') );
         if( a_pszSep )
            *(a_pszSep) = 0;

         if( i == 0 )
            _tcscpy( a_szCurrentPath, a_szPath ); // Store the current file's directory.
         
         // Thanks to "Oliver Wahl" for the following block:
         // BEGIN
         _tcscat( a_szPath, _T("\\") );
         _tcscat( a_szPath, a_szExtra );
         _tcscat( a_szPath, _T("\\") );
         _tcscat( a_szPath, a_szFileName );
         // END
         if( GetFileAttributes( a_szPath ) != 0xFFFFFFFF )
         {
            a_bFoundPath = TRUE;
            break;
         }
      }
   }
   
   if( !a_bFoundPath )
   {
      // Thanks to "EvanKeats" for the following block:
      // BEGIN
      // Inserted code to set a_szCurentPath
      CLexer * a_poLexerParent = c_oLexerStack[0];
      _tcscpy( a_szCurrentPath, a_poLexerParent->GetName() );
      a_pszSep = _tcsrchr( a_szCurrentPath, _T('\\') );
      if( a_pszSep )
          *(a_pszSep) = 0;
      // END

      // Search for the file in the include directories.
      for( int i = 0; i < c_oIncludeDirs.size(); ++i )
      {
         CTCharString & a_rsIncludeDir = c_oIncludeDirs[i];
      
         // We have two tries here.  The first try will take the current path and the include directory,
         // for cases where we have relative include paths (..\\inc), and the second case will
         // take just the include directory, for cases where we have static include paths (C:\\inc).
         for( int test = 0; test<2; ++test )
         {
            if( test == 0 )
               // Check relative paths.
               _stprintf( a_szPath, _T("%s\\%s"), a_szCurrentPath, a_rsIncludeDir.c_str() );
            else
               // Check static paths.
               _stprintf( a_szPath, _T("%s"), a_rsIncludeDir.c_str() );
         
            // Remove the trailing slash, if it's there.
            int a_iLen = _tcslen( a_szPath );
            if( a_szPath[a_iLen-1] == _T('\\') || a_szPath[a_iLen-1] == _T('/') )
               a_szPath[a_iLen-1] = _T('\0');
         
            // Put in any relative directories that were found in the actual include path.
            if( _tcslen( a_szExtra ) )
            {
               _tcscat( a_szPath, _T("\\") );
               _tcscat( a_szPath, a_szExtra );
            }
         
            // Add on the file name.
            _tcscat( a_szPath, _T("\\") );
            _tcscat( a_szPath, a_szFileName );

            if( GetFileAttributes( a_szPath ) != 0xFFFFFFFF )
            {
               a_bFoundPath = TRUE;
               break;
            }
         }

         if( a_bFoundPath )
            break;
      }
   }

   if( a_bFoundPath )
   {
      // We might have a strange path here (i.e. "C:\\Wherever\\..\\inc\\file.h"), but it's nicer to have a readable path name.
      TCHAR a_szFullPathToFile[MAX_PATH + 1] = {0};
      TCHAR * a_pszFile = NULL;
      GetFullPathName( a_szPath, MAX_PATH + 1, a_szFullPathToFile, &a_pszFile );
      
      _tcscpy( p_pszFullPath, a_szFullPathToFile );
      return TRUE;
   }

   _tcscpy( p_pszFullPath, p_pszFile );
   return FALSE;
}
//
// ------------------------------------------------------------------
//

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I like to program, I like to sail.

Comments and Discussions