// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using EpicGames.Core; namespace UnrealBuildTool { /// /// Flags for a single token /// [Flags] enum TokenFlags : byte { /// /// No flags /// None = 0x00, /// /// The token has space before it /// HasLeadingSpace = 0x01, /// /// Do not replace any instances of this token with the corresponding macro. /// DisableExpansion = 0x02, } /// /// Enumeration for token types /// enum TokenType : byte { End, LeftParen, RightParen, Comma, Identifier, Number, String, Character, Newline, Ellipsis, StringOfTokens, SystemInclude, Placemarker, Dot, QuestionMark, Colon, LogicalNot, LogicalAnd, LogicalOr, BitwiseXor, BitwiseAnd, BitwiseNot, BitwiseOr, Equals, LeftShift, RightShift, CompareEqual, CompareNotEqual, CompareLessOrEqual, CompareLess, CompareGreaterOrEqual, CompareGreater, Plus, Minus, Multiply, Divide, Modulo, Hash, HashHash, Unknown, Max } /// /// Single lexical token /// [DebuggerDisplay("{Text}")] sealed class Token : IEquatable { /// /// Static token names /// static readonly string[] s_staticTokens; /// /// Contains the type of the token /// public readonly TokenType Type; /// /// Properties for this token /// public readonly TokenFlags Flags; /// /// If this token is identifier, contains the identifier name /// public readonly Identifier? Identifier; /// /// If this token is a literal, contains the raw utf-8 representation of it. /// public readonly byte[]? Literal; /// /// Constructor /// /// The token type /// Flags for this token public Token(TokenType type, TokenFlags flags) { Type = type; Flags = flags; } /// /// Constructor /// /// The token identifier /// Flags for this token public Token(Identifier identifier, TokenFlags flags) { Type = TokenType.Identifier; Flags = flags; Identifier = identifier; } /// /// Constructs a literal token /// public Token(TokenType type, TokenFlags flags, byte[] literal) : this(type, flags) { Debug.Assert(IsLiteral(type)); Literal = literal; } /// /// Constructs a literal token /// public Token(TokenType type, TokenFlags flags, string literal) : this(type, flags) { Debug.Assert(IsLiteral(type)); Literal = Encoding.UTF8.GetBytes(literal); } /// /// Reads a token from a binary archive /// /// Archive to read from public Token(BinaryArchiveReader reader) { Type = (TokenType)reader.ReadByte(); Flags = (TokenFlags)reader.ReadByte(); if (Type == TokenType.Identifier) { Identifier = reader.ReadIdentifier(); } else if (IsLiteral(Type)) { Literal = reader.ReadByteArray(); } } /// /// Writes a token to a binary archive /// /// The writer to serialize to public void Write(BinaryArchiveWriter writer) { writer.WriteByte((byte)Type); writer.WriteByte((byte)Flags); if (Type == TokenType.Identifier) { writer.WriteIdentifier(Identifier!); } else if (IsLiteral(Type)) { writer.WriteByteArray(Literal); } } /// /// Checks if a token is equal to another object /// /// The object to compare against /// True if the objects are equal, false otherwise public override bool Equals(object? other) { return Equals(other as Token); } /// /// Checks if two tokens are equivalent /// /// The object to compare against /// True if the tokens are equal, false otherwise public bool Equals(Token? other) { if (other is null) { return false; } if (Type != other.Type || Flags != other.Flags || Identifier != other.Identifier) { return false; } if (Literal != null) { if (other.Literal == null || Literal.Length != other.Literal.Length || !Enumerable.SequenceEqual(Literal, other.Literal)) { return false; } } else { if (other.Literal != null) { return false; } } return true; } /// /// Compares two tokens for equality /// /// The first token to compare /// The second token to compare /// True if the objects are equal, false otherwise public static bool operator ==(Token lhs, Token rhs) { if (lhs is null) { return (rhs is null); } else { return lhs.Equals(rhs); } } /// /// Compares two tokens for inequality /// /// The first token to compare /// The second token to compare /// True if the objects are not equal, false otherwise public static bool operator !=(Token lhs, Token rhs) { return !(lhs == rhs); } /// /// Gets a hash code for this object /// /// Hash code for the object public override int GetHashCode() { int result = (int)Type + (int)Flags * 7; if (Identifier != null) { result = (result * 11) + Identifier.GetHashCode(); } if (Literal != null) { for (int idx = 0; idx < Literal.Length; idx++) { result = (result * 13) + Literal[idx]; } } return base.GetHashCode(); } /// /// Text corresponding to this token /// public string Text { get { if (Identifier != null) { return Identifier.ToString(); } else if (Literal != null) { return Encoding.UTF8.GetString(Literal); } else { return s_staticTokens[(int)Type]; } } } /// /// Returns a new token with different flags /// /// Flags to add from the token /// New token with updated flags public Token AddFlags(TokenFlags flagsToAdd) { if (Identifier != null) { return new Token(Identifier, Flags | flagsToAdd); } else if (Literal != null) { return new Token(Type, Flags | flagsToAdd, Literal); } else { return new Token(Type, Flags | flagsToAdd); } } /// /// Returns a new token with different flags /// /// Flags to remove from the token /// New token with updated flags public Token RemoveFlags(TokenFlags flagsToRemove) { if (Identifier != null) { return new Token(Identifier, Flags & ~flagsToRemove); } else if (Literal != null) { return new Token(Type, Flags & ~flagsToRemove, Literal); } else { return new Token(Type, Flags & ~flagsToRemove); } } /// /// Accessor for whether this token has leading whitespace /// public bool HasLeadingSpace => (Flags & TokenFlags.HasLeadingSpace) != 0; /// /// Checks whether two tokens match /// /// The token to compare against /// True if the tokens match, false otherwise public bool Matches(Token other) { if (Type != other.Type) { return false; } if (Literal == null) { return Identifier == other.Identifier; } else { return other.Literal != null && Enumerable.SequenceEqual(Literal, other.Literal); } } /// /// Determines whether the given token type is a literal /// /// The type to test /// True if the given type is a literal public static bool IsLiteral(TokenType type) { return type == TokenType.Unknown || type == TokenType.Character || type == TokenType.String || type == TokenType.Number || type == TokenType.StringOfTokens || type == TokenType.SystemInclude; } /// /// Concatenate a sequence of tokens into a string /// /// The sequence of tokens to concatenate /// String containing the concatenated tokens public static string Format(IEnumerable tokens) { StringBuilder result = new(); Format(tokens, result); return result.ToString(); } /// /// Concatenate a sequence of tokens into a string /// /// The sequence of tokens to concatenate /// Receives the formatted string public static void Format(IEnumerable tokens, StringBuilder result) { IEnumerator enumerator = tokens.GetEnumerator(); if (enumerator.MoveNext()) { result.Append(enumerator.Current.Text); Token lastToken = enumerator.Current; while (enumerator.MoveNext()) { Token token = enumerator.Current; if (token.HasLeadingSpace && (token.Type != TokenType.LeftParen || lastToken.Type != TokenType.Identifier || lastToken.Identifier != Identifiers.__pragma)) { result.Append(' '); } result.Append(token.Text); lastToken = token; } } } /// /// Concatenate two tokens together /// /// The first token /// The second token /// Current preprocessor context /// The combined token [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "")] public static Token Concatenate(Token firstToken, Token secondToken, PreprocessorContext context) { string text = firstToken.Text.ToString() + secondToken.Text.ToString(); List tokens = new(); TokenReader reader = new(text); while (reader.MoveNext()) { tokens.Add(reader.Current); } if (tokens.Count == 0) { return new Token(TokenType.Placemarker, TokenFlags.None); } else if (tokens.Count == 1) { return tokens[0]; } else { return new Token(TokenType.Unknown, TokenFlags.None, text); } } /// /// Static constructor. Initializes the StaticTokens array. /// static Token() { s_staticTokens = new string[(int)TokenType.Max]; s_staticTokens[(int)TokenType.LeftParen] = "("; s_staticTokens[(int)TokenType.RightParen] = ")"; s_staticTokens[(int)TokenType.Comma] = ","; s_staticTokens[(int)TokenType.Newline] = "\n"; s_staticTokens[(int)TokenType.Ellipsis] = "..."; s_staticTokens[(int)TokenType.Placemarker] = ""; s_staticTokens[(int)TokenType.Dot] = "."; s_staticTokens[(int)TokenType.QuestionMark] = "?"; s_staticTokens[(int)TokenType.Colon] = ":"; s_staticTokens[(int)TokenType.LogicalNot] = "!"; s_staticTokens[(int)TokenType.LogicalAnd] = "&&"; s_staticTokens[(int)TokenType.LogicalOr] = "||"; s_staticTokens[(int)TokenType.BitwiseXor] = "^"; s_staticTokens[(int)TokenType.BitwiseAnd] = "&"; s_staticTokens[(int)TokenType.BitwiseNot] = "~"; s_staticTokens[(int)TokenType.BitwiseOr] = "|"; s_staticTokens[(int)TokenType.Equals] = "="; s_staticTokens[(int)TokenType.LeftShift] = "<<"; s_staticTokens[(int)TokenType.RightShift] = ">>"; s_staticTokens[(int)TokenType.CompareEqual] = "=="; s_staticTokens[(int)TokenType.CompareNotEqual] = "!="; s_staticTokens[(int)TokenType.CompareLessOrEqual] = "<="; s_staticTokens[(int)TokenType.CompareLess] = "<"; s_staticTokens[(int)TokenType.CompareGreaterOrEqual] = ">="; s_staticTokens[(int)TokenType.CompareGreater] = ">"; s_staticTokens[(int)TokenType.Plus] = "+"; s_staticTokens[(int)TokenType.Minus] = "-"; s_staticTokens[(int)TokenType.Multiply] = "*"; s_staticTokens[(int)TokenType.Divide] = "/"; s_staticTokens[(int)TokenType.Modulo] = "%"; s_staticTokens[(int)TokenType.Hash] = "#"; s_staticTokens[(int)TokenType.HashHash] = "##"; } } /// /// Helper functions for serialization /// static class TokenExtensionMethods { /// /// Read a token from a binary archive /// /// Reader to serialize data from /// Token read from the archive public static Token ReadToken(this BinaryArchiveReader reader) { return new Token(reader); } /// /// Write a token to a binary archive /// /// Writer to serialize data to /// Token to write public static void WriteToken(this BinaryArchiveWriter writer, Token token) { token.Write(writer); } } }