// 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; 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 SourceFileMarkupType Type; /// /// The one-based line number of this markup /// public int LineNumber; /// /// The tokens parsed for this markup. Set for directives. /// public 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) { this.Type = Type; this.LineNumber = LineNumber; this.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() { switch(Type) { case SourceFileMarkupType.If: case SourceFileMarkupType.Ifdef: case SourceFileMarkupType.Ifndef: case SourceFileMarkupType.Elif: case SourceFileMarkupType.Else: case SourceFileMarkupType.Endif: return true; } return 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 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(); } /// /// 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 List(); 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) { 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 List(); 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(); } } }