// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using EpicGames.Core;
using EpicGames.UHT.Utils;
namespace EpicGames.UHT.Tokenizer
{
///
/// Type of the token
///
public enum UhtTokenType
{
///
/// End of file token.
///
EndOfFile,
///
/// End of default value.
///
EndOfDefault,
///
/// End of type
///
EndOfType,
///
/// End of declaration
///
EndOfDeclaration,
///
/// Line of text (when calling GetLine only)
///
Line,
///
/// Alphanumeric identifier.
///
Identifier,
///
/// Symbol.
///
Symbol,
///
/// Floating point constant
///
FloatConst,
///
/// Decimal Integer constant
///
DecimalConst,
///
/// Hex integer constant
///
HexConst,
///
/// Single character constant
///
CharConst,
///
/// String constant
///
StringConst,
}
///
/// Series of extension methods for the token type
///
public static class UhtTokenTypeExtensions
{
///
/// Return true if the token type is an end type
///
/// Token type in question
/// True if the token type is an end type
public static bool IsEndType(this UhtTokenType tokenType)
{
switch (tokenType)
{
case UhtTokenType.EndOfFile:
case UhtTokenType.EndOfDefault:
case UhtTokenType.EndOfType:
case UhtTokenType.EndOfDeclaration:
return true;
default:
return false;
}
}
}
///
/// Token declaration
///
public struct UhtToken
{
///
/// Names/Identifiers can not be longer that the following
///
public const int MaxNameLength = 1024;
///
/// Strings can not be longer than the following.
///
public const int MaxStringLength = 1024;
///
/// Type of the token
///
public UhtTokenType TokenType { get; set; }
///
/// Position to restore the reader
///
public int UngetPos { get; set; }
///
/// Line to restore the reader
///
public int UngetLine { get; set; }
///
/// Starting position of the token value
///
public int InputStartPos { get; set; }
///
/// End position of the token value
///
public readonly int InputEndPos => InputStartPos + Value.Span.Length;
///
/// Line containing the token
///
public int InputLine { get; set; }
///
/// Token value
///
public StringView Value { get; set; }
///
/// Construct a new token
///
/// Type of the token
public UhtToken(UhtTokenType tokenType)
{
TokenType = tokenType;
UngetPos = 0;
UngetLine = 0;
InputStartPos = 0;
InputLine = 0;
Value = new StringView();
}
///
/// Construct a new token
///
/// Type of token
/// Unget position
/// Unget line
/// Start position of value
/// Line of value
/// Token value
public UhtToken(UhtTokenType tokenType, int ungetPos, int ungetLine, int inputStartPos, int inputLine, StringView value)
{
TokenType = tokenType;
UngetPos = ungetPos;
UngetLine = ungetLine;
InputStartPos = inputStartPos;
InputLine = inputLine;
Value = value;
}
///
/// True if the token isn't an end token
///
/// Token in question
public static implicit operator bool(UhtToken token)
{
return !token.IsEndType();
}
///
/// Return true if the token is an end token
///
/// True if the token is an end token
public readonly bool IsEndType()
{
return TokenType.IsEndType();
}
///
/// Test to see if the value matches the given character
///
/// Value to test
/// True if the token value matches the given value
public readonly bool IsValue(char value)
{
return Value.Span.Length == 1 && Value.Span[0] == value;
}
///
/// Test to see if the value matches the given string
///
/// Value to test
/// If true, ignore case
/// True if the value matches
public readonly bool IsValue(string value, bool ignoreCase = false)
{
return Value.Span.Equals(value, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
}
///
/// Test to see if the value matches the given string
///
/// Value to test
/// If true, ignore case
/// True if the value matches
public readonly bool IsValue(StringView value, bool ignoreCase = false)
{
return Value.Span.Equals(value.Span, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
}
///
/// Test to see if the value starts with the given string
///
/// Value to test
/// If true, ignore case
/// True is the value starts with the given string
public readonly bool ValueStartsWith(string value, bool ignoreCase = false)
{
return Value.Span.StartsWith(value, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
}
///
/// Return true if the token is an identifier
///
/// True if the token is an identifier
public readonly bool IsIdentifier()
{
return TokenType == UhtTokenType.Identifier;
}
///
/// Return true if the identifier matches
///
/// Identifier to test
/// If true, ignore case
/// True if the identifier matches
public readonly bool IsIdentifier(string identifier, bool ignoreCase = false)
{
return IsIdentifier() && IsValue(identifier, ignoreCase);
}
///
/// Return true if the identifier matches
///
/// Identifier to test
/// If true, ignore case
/// True if the identifier matches
public readonly bool IsIdentifier(StringView identifier, bool ignoreCase = false)
{
return IsIdentifier() && IsValue(identifier, ignoreCase);
}
///
/// Return true if the token is a symbol
///
/// True if the token is a symbol
public readonly bool IsSymbol()
{
return TokenType == UhtTokenType.Symbol;
}
///
/// Return true if the symbol matches
///
/// Symbol to test
/// True if the symbol matches
public readonly bool IsSymbol(char symbol)
{
return IsSymbol() && IsValue(symbol);
}
///
/// Return true if the symbol matches
///
/// Symbol to test
/// True if the symbol matches
public readonly bool IsSymbol(string symbol)
{
return IsSymbol() && IsValue(symbol);
}
///
/// Return true if the symbol matches
///
/// Symbol to test
/// True if the symbol matches
public readonly bool IsSymbol(StringView symbol)
{
return IsSymbol() && IsValue(symbol);
}
///
/// Return true if the token is a constant integer
///
/// True if constant integer
public readonly bool IsConstInt()
{
return TokenType == UhtTokenType.DecimalConst || TokenType == UhtTokenType.HexConst;
}
///
/// Return true if the token is a constant floag
///
/// True if constant float
public readonly bool IsConstFloat()
{
return TokenType == UhtTokenType.FloatConst;
}
///
/// Get the integer value of the token. Only supported for decimal, hexadecimal, and floating point values
///
/// Resulting value
/// True if the value was set
public readonly bool GetConstInt(out int value)
{
switch (TokenType)
{
case UhtTokenType.DecimalConst:
value = (int)GetDecimalValue();
return true;
case UhtTokenType.HexConst:
value = (int)GetHexValue();
return true;
case UhtTokenType.FloatConst:
{
float floatValue = GetFloatValue();
value = (int)floatValue;
return floatValue == value;
}
default:
value = 0;
return false;
}
}
///
/// Get the integer value of the token. Only supported for decimal, hexadecimal, and floating point values
///
/// Resulting value
/// True if the value was set
public readonly bool GetConstLong(out long value)
{
switch (TokenType)
{
case UhtTokenType.DecimalConst:
value = GetDecimalValue();
return true;
case UhtTokenType.HexConst:
value = GetHexValue();
return true;
case UhtTokenType.FloatConst:
{
float floatValue = GetFloatValue();
value = (long)floatValue;
return floatValue == value;
}
default:
value = 0;
return false;
}
}
///
/// Get the float value of the token. Only supported for decimal, hexadecimal, and floating point values
///
/// Resulting value
/// True if the value was set
public readonly bool GetConstFloat(out float value)
{
switch (TokenType)
{
case UhtTokenType.DecimalConst:
value = (float)GetDecimalValue();
return true;
case UhtTokenType.HexConst:
value = (float)GetHexValue();
return true;
case UhtTokenType.FloatConst:
value = GetFloatValue();
return true;
default:
value = 0;
return false;
}
}
///
/// Get the double value of the token. Only supported for decimal, hexadecimal, and floating point values
///
/// Resulting value
/// True if the value was set
public readonly bool GetConstDouble(out double value)
{
switch (TokenType)
{
case UhtTokenType.DecimalConst:
value = (double)GetDecimalValue();
return true;
case UhtTokenType.HexConst:
value = (double)GetHexValue();
return true;
case UhtTokenType.FloatConst:
value = GetDoubleValue();
return true;
default:
value = 0;
return false;
}
}
///
/// Return true if the token is a constant string (or a char constant)
///
/// True if the token is a string or character constant
public readonly bool IsConstString()
{
return TokenType == UhtTokenType.StringConst || TokenType == UhtTokenType.CharConst;
}
// Return the token value for string constants
///
/// Return an un-escaped string. The surrounding quotes will be removed and escaped characters will be converted to the actual values.
///
///
/// Resulting string
/// Thrown if the token type is not a string or character constant
public readonly StringView GetUnescapedString(IUhtMessageSite messageSite)
{
switch (TokenType)
{
case UhtTokenType.StringConst:
ReadOnlySpan span = Value.Span[1..^1];
int index = span.IndexOf('\\');
if (index == -1)
{
return new StringView(Value, 1, span.Length);
}
else
{
StringBuilder builder = new();
while (index >= 0)
{
builder.Append(span[..index]);
if (span[index + 1] == 'n')
{
builder.Append('\n');
}
span = span[(index + 1)..];
index = span.IndexOf('\\');
}
builder.Append(span);
return new StringView(builder.ToString());
}
case UhtTokenType.CharConst:
if (Value.Span[1] == '\\')
{
switch (Value.Span[2])
{
case 't':
return new StringView("\t");
case 'n':
return new StringView("\n");
case 'r':
return new StringView("\r");
default:
return new StringView(Value, 2, 1);
}
}
else
{
return new StringView(Value, 1, 1);
}
}
throw new UhtException(messageSite, InputLine, "Call to GetUnescapedString on token that isn't a string or char constant");
}
///
/// Return a string representation of the token value. This will convert numeric values and format them.
///
/// If true, embedded quotes will be respected
/// Resulting string
public readonly StringView GetConstantValue(bool respectQuotes = false)
{
switch (TokenType)
{
case UhtTokenType.DecimalConst:
return new StringView(GetDecimalValue().ToString(NumberFormatInfo.InvariantInfo));
case UhtTokenType.HexConst:
return new StringView(GetHexValue().ToString(NumberFormatInfo.InvariantInfo));
case UhtTokenType.FloatConst:
return new StringView(GetFloatValue().ToString("F6", NumberFormatInfo.InvariantInfo));
case UhtTokenType.CharConst:
case UhtTokenType.StringConst:
return GetTokenString(respectQuotes);
default:
return "NotConstant";
}
}
///
/// Return an un-escaped string. The surrounding quotes will be removed and escaped characters will be converted to the actual values.
///
/// If true, respect embedded quotes
/// Resulting string
public readonly StringView GetTokenString(bool respectQuotes = false)
{
StringViewBuilder builder = new();
switch (TokenType)
{
case UhtTokenType.StringConst:
StringView subValue = new(Value, 1, Value.Span.Length - 2);
while (subValue.Span.Length > 0)
{
int slashIndex = subValue.Span.IndexOf('\\');
if (slashIndex == -1)
{
builder.Append(subValue);
break;
}
if (slashIndex > 0)
{
builder.Append(new StringView(subValue, 0, slashIndex));
}
if (slashIndex + 1 == subValue.Span.Length)
{
break;
}
if (slashIndex + 1 < subValue.Span.Length)
{
char c = subValue.Span[slashIndex + 1];
if (c == 'n')
{
c = '\n';
}
else if (respectQuotes && c == '"')
{
builder.Append('\\');
}
builder.Append(c);
subValue = new StringView(subValue, slashIndex + 2);
}
}
break;
case UhtTokenType.CharConst:
char charConst = Value.Span[1];
if (charConst == '\\')
{
charConst = Value.Span[2];
switch (charConst)
{
case 't':
charConst = '\t';
break;
case 'n':
charConst = '\n';
break;
case 'r':
charConst = '\r';
break;
}
}
builder.Append(charConst);
break;
default:
throw new UhtIceException("Call to GetTokenString on a token that isn't a string or char constant");
}
return builder.ToStringView();
}
///
/// Join the given tokens into a string
///
/// Tokens to join
/// Joined strings
public static string Join(IEnumerable tokens)
{
StringBuilder builder = new();
foreach (UhtToken token in tokens)
{
builder.Append(token.Value.ToString());
}
return builder.ToString();
}
///
/// Join the given tokens into a string
///
/// Separator between tokens
/// Tokens to join
/// Joined strings
public static string Join(char separator, IEnumerable tokens)
{
StringBuilder builder = new();
bool includeSeparator = false;
foreach (UhtToken token in tokens)
{
if (!includeSeparator)
{
includeSeparator = true;
}
else
{
builder.Append(separator);
}
builder.Append(token.Value.ToString());
}
return builder.ToString();
}
///
/// Join the given tokens into a string
///
/// Separator between tokens
/// Tokens to join
/// Joined strings
public static string Join(string separator, IEnumerable tokens)
{
StringBuilder builder = new();
bool includeSeparator = false;
foreach (UhtToken token in tokens)
{
if (!includeSeparator)
{
includeSeparator = true;
}
else
{
builder.Append(separator);
}
builder.Append(token.Value.ToString());
}
return builder.ToString();
}
///
/// Convert the token to a string. This will be the value.
///
/// Value of the token
public override readonly string ToString()
{
if (IsEndType())
{
return "";
}
else
{
return Value.Span.ToString();
}
}
private const NumberStyles DefaultNumberStyles = NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite |
NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowExponent;
private readonly float GetFloatValue()
{
if (Value.Span.Length > 0)
{
if (UhtFCString.IsFloatMarker(Value.Span[^1]))
{
return Single.Parse(Value.Span[0..^1], DefaultNumberStyles, CultureInfo.InvariantCulture);
}
}
return Single.Parse(Value.Span, DefaultNumberStyles, CultureInfo.InvariantCulture);
}
private readonly double GetDoubleValue()
{
if (Value.Span.Length > 0)
{
if (UhtFCString.IsFloatMarker(Value.Span[^1]))
{
return Double.Parse(Value.Span[0..^1], DefaultNumberStyles, CultureInfo.InvariantCulture);
}
}
return Double.Parse(Value.Span, DefaultNumberStyles, CultureInfo.InvariantCulture);
}
readonly long GetDecimalValue()
{
ReadOnlySpan span = Value.Span;
bool isUnsigned = false;
while (span.Length > 0)
{
char c = span[^1];
if (UhtFCString.IsLongMarker(c))
{
span = span[0..^1];
}
else if (UhtFCString.IsUnsignedMarker(c))
{
isUnsigned = true;
span = span[0..^1];
}
else
{
break;
}
}
return isUnsigned ? (long)Convert.ToUInt64(span.ToString(), 10) : Convert.ToInt64(span.ToString(), 10);
}
readonly long GetHexValue()
{
return Convert.ToInt64(Value.ToString(), 16);
}
}
}