2022-08-10 16:03:37 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
using System.Diagnostics ;
using System.IO ;
using System.Collections ;
using System.Collections.Concurrent ;
using EpicGames.Core ;
using System.Diagnostics.CodeAnalysis ;
using UnrealBuildBase ;
using Microsoft.Extensions.Logging ;
namespace UnrealBuildTool
{
/// <summary>
/// Exception class for the preprocessor, which contains the file and position of the code causing an error
/// </summary>
class PreprocessorException : Exception
{
/// <summary>
/// The context when the error was encountered
/// </summary>
PreprocessorContext ? Context ;
/// <summary>
/// Constructor
/// </summary>
/// <param name="Context">The current preprocesor context</param>
/// <param name="Format">Format string, to be passed to String.Format</param>
/// <param name="Args">Optional argument list for the format string</param>
public PreprocessorException ( PreprocessorContext ? Context , string Format , params object [ ] Args )
: base ( String . Format ( Format , Args ) )
{
this . Context = Context ;
}
}
/// <summary>
/// Implementation of a C++ preprocessor.
/// </summary>
class Preprocessor
{
/// <summary>
/// Type of an include path
/// </summary>
public enum IncludePathType
{
/// <summary>
/// Regular include path, enclosed by quotes
/// </summary>
Normal ,
/// <summary>
/// System include path, enclosed by angle brackets
/// </summary>
System ,
}
/// <summary>
/// Include paths to look in
/// </summary>
List < DirectoryItem > IncludeDirectories = new List < DirectoryItem > ( ) ;
/// <summary>
/// Framework paths to look in
/// </summary>
List < DirectoryItem > FrameworkDirectories = new List < DirectoryItem > ( ) ;
/// <summary>
/// Set of all included files with the #pragma once directive
/// </summary>
HashSet < FileItem > PragmaOnceFiles = new HashSet < FileItem > ( ) ;
/// <summary>
/// Set of any files that has been processed
/// </summary>
HashSet < FileItem > ProcessedFiles = new HashSet < FileItem > ( ) ;
/// <summary>
/// The current state of the preprocessor
/// </summary>
PreprocessorState State = new PreprocessorState ( ) ;
/// <summary>
/// Predefined token containing the constant "0"
/// </summary>
static readonly byte [ ] ZeroLiteral = Encoding . UTF8 . GetBytes ( "0" ) ;
/// <summary>
/// Predefined token containing the constant "1"
/// </summary>
static readonly byte [ ] OneLiteral = Encoding . UTF8 . GetBytes ( "1" ) ;
/// <summary>
/// Value of the __COUNTER__ variable
/// </summary>
int Counter ;
/// <summary>
/// List of files included by the preprocessor
/// </summary>
/// <returns>Enumerable of processed files</returns>
public IEnumerable < FileItem > GetProcessedFiles ( )
{
return ProcessedFiles . AsEnumerable ( ) ;
}
/// <summary>
/// Default constructor
/// </summary>
public Preprocessor ( )
{
DateTime Now = DateTime . Now ;
AddLiteralMacro ( "__DATE__" , TokenType . String , String . Format ( "\"{0} {1,2} {2}\"" , Now . ToString ( "MMM" ) , Now . Day , Now . Year ) ) ;
AddLiteralMacro ( "__TIME__" , TokenType . String , "\"" + Now . ToString ( "HH:mm:ss" ) + "\"" ) ;
AddLiteralMacro ( "__FILE__" , TokenType . String , "\"<unknown>\"" ) ;
AddLiteralMacro ( "__LINE__" , TokenType . Number , "-1" ) ;
AddLiteralMacro ( "__COUNTER__" , TokenType . Number , "-1" ) ;
AddLiteralMacro ( "CHAR_BIT" , TokenType . Number , "8" ) ; // Workaround for #include_next not being supported on Linux for limit.h
}
/// <summary>
/// Determines whether the current preprocessor branch is active
/// </summary>
/// <returns>True if the current branch is active</returns>
public bool IsCurrentBranchActive ( )
{
return State . IsCurrentBranchActive ( ) ;
}
/// <summary>
/// Defines a macro. May have an optional '=Value' suffix.
/// </summary>
/// <param name="Definition">Macro to define</param>
public void AddDefinition ( string Definition )
{
List < Token > Tokens = TokenReader . Tokenize ( Definition ) ;
if ( Tokens . Count = = 0 )
{
throw new PreprocessorException ( null , "Missing macro name" ) ;
}
if ( Tokens [ 0 ] . Type ! = TokenType . Identifier )
{
throw new PreprocessorException ( null , "'{0}' is not a valid macro name" , Tokens [ 0 ] . ToString ( ) ! ) ;
}
List < Token > ValueTokens = new List < Token > ( ) ;
if ( Tokens . Count = = 1 )
{
ValueTokens . Add ( new Token ( TokenType . Number , TokenFlags . None , OneLiteral ) ) ;
}
else if ( Tokens [ 1 ] . Type ! = TokenType . Equals )
{
throw new PreprocessorException ( null , "Unable to parse macro definition '{0}'" , Definition ) ;
}
else
{
ValueTokens . AddRange ( Tokens . Skip ( 2 ) ) ;
}
PreprocessorMacro Macro = new PreprocessorMacro ( Tokens [ 0 ] . Identifier ! , null , ValueTokens ) ;
State . DefineMacro ( Macro ) ;
}
/// <summary>
/// Defines a macro
/// </summary>
/// <param name="Name">Name of the macro</param>
/// <param name="Value">String to be parsed for the macro's value</param>
public void AddDefinition ( string Name , string Value )
{
List < Token > Tokens = new List < Token > ( ) ;
TokenReader Reader = new TokenReader ( Value ) ;
while ( Reader . MoveNext ( ) )
{
Tokens . Add ( Reader . Current ) ;
}
PreprocessorMacro Macro = new PreprocessorMacro ( Identifier . FindOrAdd ( Name ) , null , Tokens ) ;
State . DefineMacro ( Macro ) ;
}
/// <summary>
/// Defines a macro
/// </summary>
/// <param name="Macro">The macro definition</param>
public void AddDefinition ( PreprocessorMacro Macro )
{
State . DefineMacro ( Macro ) ;
}
/// <summary>
/// Adds an include path to the preprocessor
/// </summary>
/// <param name="Directory">The include path</param>
public void AddIncludePath ( DirectoryItem Directory )
{
if ( ! IncludeDirectories . Contains ( Directory ) )
{
IncludeDirectories . Add ( Directory ) ;
}
}
/// <summary>
/// Adds an include path to the preprocessor
/// </summary>
/// <param name="Location">The include path</param>
public void AddIncludePath ( DirectoryReference Location )
{
DirectoryItem Directory = DirectoryItem . GetItemByDirectoryReference ( Location ) ;
if ( ! Directory . Exists )
{
throw new FileNotFoundException ( "Unable to find " + Location . FullName ) ;
}
AddIncludePath ( Directory ) ;
}
/// <summary>
/// Adds an include path to the preprocessor
/// </summary>
/// <param name="DirectoryName">The include path</param>
public void AddIncludePath ( string DirectoryName )
{
AddIncludePath ( new DirectoryReference ( DirectoryName ) ) ;
}
/// <summary>
/// Adds a framework path to the preprocessor
/// </summary>
/// <param name="Directory">The framework path</param>
public void AddFrameworkPath ( DirectoryItem Directory )
{
if ( ! FrameworkDirectories . Contains ( Directory ) )
{
FrameworkDirectories . Add ( Directory ) ;
}
}
/// <summary>
/// Adds a framework path to the preprocessor
/// </summary>
/// <param name="Location">The framework path</param>
public void AddFrameworkPath ( DirectoryReference Location )
{
DirectoryItem Directory = DirectoryItem . GetItemByDirectoryReference ( Location ) ;
if ( ! Directory . Exists )
{
throw new FileNotFoundException ( "Unable to find " + Location . FullName ) ;
}
AddFrameworkPath ( Directory ) ;
}
/// <summary>
/// Adds a framework path to the preprocessor
/// </summary>
/// <param name="DirectoryName">The framework path</param>
public void AddFrameworkPath ( string DirectoryName )
{
AddFrameworkPath ( new DirectoryReference ( DirectoryName ) ) ;
}
/// <summary>
/// Try to resolve an quoted include against the list of include directories. Uses search order described by https://msdn.microsoft.com/en-us/library/36k2cdd4.aspx.
/// </summary>
/// <param name="Context">The current preprocessor context</param>
/// <param name="IncludePath">The path appearing in an #include directive</param>
/// <param name="Type">Specifies rules for how to resolve the include path (normal/system)</param>
/// <param name="File">If found, receives the resolved file</param>
/// <returns>True if the The resolved file</returns>
public bool TryResolveIncludePath ( PreprocessorContext Context , string IncludePath , IncludePathType Type , [ NotNullWhen ( true ) ] out FileItem ? File )
{
// From MSDN (https://msdn.microsoft.com/en-us/library/36k2cdd4.aspx?f=255&MSPPError=-2147217396)
//
// The preprocessor searches for include files in this order:
//
// Quoted form:
// 1) In the same directory as the file that contains the #include statement.
// 2) In the directories of the currently opened include files, in the reverse order in which they were opened.
// The search begins in the directory of the parent include file and continues upward through the directories of any grandparent include files.
// 3) Along the path that's specified by each /I compiler option.
// 4) Along the paths that are specified by the INCLUDE environment variable.
//
// Angle-bracket form:
// 1) Along the path that's specified by each /I compiler option.
// 2) Along the paths that are specified by the INCLUDE environment variable.
// If it's an absolute path, return it immediately
if ( Path . IsPathRooted ( IncludePath ) )
{
FileItem FileItem = FileItem . GetItemByPath ( IncludePath ) ;
if ( FileItem . Exists )
{
File = FileItem ;
return true ;
}
else
{
File = null ;
return false ;
}
}
// Split the path into fragments
string [ ] Fragments = IncludePath . Split ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ) ;
// Try to match the include path against any of the included directories
if ( Type = = IncludePathType . Normal )
{
for ( PreprocessorContext ? OuterContext = Context ; OuterContext ! = null ; OuterContext = OuterContext . Outer )
{
PreprocessorFileContext ? OuterFileContext = OuterContext as PreprocessorFileContext ;
if ( OuterFileContext ! = null )
{
FileItem ? ResolvedFile ;
if ( TryResolveRelativeIncludePath ( OuterFileContext . Directory , Fragments , out ResolvedFile ) )
{
File = ResolvedFile ;
return true ;
}
}
}
}
// Try to match the include path against any of the system directories
foreach ( DirectoryItem BaseDirectory in IncludeDirectories )
{
FileItem ? ResolvedFile ;
if ( TryResolveRelativeIncludePath ( BaseDirectory , Fragments , out ResolvedFile ) )
{
File = ResolvedFile ;
return true ;
}
}
// Try to match the include path against any of the MacOS framework Header paths
if ( Fragments . Length > 1 )
{
foreach ( DirectoryItem BaseDirectory in FrameworkDirectories )
{
if ( BaseDirectory . TryGetDirectory ( $"{Fragments[0]}.framework" , out DirectoryItem ? FrameworkBaseDirectory ) & &
FrameworkBaseDirectory . TryGetDirectory ( "Headers" , out DirectoryItem ? HeaderDirectory ) & &
TryResolveRelativeIncludePath ( HeaderDirectory , Fragments . Skip ( 1 ) . ToArray ( ) , out FileItem ? ResolvedFile ) )
{
File = ResolvedFile ;
return true ;
}
}
}
// Failed to find the file
File = null ;
return false ;
}
/// <summary>
/// Try to resolve an quoted include against the list of include directories. Uses search order described by https://msdn.microsoft.com/en-us/library/36k2cdd4.aspx.
/// </summary>
/// <param name="BaseDirectory">The base directory to search from</param>
/// <param name="Fragments">Fragments of the relative path to follow</param>
/// <param name="File">The file that was found, if successful</param>
/// <returns>True if the The resolved file</returns>
public bool TryResolveRelativeIncludePath ( DirectoryItem BaseDirectory , string [ ] Fragments , [ NotNullWhen ( true ) ] out FileItem ? File )
{
DirectoryItem ? Directory = BaseDirectory ;
for ( int Idx = 0 ; Idx < Fragments . Length - 1 ; Idx + + )
{
if ( ! Directory . TryGetDirectory ( Fragments [ Idx ] , out Directory ) )
{
File = null ;
return false ;
}
}
return Directory . TryGetFile ( Fragments [ Fragments . Length - 1 ] , out File ) ;
}
/// <summary>
/// Parses a file recursively
/// </summary>
/// <param name="File">File to parse</param>
/// <param name="Fragments">Lists of fragments that are parsed</param>
/// <param name="OuterContext">Outer context information, for error messages</param>
/// <param name="SourceFileCache">Cache for source files</param>
/// <param name="Logger">Logger for output</param>
/// <param name="bShowIncludes">Show all the included files, in order</param>
/// <param name="bIgnoreMissingIncludes">Suppress exceptions if an include path can not be resolved</param>
public void ParseFile ( FileItem File , List < SourceFileFragment > Fragments , PreprocessorContext ? OuterContext , SourceFileMetadataCache SourceFileCache , ILogger Logger , bool bShowIncludes = false , bool bIgnoreMissingIncludes = false )
{
// If the file has already been included and had a #pragma once directive, don't include it again
if ( PragmaOnceFiles . Contains ( File ) )
{
return ;
}
if ( ! ProcessedFiles . Contains ( File ) )
{
ProcessedFiles . Add ( File ) ;
}
// Output a trace of the included files
if ( bShowIncludes )
{
Logger . LogInformation ( "Note: including file: {FileLocation}" , File . Location ) ;
}
// If the file had a header guard, and the macro is still defined, don't include it again
SourceFile SourceFile = SourceFileCache . GetSourceFile ( File ) ;
if ( SourceFile . HeaderGuardMacro ! = null & & State . IsMacroDefined ( SourceFile . HeaderGuardMacro ) )
{
return ;
}
// Create a context for this file
PreprocessorFileContext Context = new PreprocessorFileContext ( SourceFile , OuterContext ) ;
// Parse the markup for this file
while ( Context . MarkupIdx < SourceFile . Markup . Length )
{
SourceFileMarkup Markup = SourceFile . Markup [ Context . MarkupIdx ] ;
if ( Markup . Type = = SourceFileMarkupType . Include )
{
if ( State . IsCurrentBranchActive ( ) )
{
// Parse the directive
FileItem ? IncludedFile = ParseIncludeDirective ( Markup , Context , bIgnoreMissingIncludes ) ;
// Parse the included file
if ( IncludedFile ! = null )
{
ParseFile ( IncludedFile , Fragments , Context , SourceFileCache , Logger , bShowIncludes , bIgnoreMissingIncludes ) ;
}
}
Context . MarkupIdx + + ;
}
else
{
// Get the next fragment
SourceFileFragment Fragment = SourceFile . Fragments [ Context . FragmentIdx ] ;
Debug . Assert ( Fragment . MarkupMin = = Context . MarkupIdx ) ;
// Parse this fragment
ParseFragment ( SourceFile , Fragment , Context ) ;
// Add this fragment to the list
Fragments . Add ( Fragment ) ;
Context . FragmentIdx + + ;
}
}
}
/// <summary>
/// Parse an include directive and resolve the file it references
/// </summary>
/// <param name="Markup">Markup for the include directive</param>
/// <param name="Context">Current preprocessor context</param>
/// <param name="bIgnoreMissingIncludes">Suppress exceptions if an include path can not be resolved</param>
/// <returns>Included file</returns>
FileItem ? ParseIncludeDirective ( SourceFileMarkup Markup , PreprocessorFileContext Context , bool bIgnoreMissingIncludes = false )
{
// Expand macros in the given tokens
List < Token > ExpandedTokens = new List < Token > ( ) ;
ExpandMacros ( Markup . Tokens ! , ExpandedTokens , false , Context ) ;
// Convert the string to a single token
string IncludeToken = Token . Format ( ExpandedTokens ) ;
// Expand any macros in them and resolve it
IncludePathType Type ;
if ( IncludeToken . Length > = 2 & & IncludeToken [ 0 ] = = '\"' & & IncludeToken [ IncludeToken . Length - 1 ] = = '\"' )
{
Type = IncludePathType . Normal ;
}
else if ( IncludeToken . Length > = 2 & & IncludeToken [ 0 ] = = '<' & & IncludeToken [ IncludeToken . Length - 1 ] = = '>' )
{
Type = IncludePathType . System ;
}
else
{
throw new PreprocessorException ( Context , "Couldn't resolve include '{0}'" , IncludeToken ) ;
}
// Get the include path
string IncludePath = IncludeToken . Substring ( 1 , IncludeToken . Length - 2 ) ;
// Resolve the included file
FileItem ? IncludedFile ;
if ( ! TryResolveIncludePath ( Context , IncludePath , Type , out IncludedFile ) )
{
if ( bIgnoreMissingIncludes )
{
Log . TraceWarningOnce ( "Couldn't resolve include '{0}' ({1})" , IncludePath , Context . SourceFile . Location ) ;
}
else
{
throw new PreprocessorException ( Context , "Couldn't resolve include '{0}' ({1})" , IncludePath , Context . SourceFile . Location ) ;
}
}
return IncludedFile ;
}
/// <summary>
/// Parse a source file fragment, using cached transforms if possible
/// </summary>
/// <param name="SourceFile">The source file being parsed</param>
/// <param name="Fragment">Fragment to parse</param>
/// <param name="Context">Current preprocessor context</param>
void ParseFragment ( SourceFile SourceFile , SourceFileFragment Fragment , PreprocessorFileContext Context )
{
// Check if there's a valid transform that matches the current state
int TransformIdx = 0 ;
for ( ; ; )
{
PreprocessorTransform [ ] Transforms ;
lock ( Fragment )
{
Transforms = Fragment . Transforms ;
if ( TransformIdx = = Transforms . Length )
{
// Attach a new transform to the current state
PreprocessorTransform Transform = State . BeginCapture ( ) ;
for ( ; Context . MarkupIdx < Fragment . MarkupMax ; Context . MarkupIdx + + )
{
SourceFileMarkup Markup = SourceFile . Markup [ Context . MarkupIdx ] ;
ParseMarkup ( Markup . Type , Markup . Tokens ! , Context ) ;
}
Transform = State . EndCapture ( ) ! ;
// Add it to the fragment for future fragments
PreprocessorTransform [ ] NewTransforms = new PreprocessorTransform [ Fragment . Transforms . Length + 1 ] ;
for ( int Idx = 0 ; Idx < Transforms . Length ; Idx + + )
{
NewTransforms [ Idx ] = Transforms [ Idx ] ;
}
NewTransforms [ Transforms . Length ] = Transform ;
// Assign it to the fragment
Fragment . Transforms = NewTransforms ;
return ;
}
}
for ( ; TransformIdx < Transforms . Length ; TransformIdx + + )
{
PreprocessorTransform Transform = Transforms [ TransformIdx ] ;
if ( State . TryToApply ( Transform ) )
{
// Update the pragma once state
if ( Transform . bHasPragmaOnce )
{
PragmaOnceFiles . Add ( SourceFile . File ) ;
}
// Move to the end of the fragment
Context . MarkupIdx = Fragment . MarkupMax ;
return ;
}
}
}
}
/// <summary>
/// Validate and add a macro using the given parameter and token list
/// </summary>
/// <param name="Context">The current preprocessor context</param>
/// <param name="Name">Name of the macro</param>
/// <param name="Parameters">Parameter list for the macro</param>
/// <param name="Tokens">List of tokens</param>
void AddMacro ( PreprocessorContext Context , Identifier Name , List < Identifier > ? Parameters , List < Token > Tokens )
{
if ( Tokens . Count = = 0 )
{
Tokens . Add ( new Token ( TokenType . Placemarker , TokenFlags . None ) ) ;
}
else
{
if ( Tokens [ 0 ] . HasLeadingSpace )
{
Tokens [ 0 ] = Tokens [ 0 ] . RemoveFlags ( TokenFlags . HasLeadingSpace ) ;
}
if ( Tokens [ 0 ] . Type = = TokenType . HashHash | | Tokens [ Tokens . Count - 1 ] . Type = = TokenType . HashHash )
{
throw new PreprocessorException ( Context , "Invalid use of concatenation at start or end of token sequence" ) ;
}
if ( Parameters = = null | | Parameters . Count = = 0 | | Parameters [ Parameters . Count - 1 ] ! = Identifiers . __VA_ARGS__ )
{
if ( Tokens . Any ( x = > x . Identifier = = Identifiers . __VA_ARGS__ ) )
{
throw new PreprocessorException ( Context , "Invalid reference to {0}" , Identifiers . __VA_ARGS__ ) ;
}
}
}
State . DefineMacro ( new PreprocessorMacro ( Name , Parameters , Tokens ) ) ;
}
/// <summary>
/// Set a predefined macro to a given value
/// </summary>
/// <param name="Name">Name of the macro</param>
/// <param name="Type">Type of the macro token</param>
/// <param name="Value">Value of the macro</param>
/// <returns>The created macro</returns>
void AddLiteralMacro ( string Name , TokenType Type , string Value )
{
Token Token = new Token ( Type , TokenFlags . None , Value ) ;
PreprocessorMacro Macro = new PreprocessorMacro ( Identifier . FindOrAdd ( Name ) , null , new List < Token > { Token } ) ;
State . DefineMacro ( Macro ) ;
}
/// <summary>
/// Parse a marked up directive from a file
/// </summary>
/// <param name="Type">The markup type</param>
/// <param name="Tokens">Tokens for the directive</param>
/// <param name="Context">The context that this markup is being parsed in</param>
public void ParseMarkup ( SourceFileMarkupType Type , List < Token > Tokens , PreprocessorContext Context )
{
switch ( Type )
{
case SourceFileMarkupType . Include :
throw new PreprocessorException ( Context , "Include directives should be handled by the caller." ) ;
case SourceFileMarkupType . Define :
ParseDefineDirective ( Tokens , Context ) ;
break ;
case SourceFileMarkupType . Undef :
ParseUndefDirective ( Tokens , Context ) ;
break ;
case SourceFileMarkupType . If :
ParseIfDirective ( Tokens , Context ) ;
break ;
case SourceFileMarkupType . Ifdef :
ParseIfdefDirective ( Tokens , Context ) ;
break ;
case SourceFileMarkupType . Ifndef :
ParseIfndefDirective ( Tokens , Context ) ;
break ;
case SourceFileMarkupType . Elif :
ParseElifDirective ( Tokens , Context ) ;
break ;
case SourceFileMarkupType . Else :
ParseElseDirective ( Tokens , Context ) ;
break ;
case SourceFileMarkupType . Endif :
ParseEndifDirective ( Tokens , Context ) ;
break ;
case SourceFileMarkupType . Pragma :
ParsePragmaDirective ( Tokens , Context ) ;
break ;
}
}
/// <summary>
/// Read a macro definition
/// </summary>
/// <param name="Tokens">List of tokens in the directive</param>
/// <param name="Context">The context that this directive is being parsed in</param>
public void ParseDefineDirective ( List < Token > Tokens , PreprocessorContext Context )
{
if ( State . IsCurrentBranchActive ( ) )
{
// Check there's a name token
if ( Tokens . Count < 1 | | Tokens [ 0 ] . Type ! = TokenType . Identifier | | Tokens [ 0 ] . Identifier = = Identifiers . Defined )
{
throw new PreprocessorException ( Context , "Invalid macro name" ) ;
}
// Read the macro name
Identifier Name = Tokens [ 0 ] . Identifier ! ;
int TokenIdx = 1 ;
// Read the macro parameter list, if there is one
List < Identifier > ? Parameters = null ;
if ( TokenIdx < Tokens . Count & & ! Tokens [ TokenIdx ] . HasLeadingSpace & & Tokens [ TokenIdx ] . Type = = TokenType . LeftParen )
{
Parameters = new List < Identifier > ( ) ;
if ( + + TokenIdx = = Tokens . Count )
{
throw new PreprocessorException ( Context , "Unexpected end of macro parameter list" ) ;
}
if ( Tokens [ TokenIdx ] . Type ! = TokenType . RightParen )
{
for ( ; ; TokenIdx + + )
{
// Check there's enough tokens left for a parameter name, plus ',' or ')'
if ( TokenIdx + 2 > Tokens . Count )
{
throw new PreprocessorException ( Context , "Unexpected end of macro parameter list" ) ;
}
// Check it's a valid name, and add it to the list
Token NameToken = Tokens [ TokenIdx + + ] ;
if ( NameToken . Type = = TokenType . Ellipsis )
{
if ( Tokens [ TokenIdx ] . Type ! = TokenType . RightParen )
{
throw new PreprocessorException ( Context , "Variadic macro arguments must be last in list" ) ;
}
else
{
NameToken = new Token ( Identifiers . __VA_ARGS__ , NameToken . Flags & TokenFlags . HasLeadingSpace ) ;
}
}
else
{
if ( NameToken . Type ! = TokenType . Identifier | | NameToken . Identifier = = Identifiers . __VA_ARGS__ )
{
throw new PreprocessorException ( Context , "Invalid preprocessor token: {0}" , NameToken ) ;
}
if ( Parameters . Contains ( NameToken . Identifier ! ) )
{
throw new PreprocessorException ( Context , "'{0}' has already been used as an argument name" , NameToken ) ;
}
}
Parameters . Add ( NameToken . Identifier ! ) ;
// Read the separator
Token SeparatorToken = Tokens [ TokenIdx ] ;
if ( SeparatorToken . Type = = TokenType . RightParen )
{
break ;
}
if ( SeparatorToken . Type ! = TokenType . Comma )
{
throw new PreprocessorException ( Context , "Expected ',' or ')'" ) ;
}
}
}
TokenIdx + + ;
}
// Read the macro tokens
AddMacro ( Context , Name , Parameters , Tokens . Skip ( TokenIdx ) . ToList ( ) ) ;
}
}
/// <summary>
/// Parse an #undef directive
/// </summary>
/// <param name="Tokens">List of tokens in the directive</param>
/// <param name="Context">The context that this directive is being parsed in</param>
public void ParseUndefDirective ( List < Token > Tokens , PreprocessorContext Context )
{
if ( State . IsCurrentBranchActive ( ) )
{
// Check there's a name token
if ( Tokens . Count ! = 1 )
{
throw new PreprocessorException ( Context , "Expected a single token after #undef" ) ;
}
if ( Tokens [ 0 ] . Type ! = TokenType . Identifier )
{
throw new PreprocessorException ( Context , "Invalid macro name '{0}'" , Tokens [ 0 ] ) ;
}
// Remove the macro from the list of definitions
State . UndefMacro ( Tokens [ 0 ] . Identifier ! ) ;
}
}
/// <summary>
/// Parse an #if directive
/// </summary>
/// <param name="Tokens">List of tokens in the directive</param>
/// <param name="Context">The context that this directive is being parsed in</param>
public void ParseIfDirective ( List < Token > Tokens , PreprocessorContext Context )
{
PreprocessorBranch Branch = PreprocessorBranch . HasIfDirective ;
if ( State . IsCurrentBranchActive ( ) )
{
// Read a line into the buffer and expand the macros in it
List < Token > ExpandedTokens = new List < Token > ( ) ;
ExpandMacros ( Tokens , ExpandedTokens , true , Context ) ;
// Evaluate the condition
long Result = PreprocessorExpression . Evaluate ( Context , ExpandedTokens ) ;
if ( Result ! = 0 )
{
Branch | = PreprocessorBranch . Active | PreprocessorBranch . Taken ;
}
}
State . PushBranch ( Branch ) ;
}
/// <summary>
/// Parse an #ifdef directive
/// </summary>
/// <param name="Tokens">List of tokens in the directive</param>
/// <param name="Context">The context that this directive is being parsed in</param>
public void ParseIfdefDirective ( List < Token > Tokens , PreprocessorContext Context )
{
PreprocessorBranch Branch = PreprocessorBranch . HasIfdefDirective ;
if ( State . IsCurrentBranchActive ( ) )
{
// Make sure there's only one token
if ( Tokens . Count ! = 1 | | Tokens [ 0 ] . Type ! = TokenType . Identifier )
{
throw new PreprocessorException ( Context , "Missing or invalid macro name for #ifdef directive" ) ;
}
// Check if the macro is defined
if ( State . IsMacroDefined ( Tokens [ 0 ] . Identifier ! ) )
{
Branch | = PreprocessorBranch . Active | PreprocessorBranch . Taken ;
}
}
State . PushBranch ( Branch ) ;
}
/// <summary>
/// Parse an #ifndef directive
/// </summary>
/// <param name="Tokens">List of tokens for this directive</param>
/// <param name="Context">The context that this directive is being parsed in</param>
public void ParseIfndefDirective ( List < Token > Tokens , PreprocessorContext Context )
{
PreprocessorBranch Branch = PreprocessorBranch . HasIfndefDirective ;
if ( State . IsCurrentBranchActive ( ) )
{
// Make sure there's only one token
if ( Tokens . Count ! = 1 | | Tokens [ 0 ] . Type ! = TokenType . Identifier )
{
throw new PreprocessorException ( Context , "Missing or invalid macro name for #ifndef directive" ) ;
}
// Check if the macro is defined
if ( ! State . IsMacroDefined ( Tokens [ 0 ] . Identifier ! ) )
{
Branch | = PreprocessorBranch . Active | PreprocessorBranch . Taken ;
}
}
State . PushBranch ( Branch ) ;
}
/// <summary>
/// Parse an #elif directive
/// </summary>
/// <param name="Tokens">List of tokens for this directive</param>
/// <param name="Context">The context that this directive is being parsed in</param>
public void ParseElifDirective ( List < Token > Tokens , PreprocessorContext Context )
{
// Check we're in a branch, and haven't already read an #else directive
PreprocessorBranch Branch ;
if ( ! State . TryPopBranch ( out Branch ) )
{
throw new PreprocessorException ( Context , "#elif directive outside conditional block" ) ;
}
if ( Branch . HasFlag ( PreprocessorBranch . Complete ) )
{
throw new PreprocessorException ( Context , "#elif directive cannot appear after #else" ) ;
}
// Pop the current branch state at this depth, so we can test against whether the parent state is enabled
Branch = ( Branch | PreprocessorBranch . HasElifDirective ) & ~ PreprocessorBranch . Active ;
if ( State . IsCurrentBranchActive ( ) )
{
// Read a line into the buffer and expand the macros in it
List < Token > ExpandedTokens = new List < Token > ( ) ;
ExpandMacros ( Tokens , ExpandedTokens , true , Context ) ;
// Check we're at the end of a conditional block
if ( ! Branch . HasFlag ( PreprocessorBranch . Taken ) )
{
long Result = PreprocessorExpression . Evaluate ( Context , ExpandedTokens ) ;
if ( Result ! = 0 )
{
Branch | = PreprocessorBranch . Active | PreprocessorBranch . Taken ;
}
}
}
State . PushBranch ( Branch ) ;
}
/// <summary>
/// Parse an #else directive
/// </summary>
/// <param name="Tokens">List of tokens in the directive</param>
/// <param name="Context">The context that this directive is being parsed in</param>
public void ParseElseDirective ( List < Token > Tokens , PreprocessorContext Context )
{
// Make sure there's nothing else on the line
if ( Tokens . Count > 0 )
{
throw new PreprocessorException ( Context , "Garbage after #else directive" ) ;
}
// Check we're in a branch, and haven't already read an #else directive
PreprocessorBranch Branch ;
if ( ! State . TryPopBranch ( out Branch ) )
{
throw new PreprocessorException ( Context , "#else directive without matching #if directive" ) ;
}
if ( ( Branch & PreprocessorBranch . Complete ) ! = 0 )
{
throw new PreprocessorException ( Context , "Only one #else directive can appear in a conditional block" ) ;
}
// Check whether to take this branch, but only allow activating if the parent state is active.
Branch & = ~ PreprocessorBranch . Active ;
if ( State . IsCurrentBranchActive ( ) & & ! Branch . HasFlag ( PreprocessorBranch . Taken ) )
{
Branch | = PreprocessorBranch . Active | PreprocessorBranch . Taken ;
}
State . PushBranch ( Branch | PreprocessorBranch . Complete ) ;
}
/// <summary>
/// Parse an #endif directive
/// </summary>
/// <param name="Tokens">List of tokens in the directive</param>
/// <param name="Context">The context that this directive is being parsed in</param>
public void ParseEndifDirective ( List < Token > Tokens , PreprocessorContext Context )
{
// Pop the branch off the stack
PreprocessorBranch Branch ;
if ( ! State . TryPopBranch ( out Branch ) )
{
throw new PreprocessorException ( Context , "#endif directive without matching #if/#ifdef/#ifndef directive" ) ;
}
}
/// <summary>
/// Parse a #pragma directive
/// </summary>
/// <param name="Tokens">List of tokens in the directive</param>
/// <param name="Context">The context that this directive is being parsed in</param>
public void ParsePragmaDirective ( List < Token > Tokens , PreprocessorContext Context )
{
if ( State . IsCurrentBranchActive ( ) )
{
if ( Tokens . Count = = 1 & & Tokens [ 0 ] . Identifier = = Identifiers . Once )
{
SourceFile SourceFile = GetCurrentSourceFile ( Context ) ! ;
PragmaOnceFiles . Add ( SourceFile . File ) ;
State . MarkPragmaOnce ( ) ;
}
}
}
/// <summary>
/// Expand macros in the given sequence.
/// </summary>
/// <param name="InputTokens">Sequence of input tokens</param>
/// <param name="OutputTokens">List to receive the expanded tokens</param>
/// <param name="bIsConditional">Whether a conditional expression is being evaluated (and 'defined' expressions are valid)</param>
/// <param name="Context">The context that this directive is being parsed in</param>
public void ExpandMacros ( IEnumerable < Token > InputTokens , List < Token > OutputTokens , bool bIsConditional , PreprocessorContext Context )
{
List < PreprocessorMacro > IgnoreMacros = new List < PreprocessorMacro > ( ) ;
ExpandMacrosRecursively ( InputTokens , OutputTokens , bIsConditional , IgnoreMacros , Context ) ;
}
/// <summary>
/// Expand macros in the given sequence, ignoring previously expanded macro names from a list.
/// </summary>
/// <param name="InputTokens">Sequence of input tokens</param>
/// <param name="OutputTokens">List to receive the expanded tokens</param>
/// <param name="bIsConditional">Whether a conditional expression is being evaluated (and 'defined' expressions are valid)</param>
/// <param name="IgnoreMacros">List of macros to ignore</param>
/// <param name="Context">The context that this directive is being parsed in</param>
void ExpandMacrosRecursively ( IEnumerable < Token > InputTokens , List < Token > OutputTokens , bool bIsConditional , List < PreprocessorMacro > IgnoreMacros , PreprocessorContext Context )
{
IEnumerator < Token > InputEnumerator = InputTokens . GetEnumerator ( ) ;
if ( InputEnumerator . MoveNext ( ) )
{
for ( ; ; )
{
if ( ! ReadExpandedToken ( InputEnumerator , OutputTokens , bIsConditional , IgnoreMacros , Context ) )
{
break ;
}
}
}
}
/// <summary>
/// Merges an optional leading space flag into the given token (recycling the original token if possible).
/// </summary>
/// <param name="Token">The token to merge a leading space into</param>
/// <param name="bHasLeadingSpace">The leading space flag</param>
/// <returns>New token with the leading space flag set, or the existing token</returns>
Token MergeLeadingSpace ( Token Token , bool bHasLeadingSpace )
{
Token Result = Token ;
if ( bHasLeadingSpace & & ! Result . HasLeadingSpace )
{
Result = Result . AddFlags ( TokenFlags . HasLeadingSpace ) ;
}
return Result ;
}
/// <summary>
/// Read a token from an enumerator and substitute it if it's a macro or 'defined' expression (reading more tokens as necessary to complete the expression).
/// </summary>
/// <param name="InputEnumerator">The enumerator of input tokens</param>
/// <param name="OutputTokens">List to receive the expanded tokens</param>
/// <param name="bIsConditional">Whether a conditional expression is being evaluated (and 'defined' expressions are valid)</param>
/// <param name="IgnoreMacros">List of macros to ignore</param>
/// <param name="Context">The context that this directive is being parsed in</param>
/// <returns>Result from calling the enumerator's MoveNext() method</returns>
bool ReadExpandedToken ( IEnumerator < Token > InputEnumerator , List < Token > OutputTokens , bool bIsConditional , List < PreprocessorMacro > IgnoreMacros , PreprocessorContext Context )
{
// Capture the first token, then move to the next
OutputTokens . Add ( InputEnumerator . Current ) ;
bool bMoveNext = InputEnumerator . MoveNext ( ) ;
// If it's an identifier, try to resolve it as a macro
if ( OutputTokens [ OutputTokens . Count - 1 ] . Identifier = = Identifiers . Defined & & bIsConditional )
{
// Remove the 'defined' keyword
OutputTokens . RemoveAt ( OutputTokens . Count - 1 ) ;
// Make sure there's another token
if ( ! bMoveNext )
{
throw new PreprocessorException ( Context , "Invalid syntax for 'defined' expression" ) ;
}
// Check for the form 'defined identifier'
Token NameToken ;
if ( InputEnumerator . Current . Type = = TokenType . Identifier )
{
NameToken = InputEnumerator . Current ;
}
else
{
// Otherwise assume the form 'defined ( identifier )'
if ( InputEnumerator . Current . Type ! = TokenType . LeftParen | | ! InputEnumerator . MoveNext ( ) | | InputEnumerator . Current . Type ! = TokenType . Identifier )
{
throw new PreprocessorException ( Context , "Invalid syntax for 'defined' expression" ) ;
}
NameToken = InputEnumerator . Current ;
if ( ! InputEnumerator . MoveNext ( ) | | InputEnumerator . Current . Type ! = TokenType . RightParen )
{
throw new PreprocessorException ( Context , "Invalid syntax for 'defined' expression" ) ;
}
}
// Insert a token for whether it's defined or not
OutputTokens . Add ( new Token ( TokenType . Number , TokenFlags . None , State . IsMacroDefined ( NameToken . Identifier ! ) ? OneLiteral : ZeroLiteral ) ) ;
bMoveNext = InputEnumerator . MoveNext ( ) ;
}
else
{
// Repeatedly try to expand the last token into the list
while ( OutputTokens [ OutputTokens . Count - 1 ] . Type = = TokenType . Identifier & & ! OutputTokens [ OutputTokens . Count - 1 ] . Flags . HasFlag ( TokenFlags . DisableExpansion ) )
{
// Try to get a macro for the current token
PreprocessorMacro ? Macro ;
if ( ! State . TryGetMacro ( OutputTokens [ OutputTokens . Count - 1 ] . Identifier ! , out Macro ) | | IgnoreMacros . Contains ( Macro ) )
{
break ;
}
if ( Macro . IsFunctionMacro & & ( ! bMoveNext | | InputEnumerator . Current . Type ! = TokenType . LeftParen ) )
{
break ;
}
// Remove the macro name from the output list
bool bHasLeadingSpace = OutputTokens [ OutputTokens . Count - 1 ] . HasLeadingSpace ;
OutputTokens . RemoveAt ( OutputTokens . Count - 1 ) ;
// Save the initial number of tokens in the list, so we can tell if it expanded
int NumTokens = OutputTokens . Count ;
// If it's an object macro, expand it immediately into the output buffer
if ( Macro . IsObjectMacro )
{
// Expand the macro tokens into the output buffer
ExpandObjectMacro ( Macro , OutputTokens , bIsConditional , IgnoreMacros , Context ) ;
}
else
{
// Read balanced token for argument list
List < Token > ArgumentTokens = new List < Token > ( ) ;
bMoveNext = ReadBalancedToken ( InputEnumerator , ArgumentTokens , Context ) ;
// Expand the macro tokens into the output buffer
ExpandFunctionMacro ( Macro , ArgumentTokens , OutputTokens , bIsConditional , IgnoreMacros , Context ) ;
}
// If the macro expanded to nothing, quit
if ( OutputTokens . Count < = NumTokens )
{
break ;
}
// Make sure the space is propagated to the expanded macro
OutputTokens [ NumTokens ] = MergeLeadingSpace ( OutputTokens [ NumTokens ] , bHasLeadingSpace ) ;
// Mark any tokens matching the macro name as not to be expanded again. This can happen with recursive object macros, eg. #define DWORD ::DWORD
for ( int Idx = NumTokens ; Idx < OutputTokens . Count ; Idx + + )
{
if ( OutputTokens [ Idx ] . Type = = TokenType . Identifier & & OutputTokens [ Idx ] . Identifier = = Macro . Name )
{
OutputTokens [ Idx ] = OutputTokens [ Idx ] . AddFlags ( TokenFlags . DisableExpansion ) ;
}
}
}
}
return bMoveNext ;
}
/// <summary>
/// Gets a string for the __FILE__ macro
/// </summary>
/// <param name="Context">Context to scan to find the current file</param>
/// <returns>String representing the current context</returns>
string GetCurrentFileMacroValue ( PreprocessorContext Context )
{
SourceFile ? SourceFile = GetCurrentSourceFile ( Context ) ;
if ( SourceFile = = null )
{
return "<unknown>" ;
}
else
{
return SourceFile . Location . FullName ;
}
}
/// <summary>
/// Gets a string for the current file
/// </summary>
/// <param name="Context">Context to scan to find the current file</param>
/// <returns>Current source file being parsed</returns>
SourceFile ? GetCurrentSourceFile ( PreprocessorContext Context )
{
SourceFile ? SourceFile = null ;
for ( PreprocessorContext ? OuterContext = Context ; OuterContext ! = null ; OuterContext = OuterContext . Outer )
{
PreprocessorFileContext ? OuterFileContext = OuterContext as PreprocessorFileContext ;
if ( OuterFileContext ! = null )
{
SourceFile = OuterFileContext . SourceFile ;
break ;
}
}
return SourceFile ;
}
/// <summary>
/// Gets the current line number
/// </summary>
/// <param name="Context">Context to scan to find the current file</param>
/// <returns>Line number in the first file encountered</returns>
int GetCurrentLine ( PreprocessorContext Context )
{
for ( PreprocessorContext ? OuterContext = Context ; OuterContext ! = null ; OuterContext = OuterContext . Outer )
{
PreprocessorFileContext ? OuterFileContext = OuterContext as PreprocessorFileContext ;
if ( OuterFileContext ! = null )
{
return OuterFileContext . SourceFile . Markup [ OuterFileContext . MarkupIdx ] . LineNumber ;
}
}
return 0 ;
}
/// <summary>
/// Expand an object macro
/// </summary>
/// <param name="Macro">The functional macro</param>
/// <param name="OutputTokens">The list to receive the output tokens</param>
/// <param name="bIsConditional">Whether the macro is being expanded in a conditional context, allowing use of the 'defined' keyword</param>
/// <param name="IgnoreMacros">List of macros currently being expanded, which should be ignored for recursion</param>
/// <param name="Context">The context that this directive is being parsed in</param>
void ExpandObjectMacro ( PreprocessorMacro Macro , List < Token > OutputTokens , bool bIsConditional , List < PreprocessorMacro > IgnoreMacros , PreprocessorContext Context )
{
// Special handling for the __LINE__ directive, since we need an updated line number for the current token
if ( Macro . Name = = Identifiers . __FILE__ )
{
Token Token = new Token ( TokenType . String , TokenFlags . None , String . Format ( "\"{0}\"" , GetCurrentFileMacroValue ( Context ) . Replace ( "\\" , "\\\\" ) ) ) ;
OutputTokens . Add ( Token ) ;
}
else if ( Macro . Name = = Identifiers . __LINE__ )
{
Token Token = new Token ( TokenType . Number , TokenFlags . None , GetCurrentLine ( Context ) . ToString ( ) ) ;
OutputTokens . Add ( Token ) ;
}
else if ( Macro . Name = = Identifiers . __COUNTER__ )
{
Token Token = new Token ( TokenType . Number , TokenFlags . None , ( Counter + + ) . ToString ( ) ) ;
OutputTokens . Add ( Token ) ;
}
else
{
int OutputTokenCount = OutputTokens . Count ;
// Expand all the macros
IgnoreMacros . Add ( Macro ) ;
ExpandMacrosRecursively ( Macro . Tokens , OutputTokens , bIsConditional , IgnoreMacros , Context ) ;
IgnoreMacros . RemoveAt ( IgnoreMacros . Count - 1 ) ;
// Concatenate any adjacent tokens
for ( int Idx = OutputTokenCount + 1 ; Idx < OutputTokens . Count - 1 ; Idx + + )
{
if ( OutputTokens [ Idx ] . Type = = TokenType . HashHash )
{
OutputTokens [ Idx - 1 ] = Token . Concatenate ( OutputTokens [ Idx - 1 ] , OutputTokens [ Idx + 1 ] , Context ) ;
OutputTokens . RemoveRange ( Idx , 2 ) ;
Idx - - ;
}
}
}
}
/// <summary>
/// Expand a function macro
/// </summary>
/// <param name="Macro">The functional macro</param>
/// <param name="ArgumentListTokens">Identifiers for each argument token</param>
/// <param name="OutputTokens">The list to receive the output tokens</param>
/// <param name="bIsConditional">Whether the macro is being expanded in a conditional context, allowing use of the 'defined' keyword</param>
/// <param name="IgnoreMacros">List of macros currently being expanded, which should be ignored for recursion</param>
/// <param name="Context">The context that this macro is being expanded</param>
void ExpandFunctionMacro ( PreprocessorMacro Macro , List < Token > ArgumentListTokens , List < Token > OutputTokens , bool bIsConditional , List < PreprocessorMacro > IgnoreMacros , PreprocessorContext Context )
{
// Replace any newlines with spaces, and merge them with the following token
for ( int Idx = 0 ; Idx < ArgumentListTokens . Count ; Idx + + )
{
if ( ArgumentListTokens [ Idx ] . Type = = TokenType . Newline )
{
if ( Idx + 1 < ArgumentListTokens . Count )
{
ArgumentListTokens [ Idx + 1 ] = MergeLeadingSpace ( ArgumentListTokens [ Idx + 1 ] , true ) ;
}
ArgumentListTokens . RemoveAt ( Idx - - ) ;
}
}
// Split the arguments out into separate lists
List < List < Token > > Arguments = new List < List < Token > > ( ) ;
if ( ArgumentListTokens . Count > 2 )
{
for ( int Idx = 1 ; ; Idx + + )
{
if ( ! Macro . HasVariableArgumentList | | Arguments . Count < Macro . Parameters ! . Count )
{
Arguments . Add ( new List < Token > ( ) ) ;
}
List < Token > Argument = Arguments [ Arguments . Count - 1 ] ;
int InitialIdx = Idx ;
while ( Idx < ArgumentListTokens . Count - 1 & & ArgumentListTokens [ Idx ] . Type ! = TokenType . Comma )
{
if ( ! ReadBalancedToken ( ArgumentListTokens , ref Idx , Argument ) )
{
throw new PreprocessorException ( Context , "Invalid argument" ) ;
}
}
if ( Argument . Count > 0 & & Arguments [ Arguments . Count - 1 ] [ 0 ] . HasLeadingSpace )
{
Argument [ 0 ] = Argument [ 0 ] . RemoveFlags ( TokenFlags . HasLeadingSpace ) ;
}
bool bHasLeadingSpace = false ;
for ( int TokenIdx = 0 ; TokenIdx < Argument . Count ; TokenIdx + + )
{
if ( Argument [ TokenIdx ] . Text . Length = = 0 )
{
bHasLeadingSpace | = Argument [ TokenIdx ] . HasLeadingSpace ;
Argument . RemoveAt ( TokenIdx - - ) ;
}
else
{
Argument [ TokenIdx ] = MergeLeadingSpace ( Argument [ TokenIdx ] , bHasLeadingSpace ) ;
bHasLeadingSpace = false ;
}
}
if ( Argument . Count = = 0 )
{
Argument . Add ( new Token ( TokenType . Placemarker , TokenFlags . None ) ) ;
Argument . Add ( new Token ( TokenType . Placemarker , bHasLeadingSpace ? TokenFlags . HasLeadingSpace : TokenFlags . None ) ) ;
}
if ( Idx = = ArgumentListTokens . Count - 1 )
{
break ;
}
if ( ArgumentListTokens [ Idx ] . Type ! = TokenType . Comma )
{
throw new PreprocessorException ( Context , "Expected ',' between arguments" ) ;
}
if ( Macro . HasVariableArgumentList & & Arguments . Count = = Macro . Parameters ! . Count & & Idx < ArgumentListTokens . Count - 1 )
{
Arguments [ Arguments . Count - 1 ] . Add ( ArgumentListTokens [ Idx ] ) ;
}
}
}
// Add an empty variable argument if one was not specified
if ( Macro . HasVariableArgumentList & & Arguments . Count = = Macro . Parameters ! . Count - 1 )
{
Arguments . Add ( new List < Token > { new Token ( TokenType . Placemarker , TokenFlags . None ) } ) ;
}
// Validate the argument list
if ( Arguments . Count ! = Macro . Parameters ! . Count )
{
throw new PreprocessorException ( Context , "Incorrect number of arguments to macro" ) ;
}
// Expand each one of the arguments
List < List < Token > > ExpandedArguments = new List < List < Token > > ( ) ;
for ( int Idx = 0 ; Idx < Arguments . Count ; Idx + + )
{
List < Token > NewArguments = new List < Token > ( ) ;
ExpandMacrosRecursively ( Arguments [ Idx ] , NewArguments , bIsConditional , IgnoreMacros , Context ) ;
ExpandedArguments . Add ( NewArguments ) ;
}
// Substitute all the argument tokens
List < Token > ExpandedTokens = new List < Token > ( ) ;
for ( int Idx = 0 ; Idx < Macro . Tokens . Count ; Idx + + )
{
Token Token = Macro . Tokens [ Idx ] ;
if ( Token . Type = = TokenType . Hash & & Idx + 1 < Macro . Tokens . Count )
{
// Stringizing operator
int ParamIdx = Macro . FindParameterIndex ( Macro . Tokens [ + + Idx ] . Identifier ! ) ;
if ( ParamIdx = = - 1 )
{
throw new PreprocessorException ( Context , "{0} is not an argument name" , Macro . Tokens [ Idx ] . Text ) ;
}
ExpandedTokens . Add ( new Token ( TokenType . String , Token . Flags & TokenFlags . HasLeadingSpace , String . Format ( "\"{0}\"" , Token . Format ( Arguments [ ParamIdx ] ) . Replace ( "\\" , "\\\\" ) . Replace ( "\"" , "\\\"" ) ) ) ) ;
}
else if ( Macro . HasVariableArgumentList & & Idx + 2 < Macro . Tokens . Count & & Token . Type = = TokenType . Comma & & Macro . Tokens [ Idx + 1 ] . Type = = TokenType . HashHash & & Macro . Tokens [ Idx + 2 ] . Identifier = = Identifiers . __VA_ARGS__ )
{
// Special MSVC/GCC extension: ', ## __VA_ARGS__' removes the comma if __VA_ARGS__ is empty. MSVC seems to format the result with a forced space.
List < Token > ExpandedArgument = ExpandedArguments [ ExpandedArguments . Count - 1 ] ;
if ( ExpandedArgument . Any ( x = > x . Text . Length > 0 ) )
{
ExpandedTokens . Add ( Token ) ;
AppendTokensWithWhitespace ( ExpandedTokens , ExpandedArgument , false ) ;
Idx + = 2 ;
}
else
{
ExpandedTokens . Add ( new Token ( TokenType . Placemarker , Token . Flags & TokenFlags . HasLeadingSpace ) ) ;
ExpandedTokens . Add ( new Token ( TokenType . Placemarker , TokenFlags . HasLeadingSpace ) ) ;
Idx + = 2 ;
}
}
else if ( Token . Type = = TokenType . Identifier )
{
// Expand a parameter
int ParamIdx = Macro . FindParameterIndex ( Token . Identifier ! ) ;
if ( ParamIdx = = - 1 )
{
ExpandedTokens . Add ( Token ) ;
}
else if ( Idx > 0 & & Macro . Tokens [ Idx - 1 ] . Type = = TokenType . HashHash )
{
AppendTokensWithWhitespace ( ExpandedTokens , Arguments [ ParamIdx ] , Token . HasLeadingSpace ) ;
}
else if ( Idx + 1 < Macro . Tokens . Count & & Macro . Tokens [ Idx + 1 ] . Type = = TokenType . HashHash )
{
AppendTokensWithWhitespace ( ExpandedTokens , Arguments [ ParamIdx ] , Token . HasLeadingSpace ) ;
}
else
{
AppendTokensWithWhitespace ( ExpandedTokens , ExpandedArguments [ ParamIdx ] , Token . HasLeadingSpace ) ;
}
}
else
{
ExpandedTokens . Add ( Token ) ;
}
}
// Concatenate adjacent tokens
for ( int Idx = 1 ; Idx < ExpandedTokens . Count - 1 ; Idx + + )
{
if ( ExpandedTokens [ Idx ] . Type = = TokenType . HashHash )
{
Token ConcatenatedToken = Token . Concatenate ( ExpandedTokens [ Idx - 1 ] , ExpandedTokens [ Idx + 1 ] , Context ) ;
ExpandedTokens . RemoveRange ( Idx , 2 ) ;
ExpandedTokens [ - - Idx ] = ConcatenatedToken ;
}
}
// Finally, return the expansion of this
IgnoreMacros . Add ( Macro ) ;
ExpandMacrosRecursively ( ExpandedTokens , OutputTokens , bIsConditional , IgnoreMacros , Context ) ;
IgnoreMacros . RemoveAt ( IgnoreMacros . Count - 1 ) ;
}
/// <summary>
/// Appends a list of tokens to another list, setting the leading whitespace flag to the given value
/// </summary>
/// <param name="OutputTokens">List to receive the appended tokens</param>
/// <param name="InputTokens">List of tokens to append</param>
/// <param name="bHasLeadingSpace">Whether there is space before the first token</param>
void AppendTokensWithWhitespace ( List < Token > OutputTokens , List < Token > InputTokens , bool bHasLeadingSpace )
{
if ( InputTokens . Count > 0 )
{
OutputTokens . Add ( MergeLeadingSpace ( InputTokens [ 0 ] , bHasLeadingSpace ) ) ;
OutputTokens . AddRange ( InputTokens . Skip ( 1 ) ) ;
}
}
/// <summary>
/// Copies a single token from one list of tokens to another, or if it's an opening parenthesis, the entire subexpression until the closing parenthesis.
/// </summary>
/// <param name="InputTokens">The input token list</param>
/// <param name="InputIdx">First token index in the input token list. Set to the last uncopied token index on return.</param>
/// <param name="OutputTokens">List to recieve the output tokens</param>
/// <returns>True if a balanced expression was read, or false if the end of the list was encountered before finding a matching token</returns>
bool ReadBalancedToken ( List < Token > InputTokens , ref int InputIdx , List < Token > OutputTokens )
{
// Copy a single token to the output list
Token Token = InputTokens [ InputIdx + + ] ;
OutputTokens . Add ( Token ) ;
// If it was the start of a subexpression, copy until the closing parenthesis
if ( Token . Type = = TokenType . LeftParen )
{
// Copy the contents of the subexpression
for ( ; ; )
{
if ( InputIdx = = InputTokens . Count )
{
return false ;
}
if ( InputTokens [ InputIdx ] . Type = = TokenType . RightParen )
{
break ;
}
if ( ! ReadBalancedToken ( InputTokens , ref InputIdx , OutputTokens ) )
{
return false ;
}
}
// Copy the closing parenthesis
Token = InputTokens [ InputIdx + + ] ;
OutputTokens . Add ( Token ) ;
}
return true ;
}
/// <summary>
/// Copies a single token from one list of tokens to another, or if it's an opening parenthesis, the entire subexpression until the closing parenthesis.
/// </summary>
/// <param name="InputEnumerator">The input token list</param>
/// <param name="OutputTokens">List to recieve the output tokens</param>
/// <param name="Context">The context that the parser is in</param>
/// <returns>True if a balanced expression was read, or false if the end of the list was encountered before finding a matching token</returns>
bool ReadBalancedToken ( IEnumerator < Token > InputEnumerator , List < Token > OutputTokens , PreprocessorContext Context )
{
// Copy a single token to the output list
Token Token = InputEnumerator . Current ;
bool bMoveNext = InputEnumerator . MoveNext ( ) ;
OutputTokens . Add ( Token ) ;
// If it was the start of a subexpression, copy until the closing parenthesis
if ( Token . Type = = TokenType . LeftParen )
{
// Copy the contents of the subexpression
for ( ; ; )
{
if ( ! bMoveNext )
{
throw new PreprocessorException ( Context , "Unbalanced token sequence" ) ;
}
if ( InputEnumerator . Current . Type = = TokenType . RightParen )
{
OutputTokens . Add ( InputEnumerator . Current ) ;
bMoveNext = InputEnumerator . MoveNext ( ) ;
break ;
}
bMoveNext = ReadBalancedToken ( InputEnumerator , OutputTokens , Context ) ;
}
}
return bMoveNext ;
}
}
}