// 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(); } } }