2020-11-23 13:33:12 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Threading ;
using System.Threading.Tasks ;
2020-12-21 23:07:37 -04:00
using EpicGames.Core ;
2020-11-23 13:33:12 -04:00
namespace UnrealBuildTool
{
/// <summary>
/// Possible types of source file markup
/// </summary>
enum SourceFileMarkupType
{
/// <summary>
/// A span of tokens which are not preprocessor directives
/// </summary>
Text ,
/// <summary>
/// An #include directive
/// </summary>
Include ,
/// <summary>
/// A #define directive
/// </summary>
Define ,
/// <summary>
/// An #undef directive
/// </summary>
Undef ,
/// <summary>
/// An #if directive
/// </summary>
If ,
/// <summary>
/// An #ifdef directive
/// </summary>
Ifdef ,
/// <summary>
/// An #ifndef directive
/// </summary>
Ifndef ,
/// <summary>
/// An #elif directive
/// </summary>
Elif ,
/// <summary>
/// An #else directive
/// </summary>
Else ,
/// <summary>
/// An #endif directive
/// </summary>
Endif ,
/// <summary>
/// A #pragma directive
/// </summary>
Pragma ,
/// <summary>
/// An #error directive
/// </summary>
Error ,
/// <summary>
/// A #warning directive
/// </summary>
Warning ,
/// <summary>
/// An empty '#' on a line of its own
/// </summary>
Empty ,
/// <summary>
/// Some other directive
/// </summary>
OtherDirective
}
/// <summary>
/// Base class for an annotated section of a source file
/// </summary>
[Serializable]
class SourceFileMarkup
{
/// <summary>
/// The directive corresponding to this markup
/// </summary>
public SourceFileMarkupType Type ;
/// <summary>
/// The one-based line number of this markup
/// </summary>
public int LineNumber ;
/// <summary>
/// The tokens parsed for this markup. Set for directives.
/// </summary>
2020-12-20 18:47:42 -04:00
public List < Token > ? Tokens ;
2020-11-23 13:33:12 -04:00
/// <summary>
/// Construct the annotation with the given range
/// </summary>
/// <param name="Type">The type of this directive</param>
/// <param name="LineNumber">The line number of this markup</param>
/// <param name="Tokens">List of tokens</param>
2020-12-20 18:47:42 -04:00
public SourceFileMarkup ( SourceFileMarkupType Type , int LineNumber , List < Token > ? Tokens )
2020-11-23 13:33:12 -04:00
{
this . Type = Type ;
this . LineNumber = LineNumber ;
this . Tokens = Tokens ;
}
/// <summary>
/// Constructs a markup object using data read from an archive
/// </summary>
/// <param name="Reader">The reader to deserialize from</param>
public SourceFileMarkup ( BinaryArchiveReader Reader )
{
Type = ( SourceFileMarkupType ) Reader . ReadByte ( ) ;
LineNumber = Reader . ReadInt ( ) ;
Tokens = Reader . ReadList ( ( ) = > Reader . ReadToken ( ) ) ;
}
/// <summary>
/// Serializes this object to a binary archive
/// </summary>
/// <param name="Writer">Writer to serialize to</param>
public void Write ( BinaryArchiveWriter Writer )
{
Writer . WriteByte ( ( byte ) Type ) ;
Writer . WriteInt ( LineNumber ) ;
Writer . WriteList ( Tokens , x = > Writer . WriteToken ( x ) ) ;
}
/// <summary>
/// Determines if this markup indicates a conditional preprocessor directive
/// </summary>
/// <returns>True if this object is a conditional preprocessor directive</returns>
public bool IsConditionalPreprocessorDirective ( )
{
switch ( Type )
{
case SourceFileMarkupType . If :
case SourceFileMarkupType . Ifdef :
case SourceFileMarkupType . Ifndef :
case SourceFileMarkupType . Elif :
case SourceFileMarkupType . Else :
case SourceFileMarkupType . Endif :
return true ;
}
return false ;
}
/// <summary>
/// How this condition modifies the condition depth. Opening "if" statements have a value of +1, "endif" statements have a value of -1, and "else" statements have a value of 0.
/// </summary>
public int GetConditionDepthDelta ( )
{
if ( Type = = SourceFileMarkupType . If | | Type = = SourceFileMarkupType . Ifdef | | Type = = SourceFileMarkupType . Ifndef )
{
return + 1 ;
}
else if ( Type = = SourceFileMarkupType . Endif )
{
return - 1 ;
}
else
{
return 0 ;
}
}
/// <summary>
/// Generate a string describing this annotation
/// </summary>
/// <returns>String representation for debugging</returns>
public override string ToString ( )
{
StringBuilder Result = new StringBuilder ( ) ;
Result . AppendFormat ( "[{0}] " , LineNumber ) ;
if ( Type = = SourceFileMarkupType . Text )
{
Result . Append ( "..." ) ;
}
else
{
Result . Append ( "#" ) ;
if ( Type ! = SourceFileMarkupType . OtherDirective )
{
Result . Append ( Type . ToString ( ) . ToLowerInvariant ( ) ) ;
}
if ( Tokens ! = null & & Tokens . Count > 0 )
{
Result . Append ( ' ' ) ;
Token . Format ( Tokens , Result ) ;
}
}
return Result . ToString ( ) ;
}
/// <summary>
/// Create markup for the given file
/// </summary>
/// <param name="Reader">Reader for tokens in the file</param>
/// <returns>Array of markup objects which split up the given text buffer</returns>
public static SourceFileMarkup [ ] Parse ( TokenReader Reader )
{
List < SourceFileMarkup > Markup = new List < SourceFileMarkup > ( ) ;
if ( Reader . MoveNext ( ) )
{
bool bMoveNext = true ;
while ( bMoveNext )
{
int StartLineNumber = Reader . LineNumber ;
if ( Reader . Current . Type = = TokenType . Hash )
{
// Create the appropriate markup object for the directive
SourceFileMarkupType Type = SourceFileMarkupType . OtherDirective ;
if ( Reader . MoveNext ( ) )
{
if ( Reader . Current . Type = = TokenType . Identifier )
{
2020-12-20 18:47:42 -04:00
Identifier Directive = Reader . Current . Identifier ! ;
2020-11-23 13:33:12 -04:00
if ( Directive = = Identifiers . Include )
{
Type = SourceFileMarkupType . Include ;
}
else if ( Directive = = Identifiers . Define )
{
Type = SourceFileMarkupType . Define ;
}
else if ( Directive = = Identifiers . Undef )
{
Type = SourceFileMarkupType . Undef ;
}
else if ( Directive = = Identifiers . If )
{
Type = SourceFileMarkupType . If ;
}
else if ( Directive = = Identifiers . Ifdef )
{
Type = SourceFileMarkupType . Ifdef ;
}
else if ( Directive = = Identifiers . Ifndef )
{
Type = SourceFileMarkupType . Ifndef ;
}
else if ( Directive = = Identifiers . Elif )
{
Type = SourceFileMarkupType . Elif ;
}
else if ( Directive = = Identifiers . Else )
{
Type = SourceFileMarkupType . Else ;
}
else if ( Directive = = Identifiers . Endif )
{
Type = SourceFileMarkupType . Endif ;
}
else if ( Directive = = Identifiers . Pragma )
{
Type = SourceFileMarkupType . Pragma ;
}
else if ( Directive = = Identifiers . Error )
{
Type = SourceFileMarkupType . Error ;
}
else if ( Directive = = Identifiers . Warning )
{
Type = SourceFileMarkupType . Warning ;
}
}
else if ( Reader . Current . Type = = TokenType . Newline )
{
Type = SourceFileMarkupType . Empty ;
}
}
// Create the token list
List < Token > Tokens = new List < Token > ( ) ;
if ( Type = = SourceFileMarkupType . OtherDirective )
{
Tokens . Add ( Reader . Current ) ;
}
// Read the first token
if ( Type = = SourceFileMarkupType . Empty )
{
bMoveNext = true ;
}
else if ( Type = = SourceFileMarkupType . Include )
{
bMoveNext = Reader . MoveNextIncludePath ( ) ;
}
else if ( Type = = SourceFileMarkupType . Error | | Type = = SourceFileMarkupType . Warning )
{
bMoveNext = Reader . MoveNextTokenString ( ) ;
}
else
{
bMoveNext = Reader . MoveNext ( ) ;
}
// Read the rest of the tokens
while ( bMoveNext & & Reader . Current . Type ! = TokenType . Newline )
{
Tokens . Add ( Reader . Current ) ;
bMoveNext = Reader . MoveNext ( ) ;
}
// Create the markup
Markup . Add ( new SourceFileMarkup ( Type , StartLineNumber , Tokens ) ) ;
// Move to the next token
bMoveNext = Reader . MoveNext ( ) ;
}
else if ( Reader . Current . Type ! = TokenType . Newline )
{
// Create the new fragment
Markup . Add ( new SourceFileMarkup ( SourceFileMarkupType . Text , StartLineNumber , null ) ) ;
// Move to the next directive
bMoveNext = Reader . MoveToNextDirective ( ) ;
}
else
{
// Skip the empty line
bMoveNext = Reader . MoveNext ( ) ;
}
}
}
return Markup . ToArray ( ) ;
}
}
}