2022-08-10 16:03:37 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
2023-03-10 12:35:47 -05:00
using System.Diagnostics ;
using System.Diagnostics.CodeAnalysis ;
using System.IO ;
2022-08-10 16:03:37 +00:00
using System.Linq ;
using System.Text ;
2023-05-30 18:38:07 -04:00
using EpicGames.Core ;
using Microsoft.Extensions.Logging ;
2022-08-10 16:03:37 +00:00
using UnrealBuildBase ;
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>
2023-03-10 12:35:47 -05:00
public readonly PreprocessorContext ? Context ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Constructor
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
Context = context ;
2022-08-10 16:03:37 +00:00
}
}
/// <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>
2023-03-10 12:35:47 -05:00
readonly List < DirectoryItem > _includeDirectories = new ( ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
2023-03-10 12:35:47 -05:00
/// Framework paths to look in
/// </summary>
readonly List < DirectoryItem > _frameworkDirectories = new ( ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Set of all included files with the #pragma once directive
/// </summary>
2023-03-10 12:35:47 -05:00
readonly HashSet < FileItem > _pragmaOnceFiles = new ( ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Set of any files that has been processed
/// </summary>
2023-03-10 12:35:47 -05:00
readonly HashSet < FileItem > _processedFiles = new ( ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// The current state of the preprocessor
/// </summary>
2023-03-10 12:35:47 -05:00
readonly PreprocessorState _state = new ( ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Predefined token containing the constant "0"
/// </summary>
2023-03-10 12:35:47 -05:00
static readonly byte [ ] s_zeroLiteral = Encoding . UTF8 . GetBytes ( "0" ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Predefined token containing the constant "1"
/// </summary>
2023-03-10 12:35:47 -05:00
static readonly byte [ ] s_oneLiteral = Encoding . UTF8 . GetBytes ( "1" ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Value of the __COUNTER__ variable
/// </summary>
2023-03-10 12:35:47 -05:00
int _counter ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// List of files included by the preprocessor
/// </summary>
/// <returns>Enumerable of processed files</returns>
public IEnumerable < FileItem > GetProcessedFiles ( )
{
2023-03-10 12:35:47 -05:00
return _processedFiles . AsEnumerable ( ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Default constructor
/// </summary>
public Preprocessor ( )
{
2023-03-10 12:35:47 -05:00
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" ) + "\"" ) ;
2022-08-10 16:03:37 +00:00
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 ( )
{
2023-03-10 12:35:47 -05:00
return _state . IsCurrentBranchActive ( ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Defines a macro. May have an optional '=Value' suffix.
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="definition">Macro to define</param>
public void AddDefinition ( string definition )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
List < Token > tokens = TokenReader . Tokenize ( definition ) ;
if ( tokens . Count = = 0 )
2022-08-10 16:03:37 +00:00
{
throw new PreprocessorException ( null , "Missing macro name" ) ;
}
2023-03-10 12:35:47 -05:00
if ( tokens [ 0 ] . Type ! = TokenType . Identifier )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( null , "'{0}' is not a valid macro name" , tokens [ 0 ] . ToString ( ) ! ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
List < Token > valueTokens = new ( ) ;
if ( tokens . Count = = 1 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
valueTokens . Add ( new Token ( TokenType . Number , TokenFlags . None , s_oneLiteral ) ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
else if ( tokens [ 1 ] . Type ! = TokenType . Equals )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( null , "Unable to parse macro definition '{0}'" , definition ) ;
2022-08-10 16:03:37 +00:00
}
else
{
2023-03-10 12:35:47 -05:00
valueTokens . AddRange ( tokens . Skip ( 2 ) ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
PreprocessorMacro macro = new ( tokens [ 0 ] . Identifier ! , null , valueTokens ) ;
_state . DefineMacro ( macro ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Defines a macro
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
List < Token > tokens = new ( ) ;
2022-08-10 16:03:37 +00:00
2023-03-10 12:35:47 -05:00
TokenReader reader = new ( value ) ;
while ( reader . MoveNext ( ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
tokens . Add ( reader . Current ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
PreprocessorMacro macro = new ( Identifier . FindOrAdd ( name ) , null , tokens ) ;
_state . DefineMacro ( macro ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Defines a macro
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="macro">The macro definition</param>
public void AddDefinition ( PreprocessorMacro macro )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
_state . DefineMacro ( macro ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Adds an include path to the preprocessor
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="directory">The include path</param>
public void AddIncludePath ( DirectoryItem directory )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( ! _includeDirectories . Contains ( directory ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
_includeDirectories . Add ( directory ) ;
2022-08-10 16:03:37 +00:00
}
}
/// <summary>
/// Adds an include path to the preprocessor
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="location">The include path</param>
public void AddIncludePath ( DirectoryReference location )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
DirectoryItem directory = DirectoryItem . GetItemByDirectoryReference ( location ) ;
if ( ! directory . Exists )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new FileNotFoundException ( "Unable to find " + location . FullName ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
AddIncludePath ( directory ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Adds an include path to the preprocessor
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="directoryName">The include path</param>
public void AddIncludePath ( string directoryName )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
AddIncludePath ( new DirectoryReference ( directoryName ) ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Adds a framework path to the preprocessor
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="directory">The framework path</param>
public void AddFrameworkPath ( DirectoryItem directory )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( ! _frameworkDirectories . Contains ( directory ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
_frameworkDirectories . Add ( directory ) ;
2022-08-10 16:03:37 +00:00
}
}
/// <summary>
/// Adds a framework path to the preprocessor
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="location">The framework path</param>
public void AddFrameworkPath ( DirectoryReference location )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
DirectoryItem directory = DirectoryItem . GetItemByDirectoryReference ( location ) ;
if ( ! directory . Exists )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new FileNotFoundException ( "Unable to find " + location . FullName ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
AddFrameworkPath ( directory ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Adds a framework path to the preprocessor
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="directoryName">The framework path</param>
public void AddFrameworkPath ( string directoryName )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
AddFrameworkPath ( new DirectoryReference ( directoryName ) ) ;
2022-08-10 16:03:37 +00:00
}
/// <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>
2023-03-10 12:35:47 -05:00
/// <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>
2022-08-10 16:03:37 +00:00
/// <returns>True if the The resolved file</returns>
2023-03-10 12:35:47 -05:00
public bool TryResolveIncludePath ( PreprocessorContext context , string includePath , IncludePathType type , [ NotNullWhen ( true ) ] out FileItem ? file )
2022-08-10 16:03:37 +00:00
{
// 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
2023-03-10 12:35:47 -05:00
if ( Path . IsPathRooted ( includePath ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
FileItem fileItem = FileItem . GetItemByPath ( includePath ) ;
if ( fileItem . Exists )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
file = fileItem ;
2022-08-10 16:03:37 +00:00
return true ;
}
else
{
2023-03-10 12:35:47 -05:00
file = null ;
2022-08-10 16:03:37 +00:00
return false ;
}
}
// Split the path into fragments
2023-03-10 12:35:47 -05:00
string [ ] fragments = includePath . Split ( Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar ) ;
2022-08-10 16:03:37 +00:00
// Try to match the include path against any of the included directories
2023-03-10 12:35:47 -05:00
if ( type = = IncludePathType . Normal )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
for ( PreprocessorContext ? outerContext = context ; outerContext ! = null ; outerContext = outerContext . Outer )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( outerContext is PreprocessorFileContext outerFileContext )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( TryResolveRelativeIncludePath ( outerFileContext . Directory , fragments , out FileItem ? resolvedFile ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
file = resolvedFile ;
2022-08-10 16:03:37 +00:00
return true ;
}
}
}
}
// Try to match the include path against any of the system directories
2023-03-10 12:35:47 -05:00
foreach ( DirectoryItem baseDirectory in _includeDirectories )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( TryResolveRelativeIncludePath ( baseDirectory , fragments , out FileItem ? resolvedFile ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
file = resolvedFile ;
2022-08-10 16:03:37 +00:00
return true ;
}
}
// Try to match the include path against any of the MacOS framework Header paths
2023-03-10 12:35:47 -05:00
if ( fragments . Length > 1 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
foreach ( DirectoryItem baseDirectory in _frameworkDirectories )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( baseDirectory . TryGetDirectory ( $"{fragments[0]}.framework" , out DirectoryItem ? frameworkBaseDirectory ) & &
frameworkBaseDirectory . TryGetDirectory ( "Headers" , out DirectoryItem ? headerDirectory ) & &
TryResolveRelativeIncludePath ( headerDirectory , fragments . Skip ( 1 ) . ToArray ( ) , out FileItem ? resolvedFile ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
file = resolvedFile ;
2022-08-10 16:03:37 +00:00
return true ;
}
}
}
// Failed to find the file
2023-03-10 12:35:47 -05:00
file = null ;
2022-08-10 16:03:37 +00:00
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>
2023-03-10 12:35:47 -05:00
/// <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>
2022-08-10 16:03:37 +00:00
/// <returns>True if the The resolved file</returns>
2023-03-10 12:35:47 -05:00
public static bool TryResolveRelativeIncludePath ( DirectoryItem baseDirectory , string [ ] fragments , [ NotNullWhen ( true ) ] out FileItem ? file )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
DirectoryItem ? directory = baseDirectory ;
for ( int idx = 0 ; idx < fragments . Length - 1 ; idx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( ! directory . TryGetDirectory ( fragments [ idx ] , out directory ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
file = null ;
2022-08-10 16:03:37 +00:00
return false ;
}
}
2023-03-10 12:35:47 -05:00
return directory . TryGetFile ( fragments [ ^ 1 ] , out file ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Parses a file recursively
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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="showIncludes">Show all the included files, in order</param>
/// <param name="ignoreMissingIncludes">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 showIncludes = false , bool ignoreMissingIncludes = false )
2022-08-10 16:03:37 +00:00
{
// If the file has already been included and had a #pragma once directive, don't include it again
2023-03-10 12:35:47 -05:00
if ( _pragmaOnceFiles . Contains ( file ) )
2022-08-10 16:03:37 +00:00
{
return ;
}
2023-03-10 12:35:47 -05:00
_processedFiles . Add ( file ) ;
2022-08-10 16:03:37 +00:00
// Output a trace of the included files
2023-03-10 12:35:47 -05:00
if ( showIncludes )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
logger . LogInformation ( "Note: including file: {FileLocation}" , file . Location ) ;
2022-08-10 16:03:37 +00:00
}
// If the file had a header guard, and the macro is still defined, don't include it again
2023-03-10 12:35:47 -05:00
SourceFile sourceFile = sourceFileCache . GetSourceFile ( file ) ;
if ( sourceFile . HeaderGuardMacro ! = null & & _state . IsMacroDefined ( sourceFile . HeaderGuardMacro ) )
2022-08-10 16:03:37 +00:00
{
return ;
}
// Create a context for this file
2023-03-10 12:35:47 -05:00
PreprocessorFileContext context = new ( sourceFile , outerContext ) ;
2022-08-10 16:03:37 +00:00
// Parse the markup for this file
2023-03-10 12:35:47 -05:00
while ( context . MarkupIdx < sourceFile . Markup . Length )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
SourceFileMarkup markup = sourceFile . Markup [ context . MarkupIdx ] ;
if ( markup . Type = = SourceFileMarkupType . Include )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( _state . IsCurrentBranchActive ( ) )
2022-08-10 16:03:37 +00:00
{
// Parse the directive
2023-03-10 12:35:47 -05:00
FileItem ? includedFile = ParseIncludeDirective ( markup , context , ignoreMissingIncludes ) ;
2022-08-10 16:03:37 +00:00
// Parse the included file
2023-03-10 12:35:47 -05:00
if ( includedFile ! = null )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
ParseFile ( includedFile , fragments , context , sourceFileCache , logger , showIncludes , ignoreMissingIncludes ) ;
2022-08-10 16:03:37 +00:00
}
}
2023-03-10 12:35:47 -05:00
context . MarkupIdx + + ;
2022-08-10 16:03:37 +00:00
}
else
{
// Get the next fragment
2023-03-10 12:35:47 -05:00
SourceFileFragment fragment = sourceFile . Fragments [ context . FragmentIdx ] ;
Debug . Assert ( fragment . MarkupMin = = context . MarkupIdx ) ;
2022-08-10 16:03:37 +00:00
// Parse this fragment
2023-03-10 12:35:47 -05:00
ParseFragment ( sourceFile , fragment , context ) ;
2022-08-10 16:03:37 +00:00
// Add this fragment to the list
2023-03-10 12:35:47 -05:00
fragments . Add ( fragment ) ;
context . FragmentIdx + + ;
2022-08-10 16:03:37 +00:00
}
}
}
/// <summary>
/// Parse an include directive and resolve the file it references
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="markup">Markup for the include directive</param>
/// <param name="context">Current preprocessor context</param>
/// <param name="ignoreMissingIncludes">Suppress exceptions if an include path can not be resolved</param>
2022-08-10 16:03:37 +00:00
/// <returns>Included file</returns>
2023-03-10 12:35:47 -05:00
FileItem ? ParseIncludeDirective ( SourceFileMarkup markup , PreprocessorFileContext context , bool ignoreMissingIncludes = false )
2022-08-10 16:03:37 +00:00
{
// Expand macros in the given tokens
2023-03-10 12:35:47 -05:00
List < Token > expandedTokens = new ( ) ;
ExpandMacros ( markup . Tokens ! , expandedTokens , false , context ) ;
2022-08-10 16:03:37 +00:00
// Convert the string to a single token
2023-03-10 12:35:47 -05:00
string includeToken = Token . Format ( expandedTokens ) ;
2022-08-10 16:03:37 +00:00
// Expand any macros in them and resolve it
2023-03-10 12:35:47 -05:00
IncludePathType type ;
if ( includeToken . Length > = 2 & & includeToken [ 0 ] = = '\"' & & includeToken [ ^ 1 ] = = '\"' )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
type = IncludePathType . Normal ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
else if ( includeToken . Length > = 2 & & includeToken [ 0 ] = = '<' & & includeToken [ ^ 1 ] = = '>' )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
type = IncludePathType . System ;
2022-08-10 16:03:37 +00:00
}
else
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Couldn't resolve include '{0}'" , includeToken ) ;
2022-08-10 16:03:37 +00:00
}
// Get the include path
2023-03-10 12:35:47 -05:00
string includePath = includeToken [ 1. . ^ 1 ] ;
2022-08-10 16:03:37 +00:00
// Resolve the included file
2023-03-10 12:35:47 -05:00
if ( ! TryResolveIncludePath ( context , includePath , type , out FileItem ? includedFile ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( ignoreMissingIncludes )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
Log . TraceWarningOnce ( "Couldn't resolve include '{0}' ({1})" , includePath , context . SourceFile . Location ) ;
2022-08-10 16:03:37 +00:00
}
else
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Couldn't resolve include '{0}' ({1})" , includePath , context . SourceFile . Location ) ;
2022-08-10 16:03:37 +00:00
}
}
2023-03-10 12:35:47 -05:00
return includedFile ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Parse a source file fragment, using cached transforms if possible
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
// Check if there's a valid transform that matches the current state
2023-03-10 12:35:47 -05:00
int transformIdx = 0 ;
for ( ; ; )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
PreprocessorTransform [ ] transforms ;
lock ( fragment )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
transforms = fragment . Transforms ;
if ( transformIdx = = transforms . Length )
2022-08-10 16:03:37 +00:00
{
// Attach a new transform to the current state
2023-03-10 12:35:47 -05:00
PreprocessorTransform transform = _state . BeginCapture ( ) ;
for ( ; context . MarkupIdx < fragment . MarkupMax ; context . MarkupIdx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
SourceFileMarkup markup = sourceFile . Markup [ context . MarkupIdx ] ;
ParseMarkup ( markup . Type , markup . Tokens ! , context ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
transform = _state . EndCapture ( ) ! ;
2022-08-10 16:03:37 +00:00
// Add it to the fragment for future fragments
2023-03-10 12:35:47 -05:00
PreprocessorTransform [ ] newTransforms = new PreprocessorTransform [ fragment . Transforms . Length + 1 ] ;
for ( int idx = 0 ; idx < transforms . Length ; idx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
newTransforms [ idx ] = transforms [ idx ] ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
newTransforms [ transforms . Length ] = transform ;
2022-08-10 16:03:37 +00:00
// Assign it to the fragment
2023-03-10 12:35:47 -05:00
fragment . Transforms = newTransforms ;
2022-08-10 16:03:37 +00:00
return ;
}
}
2023-03-10 12:35:47 -05:00
for ( ; transformIdx < transforms . Length ; transformIdx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
PreprocessorTransform transform = transforms [ transformIdx ] ;
if ( _state . TryToApply ( transform ) )
2022-08-10 16:03:37 +00:00
{
// Update the pragma once state
2023-03-10 12:35:47 -05:00
if ( transform . HasPragmaOnce )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
_pragmaOnceFiles . Add ( sourceFile . File ) ;
2022-08-10 16:03:37 +00:00
}
// Move to the end of the fragment
2023-03-10 12:35:47 -05:00
context . MarkupIdx = fragment . MarkupMax ;
2022-08-10 16:03:37 +00:00
return ;
}
}
}
}
/// <summary>
/// Validate and add a macro using the given parameter and token list
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( tokens . Count = = 0 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
tokens . Add ( new Token ( TokenType . Placemarker , TokenFlags . None ) ) ;
2022-08-10 16:03:37 +00:00
}
else
{
2023-03-10 12:35:47 -05:00
if ( tokens [ 0 ] . HasLeadingSpace )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
tokens [ 0 ] = tokens [ 0 ] . RemoveFlags ( TokenFlags . HasLeadingSpace ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
if ( tokens [ 0 ] . Type = = TokenType . HashHash | | tokens [ ^ 1 ] . Type = = TokenType . HashHash )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Invalid use of concatenation at start or end of token sequence" ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
if ( parameters = = null | | parameters . Count = = 0 | | parameters [ ^ 1 ] ! = Identifiers . __VA_ARGS__ )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( tokens . Any ( x = > x . Identifier = = Identifiers . __VA_ARGS__ ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Invalid reference to {0}" , Identifiers . __VA_ARGS__ ) ;
2022-08-10 16:03:37 +00:00
}
}
}
2023-03-10 12:35:47 -05:00
_state . DefineMacro ( new PreprocessorMacro ( name , parameters , tokens ) ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
2022-08-10 16:03:37 +00:00
/// <summary>
/// Set a predefined macro to a given value
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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>
2022-08-10 16:03:37 +00:00
/// <returns>The created macro</returns>
2023-03-10 12:35:47 -05:00
void AddLiteralMacro ( string name , TokenType type , string value )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
Token token = new ( type , TokenFlags . None , value ) ;
PreprocessorMacro macro = new ( Identifier . FindOrAdd ( name ) , null , new List < Token > { token } ) ;
_state . DefineMacro ( macro ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Parse a marked up directive from a file
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
switch ( type )
2022-08-10 16:03:37 +00:00
{
case SourceFileMarkupType . Include :
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Include directives should be handled by the caller." ) ;
2022-08-10 16:03:37 +00:00
case SourceFileMarkupType . Define :
2023-03-10 12:35:47 -05:00
ParseDefineDirective ( tokens , context ) ;
2022-08-10 16:03:37 +00:00
break ;
case SourceFileMarkupType . Undef :
2023-03-10 12:35:47 -05:00
ParseUndefDirective ( tokens , context ) ;
2022-08-10 16:03:37 +00:00
break ;
case SourceFileMarkupType . If :
2023-03-10 12:35:47 -05:00
ParseIfDirective ( tokens , context ) ;
2022-08-10 16:03:37 +00:00
break ;
case SourceFileMarkupType . Ifdef :
2023-03-10 12:35:47 -05:00
ParseIfdefDirective ( tokens , context ) ;
2022-08-10 16:03:37 +00:00
break ;
case SourceFileMarkupType . Ifndef :
2023-03-10 12:35:47 -05:00
ParseIfndefDirective ( tokens , context ) ;
2022-08-10 16:03:37 +00:00
break ;
case SourceFileMarkupType . Elif :
2023-03-10 12:35:47 -05:00
ParseElifDirective ( tokens , context ) ;
2022-08-10 16:03:37 +00:00
break ;
case SourceFileMarkupType . Else :
2023-03-10 12:35:47 -05:00
ParseElseDirective ( tokens , context ) ;
2022-08-10 16:03:37 +00:00
break ;
case SourceFileMarkupType . Endif :
2023-03-10 12:35:47 -05:00
ParseEndifDirective ( tokens , context ) ;
2022-08-10 16:03:37 +00:00
break ;
case SourceFileMarkupType . Pragma :
2023-03-10 12:35:47 -05:00
ParsePragmaDirective ( tokens , context ) ;
2022-08-10 16:03:37 +00:00
break ;
}
}
/// <summary>
/// Read a macro definition
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( _state . IsCurrentBranchActive ( ) )
2022-08-10 16:03:37 +00:00
{
// Check there's a name token
2023-03-10 12:35:47 -05:00
if ( tokens . Count < 1 | | tokens [ 0 ] . Type ! = TokenType . Identifier | | tokens [ 0 ] . Identifier = = Identifiers . Defined )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Invalid macro name" ) ;
2022-08-10 16:03:37 +00:00
}
// Read the macro name
2023-03-10 12:35:47 -05:00
Identifier name = tokens [ 0 ] . Identifier ! ;
int tokenIdx = 1 ;
2022-08-10 16:03:37 +00:00
// Read the macro parameter list, if there is one
2023-03-10 12:35:47 -05:00
List < Identifier > ? parameters = null ;
if ( tokenIdx < tokens . Count & & ! tokens [ tokenIdx ] . HasLeadingSpace & & tokens [ tokenIdx ] . Type = = TokenType . LeftParen )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
parameters = new List < Identifier > ( ) ;
if ( + + tokenIdx = = tokens . Count )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Unexpected end of macro parameter list" ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
if ( tokens [ tokenIdx ] . Type ! = TokenType . RightParen )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
for ( ; ; tokenIdx + + )
2022-08-10 16:03:37 +00:00
{
// Check there's enough tokens left for a parameter name, plus ',' or ')'
2023-03-10 12:35:47 -05:00
if ( tokenIdx + 2 > tokens . Count )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Unexpected end of macro parameter list" ) ;
2022-08-10 16:03:37 +00:00
}
// Check it's a valid name, and add it to the list
2023-03-10 12:35:47 -05:00
Token nameToken = tokens [ tokenIdx + + ] ;
if ( nameToken . Type = = TokenType . Ellipsis )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( tokens [ tokenIdx ] . Type ! = TokenType . RightParen )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Variadic macro arguments must be last in list" ) ;
2022-08-10 16:03:37 +00:00
}
else
{
2023-03-10 12:35:47 -05:00
nameToken = new Token ( Identifiers . __VA_ARGS__ , nameToken . Flags & TokenFlags . HasLeadingSpace ) ;
2022-08-10 16:03:37 +00:00
}
}
else
{
2023-03-10 12:35:47 -05:00
if ( nameToken . Type ! = TokenType . Identifier | | nameToken . Identifier = = Identifiers . __VA_ARGS__ )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Invalid preprocessor token: {0}" , nameToken ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
if ( parameters . Contains ( nameToken . Identifier ! ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "'{0}' has already been used as an argument name" , nameToken ) ;
2022-08-10 16:03:37 +00:00
}
}
2023-03-10 12:35:47 -05:00
parameters . Add ( nameToken . Identifier ! ) ;
2022-08-10 16:03:37 +00:00
// Read the separator
2023-03-10 12:35:47 -05:00
Token separatorToken = tokens [ tokenIdx ] ;
if ( separatorToken . Type = = TokenType . RightParen )
2022-08-10 16:03:37 +00:00
{
break ;
}
2023-03-10 12:35:47 -05:00
if ( separatorToken . Type ! = TokenType . Comma )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Expected ',' or ')'" ) ;
2022-08-10 16:03:37 +00:00
}
}
}
2023-03-10 12:35:47 -05:00
tokenIdx + + ;
2022-08-10 16:03:37 +00:00
}
// Read the macro tokens
2023-03-10 12:35:47 -05:00
AddMacro ( context , name , parameters , tokens . Skip ( tokenIdx ) . ToList ( ) ) ;
2022-08-10 16:03:37 +00:00
}
}
/// <summary>
/// Parse an #undef directive
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( _state . IsCurrentBranchActive ( ) )
2022-08-10 16:03:37 +00:00
{
// Check there's a name token
2023-03-10 12:35:47 -05:00
if ( tokens . Count ! = 1 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Expected a single token after #undef" ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
if ( tokens [ 0 ] . Type ! = TokenType . Identifier )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Invalid macro name '{0}'" , tokens [ 0 ] ) ;
2022-08-10 16:03:37 +00:00
}
// Remove the macro from the list of definitions
2023-03-10 12:35:47 -05:00
_state . UndefMacro ( tokens [ 0 ] . Identifier ! ) ;
2022-08-10 16:03:37 +00:00
}
}
/// <summary>
/// Parse an #if directive
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
PreprocessorBranch branch = PreprocessorBranch . HasIfDirective ;
if ( _state . IsCurrentBranchActive ( ) )
2022-08-10 16:03:37 +00:00
{
// Read a line into the buffer and expand the macros in it
2023-03-10 12:35:47 -05:00
List < Token > expandedTokens = new ( ) ;
ExpandMacros ( tokens , expandedTokens , true , context ) ;
2022-08-10 16:03:37 +00:00
// Evaluate the condition
2023-03-10 12:35:47 -05:00
long result = PreprocessorExpression . Evaluate ( context , expandedTokens ) ;
if ( result ! = 0 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
branch | = PreprocessorBranch . Active | PreprocessorBranch . Taken ;
2022-08-10 16:03:37 +00:00
}
}
2023-03-10 12:35:47 -05:00
_state . PushBranch ( branch ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Parse an #ifdef directive
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
PreprocessorBranch branch = PreprocessorBranch . HasIfdefDirective ;
if ( _state . IsCurrentBranchActive ( ) )
2022-08-10 16:03:37 +00:00
{
// Make sure there's only one token
2023-03-10 12:35:47 -05:00
if ( tokens . Count ! = 1 | | tokens [ 0 ] . Type ! = TokenType . Identifier )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Missing or invalid macro name for #ifdef directive" ) ;
2022-08-10 16:03:37 +00:00
}
// Check if the macro is defined
2023-03-10 12:35:47 -05:00
if ( _state . IsMacroDefined ( tokens [ 0 ] . Identifier ! ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
branch | = PreprocessorBranch . Active | PreprocessorBranch . Taken ;
2022-08-10 16:03:37 +00:00
}
}
2023-03-10 12:35:47 -05:00
_state . PushBranch ( branch ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Parse an #ifndef directive
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
PreprocessorBranch branch = PreprocessorBranch . HasIfndefDirective ;
if ( _state . IsCurrentBranchActive ( ) )
2022-08-10 16:03:37 +00:00
{
// Make sure there's only one token
2023-03-10 12:35:47 -05:00
if ( tokens . Count ! = 1 | | tokens [ 0 ] . Type ! = TokenType . Identifier )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Missing or invalid macro name for #ifndef directive" ) ;
2022-08-10 16:03:37 +00:00
}
// Check if the macro is defined
2023-03-10 12:35:47 -05:00
if ( ! _state . IsMacroDefined ( tokens [ 0 ] . Identifier ! ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
branch | = PreprocessorBranch . Active | PreprocessorBranch . Taken ;
2022-08-10 16:03:37 +00:00
}
}
2023-03-10 12:35:47 -05:00
_state . PushBranch ( branch ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Parse an #elif directive
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
// Check we're in a branch, and haven't already read an #else directive
2023-03-10 12:35:47 -05:00
if ( ! _state . TryPopBranch ( out PreprocessorBranch branch ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "#elif directive outside conditional block" ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
if ( branch . HasFlag ( PreprocessorBranch . Complete ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "#elif directive cannot appear after #else" ) ;
2022-08-10 16:03:37 +00:00
}
// Pop the current branch state at this depth, so we can test against whether the parent state is enabled
2023-03-10 12:35:47 -05:00
branch = ( branch | PreprocessorBranch . HasElifDirective ) & ~ PreprocessorBranch . Active ;
if ( _state . IsCurrentBranchActive ( ) )
2022-08-10 16:03:37 +00:00
{
// Read a line into the buffer and expand the macros in it
2023-03-10 12:35:47 -05:00
List < Token > expandedTokens = new ( ) ;
ExpandMacros ( tokens , expandedTokens , true , context ) ;
2022-08-10 16:03:37 +00:00
// Check we're at the end of a conditional block
2023-03-10 12:35:47 -05:00
if ( ! branch . HasFlag ( PreprocessorBranch . Taken ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
long result = PreprocessorExpression . Evaluate ( context , expandedTokens ) ;
if ( result ! = 0 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
branch | = PreprocessorBranch . Active | PreprocessorBranch . Taken ;
2022-08-10 16:03:37 +00:00
}
}
}
2023-03-10 12:35:47 -05:00
_state . PushBranch ( branch ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Parse an #else directive
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
// Make sure there's nothing else on the line
2023-03-10 12:35:47 -05:00
if ( tokens . Count > 0 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Garbage after #else directive" ) ;
2022-08-10 16:03:37 +00:00
}
// Check we're in a branch, and haven't already read an #else directive
2023-03-10 12:35:47 -05:00
if ( ! _state . TryPopBranch ( out PreprocessorBranch branch ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "#else directive without matching #if directive" ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
if ( ( branch & PreprocessorBranch . Complete ) ! = 0 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Only one #else directive can appear in a conditional block" ) ;
2022-08-10 16:03:37 +00:00
}
// Check whether to take this branch, but only allow activating if the parent state is active.
2023-03-10 12:35:47 -05:00
branch & = ~ PreprocessorBranch . Active ;
if ( _state . IsCurrentBranchActive ( ) & & ! branch . HasFlag ( PreprocessorBranch . Taken ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
branch | = PreprocessorBranch . Active | PreprocessorBranch . Taken ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
_state . PushBranch ( branch | PreprocessorBranch . Complete ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Parse an #endif directive
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="tokens">List of tokens in the directive</param>
/// <param name="context">The context that this directive is being parsed in</param>
[SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "<Pending>")]
public void ParseEndifDirective ( List < Token > tokens , PreprocessorContext context )
2022-08-10 16:03:37 +00:00
{
// Pop the branch off the stack
2023-03-10 12:35:47 -05:00
if ( ! _state . TryPopBranch ( out _ ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "#endif directive without matching #if/#ifdef/#ifndef directive" ) ;
2022-08-10 16:03:37 +00:00
}
}
/// <summary>
/// Parse a #pragma directive
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( _state . IsCurrentBranchActive ( ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( tokens . Count = = 1 & & tokens [ 0 ] . Identifier = = Identifiers . Once )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
SourceFile sourceFile = GetCurrentSourceFile ( context ) ! ;
_pragmaOnceFiles . Add ( sourceFile . File ) ;
_state . MarkPragmaOnce ( ) ;
2022-08-10 16:03:37 +00:00
}
}
}
/// <summary>
/// Expand macros in the given sequence.
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="inputTokens">Sequence of input tokens</param>
/// <param name="outputTokens">List to receive the expanded tokens</param>
/// <param name="isConditional">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 isConditional , PreprocessorContext context )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
List < PreprocessorMacro > ignoreMacros = new ( ) ;
ExpandMacrosRecursively ( inputTokens , outputTokens , isConditional , ignoreMacros , context ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Expand macros in the given sequence, ignoring previously expanded macro names from a list.
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="inputTokens">Sequence of input tokens</param>
/// <param name="outputTokens">List to receive the expanded tokens</param>
/// <param name="isConditional">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 isConditional , List < PreprocessorMacro > ignoreMacros , PreprocessorContext context )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
IEnumerator < Token > inputEnumerator = inputTokens . GetEnumerator ( ) ;
if ( inputEnumerator . MoveNext ( ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
for ( ; ; )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( ! ReadExpandedToken ( inputEnumerator , outputTokens , isConditional , ignoreMacros , context ) )
2022-08-10 16:03:37 +00:00
{
break ;
}
}
}
}
/// <summary>
/// Merges an optional leading space flag into the given token (recycling the original token if possible).
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="token">The token to merge a leading space into</param>
/// <param name="hasLeadingSpace">The leading space flag</param>
2022-08-10 16:03:37 +00:00
/// <returns>New token with the leading space flag set, or the existing token</returns>
2023-03-10 12:35:47 -05:00
static Token MergeLeadingSpace ( Token token , bool hasLeadingSpace )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
Token result = token ;
if ( hasLeadingSpace & & ! result . HasLeadingSpace )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
result = result . AddFlags ( TokenFlags . HasLeadingSpace ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
return result ;
2022-08-10 16:03:37 +00:00
}
/// <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>
2023-03-10 12:35:47 -05:00
/// <param name="inputEnumerator">The enumerator of input tokens</param>
/// <param name="outputTokens">List to receive the expanded tokens</param>
/// <param name="isConditional">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>
2022-08-10 16:03:37 +00:00
/// <returns>Result from calling the enumerator's MoveNext() method</returns>
2023-03-10 12:35:47 -05:00
bool ReadExpandedToken ( IEnumerator < Token > inputEnumerator , List < Token > outputTokens , bool isConditional , List < PreprocessorMacro > ignoreMacros , PreprocessorContext context )
2022-08-10 16:03:37 +00:00
{
// Capture the first token, then move to the next
2023-03-10 12:35:47 -05:00
outputTokens . Add ( inputEnumerator . Current ) ;
bool moveNext = inputEnumerator . MoveNext ( ) ;
2022-08-10 16:03:37 +00:00
// If it's an identifier, try to resolve it as a macro
2023-03-10 12:35:47 -05:00
if ( outputTokens [ ^ 1 ] . Identifier = = Identifiers . Defined & & isConditional )
2022-08-10 16:03:37 +00:00
{
// Remove the 'defined' keyword
2023-03-10 12:35:47 -05:00
outputTokens . RemoveAt ( outputTokens . Count - 1 ) ;
2022-08-10 16:03:37 +00:00
// Make sure there's another token
2023-03-10 12:35:47 -05:00
if ( ! moveNext )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Invalid syntax for 'defined' expression" ) ;
2022-08-10 16:03:37 +00:00
}
// Check for the form 'defined identifier'
2023-03-10 12:35:47 -05:00
Token nameToken ;
if ( inputEnumerator . Current . Type = = TokenType . Identifier )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
nameToken = inputEnumerator . Current ;
2022-08-10 16:03:37 +00:00
}
else
{
// Otherwise assume the form 'defined ( identifier )'
2023-03-10 12:35:47 -05:00
if ( inputEnumerator . Current . Type ! = TokenType . LeftParen | | ! inputEnumerator . MoveNext ( ) | | inputEnumerator . Current . Type ! = TokenType . Identifier )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Invalid syntax for 'defined' expression" ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
nameToken = inputEnumerator . Current ;
if ( ! inputEnumerator . MoveNext ( ) | | inputEnumerator . Current . Type ! = TokenType . RightParen )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Invalid syntax for 'defined' expression" ) ;
2022-08-10 16:03:37 +00:00
}
}
// Insert a token for whether it's defined or not
2023-03-10 12:35:47 -05:00
outputTokens . Add ( new Token ( TokenType . Number , TokenFlags . None , _state . IsMacroDefined ( nameToken . Identifier ! ) ? s_oneLiteral : s_zeroLiteral ) ) ;
moveNext = inputEnumerator . MoveNext ( ) ;
2022-08-10 16:03:37 +00:00
}
else
{
// Repeatedly try to expand the last token into the list
2023-03-10 12:35:47 -05:00
while ( outputTokens [ ^ 1 ] . Type = = TokenType . Identifier & & ! outputTokens [ ^ 1 ] . Flags . HasFlag ( TokenFlags . DisableExpansion ) )
2022-08-10 16:03:37 +00:00
{
// Try to get a macro for the current token
2023-03-10 12:35:47 -05:00
if ( ! _state . TryGetMacro ( outputTokens [ ^ 1 ] . Identifier ! , out PreprocessorMacro ? macro ) | | ignoreMacros . Contains ( macro ) )
2022-08-10 16:03:37 +00:00
{
break ;
}
2023-03-10 12:35:47 -05:00
if ( macro . IsFunctionMacro & & ( ! moveNext | | inputEnumerator . Current . Type ! = TokenType . LeftParen ) )
2022-08-10 16:03:37 +00:00
{
break ;
}
// Remove the macro name from the output list
2023-03-10 12:35:47 -05:00
bool hasLeadingSpace = outputTokens [ ^ 1 ] . HasLeadingSpace ;
outputTokens . RemoveAt ( outputTokens . Count - 1 ) ;
2022-08-10 16:03:37 +00:00
// Save the initial number of tokens in the list, so we can tell if it expanded
2023-03-10 12:35:47 -05:00
int numTokens = outputTokens . Count ;
2022-08-10 16:03:37 +00:00
// If it's an object macro, expand it immediately into the output buffer
2023-03-10 12:35:47 -05:00
if ( macro . IsObjectMacro )
2022-08-10 16:03:37 +00:00
{
// Expand the macro tokens into the output buffer
2023-03-10 12:35:47 -05:00
ExpandObjectMacro ( macro , outputTokens , isConditional , ignoreMacros , context ) ;
2022-08-10 16:03:37 +00:00
}
else
{
// Read balanced token for argument list
2023-03-10 12:35:47 -05:00
List < Token > argumentTokens = new ( ) ;
moveNext = ReadBalancedToken ( inputEnumerator , argumentTokens , context ) ;
2022-08-10 16:03:37 +00:00
// Expand the macro tokens into the output buffer
2023-03-10 12:35:47 -05:00
ExpandFunctionMacro ( macro , argumentTokens , outputTokens , isConditional , ignoreMacros , context ) ;
2022-08-10 16:03:37 +00:00
}
// If the macro expanded to nothing, quit
2023-03-10 12:35:47 -05:00
if ( outputTokens . Count < = numTokens )
2022-08-10 16:03:37 +00:00
{
break ;
}
// Make sure the space is propagated to the expanded macro
2023-03-10 12:35:47 -05:00
outputTokens [ numTokens ] = MergeLeadingSpace ( outputTokens [ numTokens ] , hasLeadingSpace ) ;
2022-08-10 16:03:37 +00:00
// Mark any tokens matching the macro name as not to be expanded again. This can happen with recursive object macros, eg. #define DWORD ::DWORD
2023-03-10 12:35:47 -05:00
for ( int idx = numTokens ; idx < outputTokens . Count ; idx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( outputTokens [ idx ] . Type = = TokenType . Identifier & & outputTokens [ idx ] . Identifier = = macro . Name )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
outputTokens [ idx ] = outputTokens [ idx ] . AddFlags ( TokenFlags . DisableExpansion ) ;
2022-08-10 16:03:37 +00:00
}
}
}
}
2023-03-10 12:35:47 -05:00
return moveNext ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Gets a string for the __FILE__ macro
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="context">Context to scan to find the current file</param>
2022-08-10 16:03:37 +00:00
/// <returns>String representing the current context</returns>
2023-03-10 12:35:47 -05:00
static string GetCurrentFileMacroValue ( PreprocessorContext context )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
SourceFile ? sourceFile = GetCurrentSourceFile ( context ) ;
if ( sourceFile = = null )
2022-08-10 16:03:37 +00:00
{
return "<unknown>" ;
}
else
{
2023-03-10 12:35:47 -05:00
return sourceFile . Location . FullName ;
2022-08-10 16:03:37 +00:00
}
}
/// <summary>
/// Gets a string for the current file
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="context">Context to scan to find the current file</param>
2022-08-10 16:03:37 +00:00
/// <returns>Current source file being parsed</returns>
2023-03-10 12:35:47 -05:00
static SourceFile ? GetCurrentSourceFile ( PreprocessorContext context )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
SourceFile ? sourceFile = null ;
for ( PreprocessorContext ? outerContext = context ; outerContext ! = null ; outerContext = outerContext . Outer )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( outerContext is PreprocessorFileContext outerFileContext )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
sourceFile = outerFileContext . SourceFile ;
2022-08-10 16:03:37 +00:00
break ;
}
}
2023-03-10 12:35:47 -05:00
return sourceFile ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Gets the current line number
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="context">Context to scan to find the current file</param>
2022-08-10 16:03:37 +00:00
/// <returns>Line number in the first file encountered</returns>
2023-03-10 12:35:47 -05:00
static int GetCurrentLine ( PreprocessorContext context )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
for ( PreprocessorContext ? outerContext = context ; outerContext ! = null ; outerContext = outerContext . Outer )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( outerContext is PreprocessorFileContext outerFileContext )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
return outerFileContext . SourceFile . Markup [ outerFileContext . MarkupIdx ] . LineNumber ;
2022-08-10 16:03:37 +00:00
}
}
return 0 ;
}
/// <summary>
/// Expand an object macro
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="macro">The functional macro</param>
/// <param name="outputTokens">The list to receive the output tokens</param>
/// <param name="isConditional">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 isConditional , List < PreprocessorMacro > ignoreMacros , PreprocessorContext context )
2022-08-10 16:03:37 +00:00
{
// Special handling for the __LINE__ directive, since we need an updated line number for the current token
2023-03-10 12:35:47 -05:00
if ( macro . Name = = Identifiers . __FILE__ )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
Token token = new ( TokenType . String , TokenFlags . None , String . Format ( "\"{0}\"" , GetCurrentFileMacroValue ( context ) . Replace ( "\\" , "\\\\" ) ) ) ;
outputTokens . Add ( token ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
else if ( macro . Name = = Identifiers . __LINE__ )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
Token token = new ( TokenType . Number , TokenFlags . None , GetCurrentLine ( context ) . ToString ( ) ) ;
outputTokens . Add ( token ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
else if ( macro . Name = = Identifiers . __COUNTER__ )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
Token token = new ( TokenType . Number , TokenFlags . None , ( _counter + + ) . ToString ( ) ) ;
outputTokens . Add ( token ) ;
2022-08-10 16:03:37 +00:00
}
else
{
2023-03-10 12:35:47 -05:00
int outputTokenCount = outputTokens . Count ;
2022-08-10 16:03:37 +00:00
// Expand all the macros
2023-03-10 12:35:47 -05:00
ignoreMacros . Add ( macro ) ;
ExpandMacrosRecursively ( macro . Tokens , outputTokens , isConditional , ignoreMacros , context ) ;
ignoreMacros . RemoveAt ( ignoreMacros . Count - 1 ) ;
2022-08-10 16:03:37 +00:00
// Concatenate any adjacent tokens
2023-03-10 12:35:47 -05:00
for ( int idx = outputTokenCount + 1 ; idx < outputTokens . Count - 1 ; idx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( outputTokens [ idx ] . Type = = TokenType . HashHash )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
outputTokens [ idx - 1 ] = Token . Concatenate ( outputTokens [ idx - 1 ] , outputTokens [ idx + 1 ] , context ) ;
outputTokens . RemoveRange ( idx , 2 ) ;
idx - - ;
2022-08-10 16:03:37 +00:00
}
}
}
}
/// <summary>
/// Expand a function macro
/// </summary>
2023-03-10 12:35:47 -05:00
/// <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="isConditional">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 isConditional , List < PreprocessorMacro > ignoreMacros , PreprocessorContext context )
2022-08-10 16:03:37 +00:00
{
// Replace any newlines with spaces, and merge them with the following token
2023-03-10 12:35:47 -05:00
for ( int idx = 0 ; idx < argumentListTokens . Count ; idx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( argumentListTokens [ idx ] . Type = = TokenType . Newline )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( idx + 1 < argumentListTokens . Count )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
argumentListTokens [ idx + 1 ] = MergeLeadingSpace ( argumentListTokens [ idx + 1 ] , true ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
argumentListTokens . RemoveAt ( idx - - ) ;
2022-08-10 16:03:37 +00:00
}
}
// Split the arguments out into separate lists
2023-03-10 12:35:47 -05:00
List < List < Token > > arguments = new ( ) ;
if ( argumentListTokens . Count > 2 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
for ( int idx = 1 ; ; idx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( ! macro . HasVariableArgumentList | | arguments . Count < macro . Parameters ! . Count )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
arguments . Add ( new List < Token > ( ) ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
List < Token > argument = arguments [ ^ 1 ] ;
2022-08-10 16:03:37 +00:00
2023-03-10 12:35:47 -05:00
int initialIdx = idx ;
while ( idx < argumentListTokens . Count - 1 & & argumentListTokens [ idx ] . Type ! = TokenType . Comma )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( ! ReadBalancedToken ( argumentListTokens , ref idx , argument ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Invalid argument" ) ;
2022-08-10 16:03:37 +00:00
}
}
2023-03-10 12:35:47 -05:00
if ( argument . Count > 0 & & arguments [ ^ 1 ] [ 0 ] . HasLeadingSpace )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
argument [ 0 ] = argument [ 0 ] . RemoveFlags ( TokenFlags . HasLeadingSpace ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
bool hasLeadingSpace = false ;
for ( int tokenIdx = 0 ; tokenIdx < argument . Count ; tokenIdx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( argument [ tokenIdx ] . Text . Length = = 0 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
hasLeadingSpace | = argument [ tokenIdx ] . HasLeadingSpace ;
argument . RemoveAt ( tokenIdx - - ) ;
2022-08-10 16:03:37 +00:00
}
else
{
2023-03-10 12:35:47 -05:00
argument [ tokenIdx ] = MergeLeadingSpace ( argument [ tokenIdx ] , hasLeadingSpace ) ;
hasLeadingSpace = false ;
2022-08-10 16:03:37 +00:00
}
}
2023-03-10 12:35:47 -05:00
if ( argument . Count = = 0 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
argument . Add ( new Token ( TokenType . Placemarker , TokenFlags . None ) ) ;
argument . Add ( new Token ( TokenType . Placemarker , hasLeadingSpace ? TokenFlags . HasLeadingSpace : TokenFlags . None ) ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
if ( idx = = argumentListTokens . Count - 1 )
2022-08-10 16:03:37 +00:00
{
break ;
}
2023-03-10 12:35:47 -05:00
if ( argumentListTokens [ idx ] . Type ! = TokenType . Comma )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Expected ',' between arguments" ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
if ( macro . HasVariableArgumentList & & arguments . Count = = macro . Parameters ! . Count & & idx < argumentListTokens . Count - 1 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
arguments [ ^ 1 ] . Add ( argumentListTokens [ idx ] ) ;
2022-08-10 16:03:37 +00:00
}
}
}
// Add an empty variable argument if one was not specified
2023-03-10 12:35:47 -05:00
if ( macro . HasVariableArgumentList & & arguments . Count = = macro . Parameters ! . Count - 1 )
2022-08-10 16:03:37 +00:00
{
2024-04-02 19:54:03 -04:00
arguments . Add ( new List < Token > { new ( TokenType . Placemarker , TokenFlags . None ) } ) ;
2022-08-10 16:03:37 +00:00
}
// Validate the argument list
2023-03-10 12:35:47 -05:00
if ( arguments . Count ! = macro . Parameters ! . Count )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Incorrect number of arguments to macro" ) ;
2022-08-10 16:03:37 +00:00
}
// Expand each one of the arguments
2023-03-10 12:35:47 -05:00
List < List < Token > > expandedArguments = new ( ) ;
for ( int idx = 0 ; idx < arguments . Count ; idx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
List < Token > newArguments = new ( ) ;
ExpandMacrosRecursively ( arguments [ idx ] , newArguments , isConditional , ignoreMacros , context ) ;
expandedArguments . Add ( newArguments ) ;
2022-08-10 16:03:37 +00:00
}
// Substitute all the argument tokens
2023-03-10 12:35:47 -05:00
List < Token > expandedTokens = new ( ) ;
for ( int idx = 0 ; idx < macro . Tokens . Count ; idx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
Token token = macro . Tokens [ idx ] ;
if ( token . Type = = TokenType . Hash & & idx + 1 < macro . Tokens . Count )
2022-08-10 16:03:37 +00:00
{
// Stringizing operator
2023-03-10 12:35:47 -05:00
int paramIdx = macro . FindParameterIndex ( macro . Tokens [ + + idx ] . Identifier ! ) ;
if ( paramIdx = = - 1 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "{0} is not an argument name" , macro . Tokens [ idx ] . Text ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
expandedTokens . Add ( new Token ( TokenType . String , token . Flags & TokenFlags . HasLeadingSpace , String . Format ( "\"{0}\"" , Token . Format ( arguments [ paramIdx ] ) . Replace ( "\\" , "\\\\" ) . Replace ( "\"" , "\\\"" ) ) ) ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
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__ )
2022-08-10 16:03:37 +00:00
{
// Special MSVC/GCC extension: ', ## __VA_ARGS__' removes the comma if __VA_ARGS__ is empty. MSVC seems to format the result with a forced space.
2023-03-10 12:35:47 -05:00
List < Token > expandedArgument = expandedArguments [ ^ 1 ] ;
if ( expandedArgument . Any ( x = > x . Text . Length > 0 ) )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
expandedTokens . Add ( token ) ;
AppendTokensWithWhitespace ( expandedTokens , expandedArgument , false ) ;
idx + = 2 ;
2022-08-10 16:03:37 +00:00
}
else
{
2023-03-10 12:35:47 -05:00
expandedTokens . Add ( new Token ( TokenType . Placemarker , token . Flags & TokenFlags . HasLeadingSpace ) ) ;
expandedTokens . Add ( new Token ( TokenType . Placemarker , TokenFlags . HasLeadingSpace ) ) ;
idx + = 2 ;
2022-08-10 16:03:37 +00:00
}
}
2023-03-10 12:35:47 -05:00
else if ( token . Type = = TokenType . Identifier )
2022-08-10 16:03:37 +00:00
{
// Expand a parameter
2023-03-10 12:35:47 -05:00
int paramIdx = macro . FindParameterIndex ( token . Identifier ! ) ;
if ( paramIdx = = - 1 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
expandedTokens . Add ( token ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
else if ( idx > 0 & & macro . Tokens [ idx - 1 ] . Type = = TokenType . HashHash )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
AppendTokensWithWhitespace ( expandedTokens , arguments [ paramIdx ] , token . HasLeadingSpace ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
else if ( idx + 1 < macro . Tokens . Count & & macro . Tokens [ idx + 1 ] . Type = = TokenType . HashHash )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
AppendTokensWithWhitespace ( expandedTokens , arguments [ paramIdx ] , token . HasLeadingSpace ) ;
2022-08-10 16:03:37 +00:00
}
else
{
2023-03-10 12:35:47 -05:00
AppendTokensWithWhitespace ( expandedTokens , expandedArguments [ paramIdx ] , token . HasLeadingSpace ) ;
2022-08-10 16:03:37 +00:00
}
}
else
{
2023-03-10 12:35:47 -05:00
expandedTokens . Add ( token ) ;
2022-08-10 16:03:37 +00:00
}
}
// Concatenate adjacent tokens
2023-03-10 12:35:47 -05:00
for ( int idx = 1 ; idx < expandedTokens . Count - 1 ; idx + + )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( expandedTokens [ idx ] . Type = = TokenType . HashHash )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
Token concatenatedToken = Token . Concatenate ( expandedTokens [ idx - 1 ] , expandedTokens [ idx + 1 ] , context ) ;
expandedTokens . RemoveRange ( idx , 2 ) ;
expandedTokens [ - - idx ] = concatenatedToken ;
2022-08-10 16:03:37 +00:00
}
}
// Finally, return the expansion of this
2023-03-10 12:35:47 -05:00
ignoreMacros . Add ( macro ) ;
ExpandMacrosRecursively ( expandedTokens , outputTokens , isConditional , ignoreMacros , context ) ;
ignoreMacros . RemoveAt ( ignoreMacros . Count - 1 ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Appends a list of tokens to another list, setting the leading whitespace flag to the given value
/// </summary>
2023-03-10 12:35:47 -05:00
/// <param name="outputTokens">List to receive the appended tokens</param>
/// <param name="inputTokens">List of tokens to append</param>
/// <param name="hasLeadingSpace">Whether there is space before the first token</param>
static void AppendTokensWithWhitespace ( List < Token > outputTokens , List < Token > inputTokens , bool hasLeadingSpace )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( inputTokens . Count > 0 )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
outputTokens . Add ( MergeLeadingSpace ( inputTokens [ 0 ] , hasLeadingSpace ) ) ;
outputTokens . AddRange ( inputTokens . Skip ( 1 ) ) ;
2022-08-10 16:03:37 +00:00
}
}
/// <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>
2023-03-10 12:35:47 -05:00
/// <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>
2022-08-10 16:03:37 +00:00
/// <returns>True if a balanced expression was read, or false if the end of the list was encountered before finding a matching token</returns>
2024-04-17 11:04:13 -04:00
static bool ReadBalancedToken ( List < Token > inputTokens , ref int inputIdx , List < Token > outputTokens )
2022-08-10 16:03:37 +00:00
{
// Copy a single token to the output list
2023-03-10 12:35:47 -05:00
Token token = inputTokens [ inputIdx + + ] ;
outputTokens . Add ( token ) ;
2022-08-10 16:03:37 +00:00
// If it was the start of a subexpression, copy until the closing parenthesis
2023-03-10 12:35:47 -05:00
if ( token . Type = = TokenType . LeftParen )
2022-08-10 16:03:37 +00:00
{
// Copy the contents of the subexpression
2023-03-10 12:35:47 -05:00
for ( ; ; )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( inputIdx = = inputTokens . Count )
2022-08-10 16:03:37 +00:00
{
return false ;
}
2023-03-10 12:35:47 -05:00
if ( inputTokens [ inputIdx ] . Type = = TokenType . RightParen )
2022-08-10 16:03:37 +00:00
{
break ;
}
2023-03-10 12:35:47 -05:00
if ( ! ReadBalancedToken ( inputTokens , ref inputIdx , outputTokens ) )
2022-08-10 16:03:37 +00:00
{
return false ;
}
}
// Copy the closing parenthesis
2023-03-10 12:35:47 -05:00
token = inputTokens [ inputIdx + + ] ;
outputTokens . Add ( token ) ;
2022-08-10 16:03:37 +00:00
}
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>
2023-03-10 12:35:47 -05:00
/// <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>
2022-08-10 16:03:37 +00:00
/// <returns>True if a balanced expression was read, or false if the end of the list was encountered before finding a matching token</returns>
2024-04-17 11:04:13 -04:00
static bool ReadBalancedToken ( IEnumerator < Token > inputEnumerator , List < Token > outputTokens , PreprocessorContext context )
2022-08-10 16:03:37 +00:00
{
// Copy a single token to the output list
2023-03-10 12:35:47 -05:00
Token token = inputEnumerator . Current ;
bool moveNext = inputEnumerator . MoveNext ( ) ;
outputTokens . Add ( token ) ;
2022-08-10 16:03:37 +00:00
// If it was the start of a subexpression, copy until the closing parenthesis
2023-03-10 12:35:47 -05:00
if ( token . Type = = TokenType . LeftParen )
2022-08-10 16:03:37 +00:00
{
// Copy the contents of the subexpression
2023-03-10 12:35:47 -05:00
for ( ; ; )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
if ( ! moveNext )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
throw new PreprocessorException ( context , "Unbalanced token sequence" ) ;
2022-08-10 16:03:37 +00:00
}
2023-03-10 12:35:47 -05:00
if ( inputEnumerator . Current . Type = = TokenType . RightParen )
2022-08-10 16:03:37 +00:00
{
2023-03-10 12:35:47 -05:00
outputTokens . Add ( inputEnumerator . Current ) ;
moveNext = inputEnumerator . MoveNext ( ) ;
2022-08-10 16:03:37 +00:00
break ;
}
2023-03-10 12:35:47 -05:00
moveNext = ReadBalancedToken ( inputEnumerator , outputTokens , context ) ;
2022-08-10 16:03:37 +00:00
}
}
2023-03-10 12:35:47 -05:00
return moveNext ;
2022-08-10 16:03:37 +00:00
}
}
}