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