// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 string[] StaticTokens;
///
/// Contains the type of the token
///
public TokenType Type
{
get;
}
///
/// Properties for this token
///
public TokenFlags Flags
{
get;
}
///
/// If this token is identifier, contains the identifier name
///
public Identifier? Identifier
{
get;
}
///
/// If this token is a literal, contains the raw utf-8 representation of it.
///
public byte[]? Literal
{
get;
}
///
/// Constructor
///
/// The token type
/// Flags for this token
public Token(TokenType Type, TokenFlags Flags)
{
this.Type = Type;
this.Flags = Flags;
}
///
/// Constructor
///
/// The token identifier
/// Flags for this token
public Token(Identifier Identifier, TokenFlags Flags)
{
this.Type = TokenType.Identifier;
this.Flags = Flags;
this.Identifier = Identifier;
}
///
/// Constructs a literal token
///
public Token(TokenType Type, TokenFlags Flags, byte[] Literal)
: this(Type, Flags)
{
Debug.Assert(IsLiteral(Type));
this.Literal = Literal;
}
///
/// Constructs a literal token
///
public Token(TokenType Type, TokenFlags Flags, string Literal)
: this(Type, Flags)
{
Debug.Assert(IsLiteral(Type));
this.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(ReferenceEquals(Other, 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 A, Token B)
{
if((object)A == null)
{
return ((object)B == null);
}
else
{
return A.Equals(B);
}
}
///
/// 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 A, Token B)
{
return !(A == B);
}
///
/// 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 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
{
get { return (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 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 StringBuilder();
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
public static Token Concatenate(Token FirstToken, Token SecondToken, PreprocessorContext Context)
{
string Text = FirstToken.Text.ToString() + SecondToken.Text.ToString();
List Tokens = new List();
TokenReader Reader = new TokenReader(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()
{
StaticTokens = new string[(int)TokenType.Max];
StaticTokens[(int)TokenType.LeftParen] = "(";
StaticTokens[(int)TokenType.RightParen] = ")";
StaticTokens[(int)TokenType.Comma] = ",";
StaticTokens[(int)TokenType.Newline] = "\n";
StaticTokens[(int)TokenType.Ellipsis] = "...";
StaticTokens[(int)TokenType.Placemarker] = "";
StaticTokens[(int)TokenType.Dot] = ".";
StaticTokens[(int)TokenType.QuestionMark] = "?";
StaticTokens[(int)TokenType.Colon] = ":";
StaticTokens[(int)TokenType.LogicalNot] = "!";
StaticTokens[(int)TokenType.LogicalAnd] = "&&";
StaticTokens[(int)TokenType.LogicalOr] = "||";
StaticTokens[(int)TokenType.BitwiseXor] = "^";
StaticTokens[(int)TokenType.BitwiseAnd] = "&";
StaticTokens[(int)TokenType.BitwiseNot] = "~";
StaticTokens[(int)TokenType.BitwiseOr] = "|";
StaticTokens[(int)TokenType.Equals] = "=";
StaticTokens[(int)TokenType.LeftShift] = "<<";
StaticTokens[(int)TokenType.RightShift] = ">>";
StaticTokens[(int)TokenType.CompareEqual] = "==";
StaticTokens[(int)TokenType.CompareNotEqual] = "!=";
StaticTokens[(int)TokenType.CompareLessOrEqual] = "<=";
StaticTokens[(int)TokenType.CompareLess] = "<";
StaticTokens[(int)TokenType.CompareGreaterOrEqual] = ">=";
StaticTokens[(int)TokenType.CompareGreater] = ">";
StaticTokens[(int)TokenType.Plus] = "+";
StaticTokens[(int)TokenType.Minus] = "-";
StaticTokens[(int)TokenType.Multiply] = "*";
StaticTokens[(int)TokenType.Divide] = "/";
StaticTokens[(int)TokenType.Modulo] = "%";
StaticTokens[(int)TokenType.Hash] = "#";
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);
}
}
}