// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Text;
using EpicGames.Core;
namespace UnrealBuildTool
{
///
/// Possible types of source file markup
///
enum SourceFileMarkupType
{
///
/// A span of tokens which are not preprocessor directives
///
Text,
///
/// An #include directive
///
Include,
///
/// A #define directive
///
Define,
///
/// An #undef directive
///
Undef,
///
/// An #if directive
///
If,
///
/// An #ifdef directive
///
Ifdef,
///
/// An #ifndef directive
///
Ifndef,
///
/// An #elif directive
///
Elif,
///
/// An #else directive
///
Else,
///
/// An #endif directive
///
Endif,
///
/// A #pragma directive
///
Pragma,
///
/// An #error directive
///
Error,
///
/// A #warning directive
///
Warning,
///
/// An empty '#' on a line of its own
///
Empty,
///
/// Some other directive
///
OtherDirective
}
///
/// Base class for an annotated section of a source file
///
[Serializable]
class SourceFileMarkup
{
///
/// The directive corresponding to this markup
///
public readonly SourceFileMarkupType Type;
///
/// The one-based line number of this markup
///
public readonly int LineNumber;
///
/// The tokens parsed for this markup. Set for directives.
///
public readonly List? Tokens;
///
/// Construct the annotation with the given range
///
/// The type of this directive
/// The line number of this markup
/// List of tokens
public SourceFileMarkup(SourceFileMarkupType type, int lineNumber, List? tokens)
{
Type = type;
LineNumber = lineNumber;
Tokens = tokens;
}
///
/// Constructs a markup object using data read from an archive
///
/// The reader to deserialize from
public SourceFileMarkup(BinaryArchiveReader reader)
{
Type = (SourceFileMarkupType)reader.ReadByte();
LineNumber = reader.ReadInt();
Tokens = reader.ReadList(() => reader.ReadToken());
}
///
/// Serializes this object to a binary archive
///
/// Writer to serialize to
public void Write(BinaryArchiveWriter writer)
{
writer.WriteByte((byte)Type);
writer.WriteInt(LineNumber);
writer.WriteList(Tokens, x => writer.WriteToken(x));
}
///
/// Determines if this markup indicates a conditional preprocessor directive
///
/// True if this object is a conditional preprocessor directive
public bool IsConditionalPreprocessorDirective()
{
return Type switch
{
SourceFileMarkupType.If or SourceFileMarkupType.Ifdef or SourceFileMarkupType.Ifndef or SourceFileMarkupType.Elif or SourceFileMarkupType.Else or SourceFileMarkupType.Endif => true,
_ => false,
};
}
///
/// 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.
///
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;
}
}
///
/// Generate a string describing this annotation
///
/// String representation for debugging
public override string ToString()
{
StringBuilder result = new();
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();
}
///
/// Create markup for the given file
///
/// Reader for tokens in the file
/// Array of markup objects which split up the given text buffer
public static SourceFileMarkup[] Parse(TokenReader reader)
{
List markup = new();
if (reader.MoveNext())
{
bool moveNext = true;
while (moveNext)
{
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)
{
Identifier directive = reader.Current.Identifier!;
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 tokens = new();
if (type == SourceFileMarkupType.OtherDirective)
{
tokens.Add(reader.Current);
}
// Read the first token
if (type == SourceFileMarkupType.Empty)
{
moveNext = true;
}
else if (type == SourceFileMarkupType.Include)
{
moveNext = reader.MoveNextIncludePath();
}
else if (type == SourceFileMarkupType.Error || type == SourceFileMarkupType.Warning)
{
moveNext = reader.MoveNextTokenString();
}
else
{
moveNext = reader.MoveNext();
}
// Read the rest of the tokens
while (moveNext && reader.Current.Type != TokenType.Newline)
{
tokens.Add(reader.Current);
moveNext = reader.MoveNext();
}
// Create the markup
markup.Add(new SourceFileMarkup(type, startLineNumber, tokens));
// Move to the next token
moveNext = 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
moveNext = reader.MoveToNextDirective();
}
else
{
// Skip the empty line
moveNext = reader.MoveNext();
}
}
}
return markup.ToArray();
}
}
}