// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Text; using EpicGames.Core; namespace EpicGames.UHT.Utils { /// /// Collection of helper methods to emulate UHT string functions /// public static class UhtFCString { /// /// Delimiter between the subobjects /// public static readonly char SubObjectDelimiter = ':'; /// /// Test to see if the string is a boolean /// /// Boolean to test /// True if the value is true public static bool ToBool(StringView value) { ReadOnlySpan span = value.Span; if (span.Length == 0) { return false; } else if ( span.Equals("true", StringComparison.OrdinalIgnoreCase) || span.Equals("yes", StringComparison.OrdinalIgnoreCase) || span.Equals("on", StringComparison.OrdinalIgnoreCase)) { return true; } else if ( span.Equals("false", StringComparison.OrdinalIgnoreCase) || span.Equals("no", StringComparison.OrdinalIgnoreCase) || span.Equals("off", StringComparison.OrdinalIgnoreCase)) { return false; } else { return Int32.TryParse(span, out int intValue) && intValue != 0; } } /// /// Test to see if the string is a boolean /// /// Boolean to test /// True if the value is true public static bool ToBool(string? value) { if (String.IsNullOrEmpty(value)) { return false; } else if (String.Equals(value, "true", StringComparison.OrdinalIgnoreCase) || String.Equals(value, "yes", StringComparison.OrdinalIgnoreCase) || String.Equals(value, "on", StringComparison.OrdinalIgnoreCase)) { return true; } else if (String.Equals(value, "false", StringComparison.OrdinalIgnoreCase) || String.Equals(value, "no", StringComparison.OrdinalIgnoreCase) || String.Equals(value, "off", StringComparison.OrdinalIgnoreCase)) { return false; } else { return Int32.TryParse(value, out int intValue) && intValue != 0; } } /// /// Test to see if the character is a digit /// /// Character to test /// True if it is public static bool IsDigit(char c) { return c >= '0' && c <= '9'; } /// /// Test to see if the character is an alphabet character /// /// Character to test /// True if it is public static bool IsAlpha(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } /// /// Test to see if the character is a digit or alphabet character /// /// Character to test /// True if it is public static bool IsAlnum(char c) { return IsDigit(c) || IsAlpha(c); } /// /// Test to see if the character is a hex digit (A-F) /// /// Character to test /// True if it is public static bool IsHexAlphaDigit(char c) { return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } /// /// Test to see if the character is a hex digit (0-9a-f) /// /// Character to test /// True if it is public static bool IsHexDigit(char c) { return IsDigit(c) || IsHexAlphaDigit(c); } /// /// Test to see if the character is whitespace /// /// Character to test /// True if it is public static bool IsWhitespace(char c) { return Char.IsWhiteSpace(c); } /// /// Test to see if the character is a sign /// /// Character to test /// True if it is public static bool IsSign(char c) { return c == '+' || c == '-'; } /// /// Test to see if the character is a hex marker (xX) /// /// Character to test /// True if it is public static bool IsHexMarker(char c) { return c == 'x' || c == 'X'; } /// /// Test to see if the character is a float marker (fF) /// /// Character to test /// True if it is public static bool IsFloatMarker(char c) { return c == 'f' || c == 'F'; } /// /// Test to see if the character is an exponent marker (eE) /// /// Character to test /// True if it is public static bool IsExponentMarker(char c) { return c == 'e' || c == 'E'; } /// /// Test to see if the character is an unsigned marker (uU) /// /// Character to test /// True if it is public static bool IsUnsignedMarker(char c) { return c == 'u' || c == 'U'; } /// /// Test to see if the character is a long marker (lL) /// /// Character to test /// True if it is public static bool IsLongMarker(char c) { return c == 'l' || c == 'L'; } /// /// Test to see if the span is a numeric value /// /// Span to test /// True if it is public static bool IsNumeric(ReadOnlySpan span) { int index = 0; char c = span[index]; if (c == '-' || c == '+') { ++index; } bool hasDot = false; for (; index < span.Length; ++index) { c = span[index]; if (c == '.') { if (hasDot) { return false; } hasDot = true; } else if (!IsDigit(c)) { return false; } } return true; } /// /// Test to see if the character is a linebreak character /// /// Character to test /// True if it is public static bool IsLinebreak(char c) { return (c >= 0x0a && c <= 0x0d) || c == 0x85 || c == 0x2028 || c == 0x2029; } /// /// Return an unescaped string /// /// Text to unescape /// Resulting string public static string UnescapeText(string text) { StringBuilder result = new(); for (int idx = 0; idx < text.Length; idx++) { if (text[idx] == '\\' && idx + 1 < text.Length) { switch (text[++idx]) { case 't': result.Append('\t'); break; case 'r': result.Append('\r'); break; case 'n': result.Append('\n'); break; case '\'': result.Append('\''); break; case '\"': result.Append('\"'); break; case 'u': bool parsed = false; if (idx + 4 < text.Length) { if (Int32.TryParse(text.Substring(idx + 1, 4), System.Globalization.NumberStyles.HexNumber, null, out int value)) { parsed = true; result.Append((char)value); idx += 4; } } if (!parsed) { result.Append('\\'); result.Append('u'); } break; default: result.Append(text[idx]); break; } } else { result.Append(text[idx]); } } return result.ToString(); } /// /// Replace tabs to spaces in a string containing only a single line. /// /// Input string /// Number of spaces to exchange for tabs /// Due to a bug in UE ConvertTabsToSpacesInline, any \n is considered part of the line length. /// Resulting string or the original string if the string didn't contain any spaces. public static ReadOnlyMemory TabsToSpaces(ReadOnlyMemory input, int tabSpacing, bool emulateCrBug) { // If we have any tab characters, then we need to convert them to spaces int tabIndex = input.Span.IndexOf('\t'); if (tabIndex == -1) { return input; } using BorrowStringBuilder borrower = new(StringBuilderCache.Small); StringBuilder builder = borrower.StringBuilder; TabsToSpaces(input.Span, tabSpacing, emulateCrBug, tabIndex, builder); return builder.ToString().AsMemory(); } /// /// Replace tabs to spaces in a string containing zero or more lines. /// /// Input string to convert /// Number of spaces to exchange for tabs /// Due to a bug in UE ConvertTabsToSpacesInline, any \n is considered part of the line length. /// Initial tab index /// Destination string builder public static void TabsToSpaces(ReadOnlySpan span, int tabSpacing, bool emulateCrBug, int tabIndex, StringBuilder builder) { // Locate the last \n since all tabs have to be computed relative to int crPos = span[..tabIndex].LastIndexOf('\n') + 1; int committed = 0; do { // Commit everything prior to the tab to the builder builder.Append(span[..tabIndex]); span = span[(tabIndex + 1)..]; committed += tabIndex; // Add the appropriate number of spaces int adjustedCrPos = crPos; if (emulateCrBug && adjustedCrPos > 0) { --adjustedCrPos; } int spacesToInsert = tabSpacing - (committed - adjustedCrPos) % tabSpacing; builder.AppendSpaces(spacesToInsert); committed += spacesToInsert; // Search for the next \t or \n for (tabIndex = 0; tabIndex < span.Length; ++tabIndex) { if (span[tabIndex] == '\n') { crPos = committed + tabIndex + 1; } else if (span[tabIndex] == '\t') { break; } } } while (tabIndex < span.Length); // Commit the remaining data builder.Append(span); } } }