864 lines
25 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|