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