Files
ft/Client/Packages/io.github.hatayama.uloopmcp/Editor/Compilation/DynamicCodeLiteralHoister.cs

864 lines
25 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace io.github.hatayama.uLoopMCP
{
internal static class DynamicCodeLiteralHoister
{
private const string LiteralParameterPrefix = "__uloop_literal_";
public static HoistedLiteralRewriteResult Rewrite(string source)
{
StringBuilder rewrittenSource = new StringBuilder(source.Length);
List<HoistedLiteralBinding> bindings = new List<HoistedLiteralBinding>();
int index = 0;
while (index < source.Length)
{
if (TryCopyInterpolatedStringLiteral(source, rewrittenSource, ref index))
{
continue;
}
if (TryCopyVerbatimStringLiteral(source, rewrittenSource, ref index))
{
continue;
}
if (TryCopyCharLiteral(source, rewrittenSource, ref index))
{
continue;
}
if (TryCopyLineComment(source, rewrittenSource, ref index))
{
continue;
}
if (TryCopyBlockComment(source, rewrittenSource, ref index))
{
continue;
}
if (TryHoistRegularStringLiteral(source, rewrittenSource, bindings, ref index))
{
continue;
}
if (TryHoistIntegerLiteral(source, rewrittenSource, bindings, ref index))
{
continue;
}
rewrittenSource.Append(source[index]);
index++;
}
List<string> declarationLines = new List<string>();
foreach (HoistedLiteralBinding binding in bindings)
{
declarationLines.Add(
$"{binding.TypeName} {binding.ParameterName} = ({binding.TypeName})parameters[\"{binding.ParameterName}\"];");
}
return new HoistedLiteralRewriteResult(
rewrittenSource.ToString(),
bindings,
declarationLines);
}
private static bool TryCopyInterpolatedStringLiteral(
string source,
StringBuilder rewrittenSource,
ref int index)
{
int start = index;
if (!TryAdvanceInterpolatedStringLiteral(source, ref index))
{
return false;
}
rewrittenSource.Append(source, start, index - start);
return true;
}
private static bool TryCopyLineComment(
string source,
StringBuilder rewrittenSource,
ref int index)
{
if (index + 1 >= source.Length || source[index] != '/' || source[index + 1] != '/')
{
return false;
}
while (index < source.Length)
{
char current = source[index];
rewrittenSource.Append(current);
index++;
if (current == '\n')
{
break;
}
}
return true;
}
private static bool TryCopyBlockComment(
string source,
StringBuilder rewrittenSource,
ref int index)
{
if (index + 1 >= source.Length || source[index] != '/' || source[index + 1] != '*')
{
return false;
}
rewrittenSource.Append(source[index]);
rewrittenSource.Append(source[index + 1]);
index += 2;
while (index < source.Length)
{
char current = source[index];
rewrittenSource.Append(current);
index++;
if (current == '*' && index < source.Length && source[index] == '/')
{
rewrittenSource.Append('/');
index++;
break;
}
}
return true;
}
private static bool TryCopyVerbatimStringLiteral(
string source,
StringBuilder rewrittenSource,
ref int index)
{
if (source[index] != '@'
|| index + 1 >= source.Length
|| source[index + 1] != '"')
{
return false;
}
int start = index;
index += 2;
while (index < source.Length)
{
if (source[index] != '"')
{
index++;
continue;
}
if (index + 1 < source.Length && source[index + 1] == '"')
{
index += 2;
continue;
}
index++;
rewrittenSource.Append(source, start, index - start);
return true;
}
index = start;
return false;
}
private static bool TryAdvanceInterpolatedStringLiteral(string source, ref int index)
{
int start = index;
if (!TryMatchInterpolatedStringStart(source, ref index, out bool isVerbatim))
{
return false;
}
int interpolationDepth = 0;
while (index < source.Length)
{
if (interpolationDepth > 0)
{
if (TryAdvanceInterpolatedExpressionToken(source, ref index))
{
continue;
}
char expressionCharacter = source[index];
if (expressionCharacter == '{')
{
interpolationDepth++;
index++;
continue;
}
if (expressionCharacter == '}')
{
interpolationDepth--;
index++;
continue;
}
index++;
continue;
}
char current = source[index];
if (current == '{')
{
if (index + 1 < source.Length && source[index + 1] == '{')
{
index += 2;
continue;
}
interpolationDepth = 1;
index++;
continue;
}
if (current == '}')
{
if (index + 1 < source.Length && source[index + 1] == '}')
{
index += 2;
continue;
}
index++;
continue;
}
if (!isVerbatim && current == '\\')
{
AdvanceEscapedLiteralSequence(source, ref index);
continue;
}
if (current == '"')
{
if (isVerbatim && index + 1 < source.Length && source[index + 1] == '"')
{
index += 2;
continue;
}
index++;
return true;
}
index++;
}
index = start;
return false;
}
private static bool TryMatchInterpolatedStringStart(
string source,
ref int index,
out bool isVerbatim)
{
isVerbatim = false;
if (source[index] == '$')
{
if (index + 1 < source.Length && source[index + 1] == '"')
{
index += 2;
return true;
}
if (index + 2 < source.Length && source[index + 1] == '@' && source[index + 2] == '"')
{
isVerbatim = true;
index += 3;
return true;
}
return false;
}
if (source[index] != '@')
{
return false;
}
if (index + 2 < source.Length && source[index + 1] == '$' && source[index + 2] == '"')
{
isVerbatim = true;
index += 3;
return true;
}
return false;
}
private static bool TryAdvanceInterpolatedExpressionToken(string source, ref int index)
{
if (TryAdvanceInterpolatedStringLiteral(source, ref index))
{
return true;
}
if (TryAdvanceVerbatimStringLiteral(source, ref index))
{
return true;
}
if (TryAdvanceRegularStringLiteral(source, ref index))
{
return true;
}
if (TryAdvanceCharLiteral(source, ref index))
{
return true;
}
if (TryAdvanceLineComment(source, ref index))
{
return true;
}
if (TryAdvanceBlockComment(source, ref index))
{
return true;
}
return false;
}
private static bool TryAdvanceLineComment(string source, ref int index)
{
if (index + 1 >= source.Length || source[index] != '/' || source[index + 1] != '/')
{
return false;
}
index += 2;
while (index < source.Length && source[index] != '\n')
{
index++;
}
if (index < source.Length)
{
index++;
}
return true;
}
private static bool TryAdvanceBlockComment(string source, ref int index)
{
if (index + 1 >= source.Length || source[index] != '/' || source[index + 1] != '*')
{
return false;
}
index += 2;
while (index < source.Length)
{
if (source[index] == '*' && index + 1 < source.Length && source[index + 1] == '/')
{
index += 2;
return true;
}
index++;
}
return true;
}
private static bool TryAdvanceVerbatimStringLiteral(string source, ref int index)
{
if (source[index] != '@'
|| index + 1 >= source.Length
|| source[index + 1] != '"')
{
return false;
}
int start = index;
index += 2;
while (index < source.Length)
{
if (source[index] != '"')
{
index++;
continue;
}
if (index + 1 < source.Length && source[index + 1] == '"')
{
index += 2;
continue;
}
index++;
return true;
}
index = start;
return false;
}
private static bool TryAdvanceRegularStringLiteral(string source, ref int index)
{
if (source[index] != '"')
{
return false;
}
if (index > 0 && (source[index - 1] == '@' || source[index - 1] == '$'))
{
return false;
}
int start = index;
index++;
while (index < source.Length)
{
char current = source[index];
if (current == '\\')
{
AdvanceEscapedLiteralSequence(source, ref index);
continue;
}
if (current == '"')
{
index++;
return true;
}
index++;
}
index = start;
return false;
}
private static bool TryAdvanceCharLiteral(string source, ref int index)
{
if (source[index] != '\'')
{
return false;
}
int start = index;
index++;
while (index < source.Length)
{
char current = source[index];
if (current == '\\')
{
AdvanceEscapedLiteralSequence(source, ref index);
continue;
}
if (current == '\'')
{
index++;
return true;
}
index++;
}
index = start;
return false;
}
private static bool TryCopyCharLiteral(
string source,
StringBuilder rewrittenSource,
ref int index)
{
if (source[index] != '\'')
{
return false;
}
int start = index;
index++;
while (index < source.Length)
{
char current = source[index];
if (current == '\\')
{
AdvanceEscapedLiteralSequence(source, ref index);
continue;
}
if (current == '\'')
{
index++;
rewrittenSource.Append(source, start, index - start);
return true;
}
index++;
}
index = start;
return false;
}
private static bool TryHoistRegularStringLiteral(
string source,
StringBuilder rewrittenSource,
List<HoistedLiteralBinding> bindings,
ref int index)
{
if (source[index] != '"')
{
return false;
}
if (index > 0 && (source[index - 1] == '@' || source[index - 1] == '$'))
{
return false;
}
int start = index;
index++;
while (index < source.Length)
{
char current = source[index];
if (current == '\\')
{
AdvanceEscapedLiteralSequence(source, ref index);
continue;
}
if (current == '"')
{
index++;
string literalToken = source.Substring(start, index - start);
if (!TryUnescapeRegularStringLiteral(literalToken, out string value))
{
rewrittenSource.Append(literalToken);
return true;
}
string parameterName = CreateParameterName(bindings.Count);
bindings.Add(new HoistedLiteralBinding(parameterName, "string", value));
rewrittenSource.Append(parameterName);
return true;
}
index++;
}
index = start;
return false;
}
private static bool TryHoistIntegerLiteral(
string source,
StringBuilder rewrittenSource,
List<HoistedLiteralBinding> bindings,
ref int index)
{
char current = source[index];
if (!char.IsDigit(current))
{
return false;
}
if (index > 0)
{
char previous = source[index - 1];
if (char.IsLetterOrDigit(previous) || previous == '_' || previous == '.')
{
return false;
}
}
int start = index;
index++;
while (index < source.Length && char.IsDigit(source[index]))
{
index++;
}
if (index < source.Length && (source[index] == 'L' || source[index] == 'l'))
{
int suffixIndex = index;
index++;
if (!HasIntegerLiteralBoundary(source, index))
{
index = start;
return false;
}
string longToken = source.Substring(start, suffixIndex - start);
if (!long.TryParse(longToken, NumberStyles.None, CultureInfo.InvariantCulture, out long longValue))
{
index = start;
return false;
}
string longParameterName = CreateParameterName(bindings.Count);
bindings.Add(new HoistedLiteralBinding(longParameterName, "long", longValue));
rewrittenSource.Append(longParameterName);
return true;
}
if (!HasIntegerLiteralBoundary(source, index))
{
index = start;
return false;
}
string token = source.Substring(start, index - start);
if (!int.TryParse(token, NumberStyles.None, CultureInfo.InvariantCulture, out int intValue))
{
index = start;
return false;
}
string parameterName = CreateParameterName(bindings.Count);
bindings.Add(new HoistedLiteralBinding(parameterName, "int", intValue));
rewrittenSource.Append(parameterName);
return true;
}
private static bool HasIntegerLiteralBoundary(string source, int index)
{
if (index >= source.Length)
{
return true;
}
char next = source[index];
return !char.IsLetter(next) && !char.IsDigit(next) && next != '_' && next != '.';
}
private static string CreateParameterName(int index)
{
return $"{LiteralParameterPrefix}{index}";
}
private static void AdvanceEscapedLiteralSequence(string source, ref int index)
{
index++;
if (index >= source.Length)
{
return;
}
char escape = source[index];
index++;
switch (escape)
{
case 'u':
index = Math.Min(source.Length, index + 4);
break;
case 'U':
index = Math.Min(source.Length, index + 8);
break;
case 'x':
index = AdvanceVariableLengthHexDigits(source, index, 4);
break;
}
}
private static int AdvanceVariableLengthHexDigits(string value, int index, int maxDigits)
{
int digitsConsumed = 0;
while (index < value.Length && digitsConsumed < maxDigits && IsHexDigit(value[index]))
{
index++;
digitsConsumed++;
}
return index;
}
private static bool TryUnescapeRegularStringLiteral(string token, out string value)
{
string inner = token.Substring(1, token.Length - 2);
StringBuilder unescaped = new StringBuilder(inner.Length);
for (int index = 0; index < inner.Length; index++)
{
char current = inner[index];
if (current != '\\')
{
unescaped.Append(current);
continue;
}
if (index + 1 >= inner.Length)
{
value = null;
return false;
}
index++;
char escape = inner[index];
switch (escape)
{
case '\'':
unescaped.Append('\'');
break;
case '"':
unescaped.Append('"');
break;
case '\\':
unescaped.Append('\\');
break;
case '0':
unescaped.Append('\0');
break;
case 'a':
unescaped.Append('\a');
break;
case 'b':
unescaped.Append('\b');
break;
case 'f':
unescaped.Append('\f');
break;
case 'n':
unescaped.Append('\n');
break;
case 'r':
unescaped.Append('\r');
break;
case 't':
unescaped.Append('\t');
break;
case 'v':
unescaped.Append('\v');
break;
case 'u':
if (!TryParseHexDigits(inner, ref index, 4, out int unicodeValue))
{
value = null;
return false;
}
unescaped.Append((char)unicodeValue);
break;
case 'U':
if (!TryParseHexDigits(inner, ref index, 8, out int codePoint) ||
!IsValidUtf32CodePoint(codePoint))
{
value = null;
return false;
}
unescaped.Append(char.ConvertFromUtf32(codePoint));
break;
case 'x':
if (!TryParseVariableLengthHexDigits(inner, ref index, out int variableLengthValue))
{
value = null;
return false;
}
unescaped.Append((char)variableLengthValue);
break;
default:
value = null;
return false;
}
}
value = unescaped.ToString();
return true;
}
private static bool TryParseHexDigits(string value, ref int index, int digitCount, out int parsedValue)
{
int start = index + 1;
if (start + digitCount > value.Length)
{
parsedValue = 0;
return false;
}
string hex = value.Substring(start, digitCount);
if (!int.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out parsedValue))
{
return false;
}
index += digitCount;
return true;
}
private static bool TryParseVariableLengthHexDigits(string value, ref int index, out int parsedValue)
{
int start = index + 1;
int end = AdvanceVariableLengthHexDigits(value, start, 4);
if (end == start)
{
parsedValue = 0;
return false;
}
string hex = value.Substring(start, end - start);
if (!int.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out parsedValue))
{
return false;
}
index = end - 1;
return true;
}
private static bool IsValidUtf32CodePoint(int value)
{
return value >= 0
&& value <= 0x10FFFF
&& (value < 0xD800 || value > 0xDFFF);
}
private static bool IsHexDigit(char value)
{
return (value >= '0' && value <= '9')
|| (value >= 'a' && value <= 'f')
|| (value >= 'A' && value <= 'F');
}
}
internal sealed class HoistedLiteralRewriteResult
{
public string RewrittenSource { get; }
public List<HoistedLiteralBinding> Bindings { get; }
public List<string> DeclarationLines { get; }
public HoistedLiteralRewriteResult(
string rewrittenSource,
List<HoistedLiteralBinding> bindings,
List<string> declarationLines)
{
RewrittenSource = rewrittenSource;
Bindings = bindings;
DeclarationLines = declarationLines;
}
}
}