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