upm_guru_kcp/Editor/protobuf-net.Reflection/TokenExtensions.cs

643 lines
24 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
namespace ProtoBuf.Reflection
{
internal static class TokenExtensions
{
public static bool Is(this Peekable<Token> tokens, TokenType type, string value = null)
{
Token val;
return tokens.Peek(out val) && val.Is(type, value);
}
public static void Consume(this Peekable<Token> tokens, TokenType type, string value)
{
var token = tokens.Read();
token.Assert(type, value);
tokens.Consume();
}
public static bool ConsumeIf(this Peekable<Token> tokens, TokenType type, string value)
{
Token token;
if (tokens.Peek(out token) && token.Is(type, value))
{
tokens.Consume();
return true;
}
return false;
}
public static Token Read(this Peekable<Token> tokens)
{
Token val;
if (!tokens.Peek(out val))
{
throw new ParserException(tokens.Previous, "Unexpected end of file", true);
}
return val;
}
public static bool SkipToEndOptions(this Peekable<Token> tokens)
{
Token token;
while (tokens.Peek(out token))
{
if (token.Is(TokenType.Symbol, ";") || token.Is(TokenType.Symbol, "}"))
return true; // but don't consume
tokens.Consume();
if (token.Is(TokenType.Symbol, "]"))
return true;
}
return false;
}
public static bool SkipToEndStatement(this Peekable<Token> tokens)
{
Token token;
while (tokens.Peek(out token))
{
if (token.Is(TokenType.Symbol, "}"))
return true; // but don't consume
tokens.Consume();
if (token.Is(TokenType.Symbol, ";"))
return true;
}
return false;
}
public static bool SkipToEndObject(this Peekable<Token> tokens) => SkipToSymbol(tokens, "}");
private static bool SkipToSymbol(this Peekable<Token> tokens, string symbol)
{
Token token;
while (tokens.Peek(out token))
{
tokens.Consume();
if (token.Is(TokenType.Symbol, symbol))
return true;
}
return false;
}
public static bool SkipToEndStatementOrObject(this Peekable<Token> tokens)
{
Token token;
while (tokens.Peek(out token))
{
tokens.Consume();
if (token.Is(TokenType.Symbol, "}") || token.Is(TokenType.Symbol, ";"))
return true;
}
return false;
}
public static string Consume(this Peekable<Token> tokens, TokenType type)
{
var token = tokens.Read();
token.Assert(type);
string s = token.Value;
tokens.Consume();
return s;
}
static class EnumCache<T>
{
private static readonly Dictionary<string, T> lookup;
public static bool TryGet(string name, out T value) => lookup.TryGetValue(name, out value);
static EnumCache()
{
var fields = typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public);
var tmp = new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
foreach (var field in fields)
{
string name = field.Name;
var attrib = (ProtoEnumAttribute)field.GetCustomAttributes(false).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(attrib?.Name)) name = attrib.Name;
var val = (T)field.GetValue(null);
tmp.Add(name, val);
}
lookup = tmp;
}
}
internal static T ConsumeEnum<T>(this Peekable<Token> tokens, bool ignoreCase = true) where T : struct
{
var token = tokens.Read();
var value = tokens.ConsumeString();
T val;
if (!EnumCache<T>.TryGet(token.Value, out val))
token.Throw("Unable to parse " + typeof(T).Name);
return val;
}
internal static bool TryParseUInt32(string token, out uint val, uint? max = null)
{
if (max.HasValue && token == "max")
{
val = max.GetValueOrDefault();
return true;
}
if (token.StartsWith("0x", StringComparison.OrdinalIgnoreCase) && uint.TryParse(token.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val))
{
return true;
}
return uint.TryParse(token, NumberStyles.Integer | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val);
}
internal static bool TryParseUInt64(string token, out ulong val, ulong? max = null)
{
if (max.HasValue && token == "max")
{
val = max.GetValueOrDefault();
return true;
}
if (token.StartsWith("0x", StringComparison.OrdinalIgnoreCase) && ulong.TryParse(token.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val))
{
return true;
}
return ulong.TryParse(token, NumberStyles.Integer | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val);
}
internal static bool TryParseInt32(string token, out int val, int? max = null)
{
if (max.HasValue && token == "max")
{
val = max.GetValueOrDefault();
return true;
}
if (token.StartsWith("-0x", StringComparison.OrdinalIgnoreCase) && int.TryParse(token.Substring(3), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val))
{
val = -val;
return true;
}
if (token.StartsWith("0x", StringComparison.OrdinalIgnoreCase) && int.TryParse(token.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val))
{
return true;
}
return int.TryParse(token, NumberStyles.Integer | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val);
}
internal static bool TryParseInt64(string token, out long val, long? max = null)
{
if (max.HasValue && token == "max")
{
val = max.GetValueOrDefault();
return true;
}
if (token.StartsWith("-0x", StringComparison.OrdinalIgnoreCase) && long.TryParse(token.Substring(3), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val))
{
val = -val;
return true;
}
if (token.StartsWith("0x", StringComparison.OrdinalIgnoreCase) && long.TryParse(token.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val))
{
return true;
}
return long.TryParse(token, NumberStyles.Integer | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val);
}
internal static int ConsumeInt32(this Peekable<Token> tokens, int? max = null)
{
var token = tokens.Read();
token.Assert(TokenType.AlphaNumeric);
tokens.Consume();
int val;
if (TryParseInt32(token.Value, out val, max)) return val;
throw token.Throw("Unable to parse integer");
}
internal static string ConsumeString(this Peekable<Token> tokens, bool asBytes = false)
{
var token = tokens.Read();
switch (token.Type)
{
case TokenType.StringLiteral:
MemoryStream ms = null;
do
{
ReadStringBytes(ref ms, token.Value);
tokens.Consume();
} while (tokens.Peek(out token) && token.Type == TokenType.StringLiteral); // literal concat is a thing
if (ms == null) return "";
if (!asBytes)
{
#if NETSTANDARD1_3
string s = ms.TryGetBuffer(out var segment)
? Encoding.UTF8.GetString(segment.Array, segment.Offset, segment.Count)
: Encoding.UTF8.GetString(ms.ToArray());
#else
string s = Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length);
#endif
return s.Replace("\\", @"\\")
.Replace("\'", @"\'")
.Replace("\"", @"\""")
.Replace("\r", @"\r")
.Replace("\n", @"\n")
.Replace("\t", @"\t");
}
var sb = new StringBuilder((int)ms.Length);
int b;
ms.Position = 0;
while ((b = ms.ReadByte()) >= 0)
{
switch (b)
{
case '\n': sb.Append(@"\n"); break;
case '\r': sb.Append(@"\r"); break;
case '\t': sb.Append(@"\t"); break;
case '\'': sb.Append(@"\'"); break;
case '\"': sb.Append(@"\"""); break;
case '\\': sb.Append(@"\\"); break;
default:
if (b >= 32 && b < 127)
{
sb.Append((char)b);
}
else
{
// encode as 3-part octal
sb.Append('\\')
.Append((char)(((b >> 6) & 7) + (int)'0'))
.Append((char)(((b >> 3) & 7) + (int)'0'))
.Append((char)(((b >> 0) & 7) + (int)'0'));
}
break;
}
}
return sb.ToString();
case TokenType.AlphaNumeric:
tokens.Consume();
return token.Value;
default:
throw token.Throw();
}
}
internal static void AppendAscii(MemoryStream target, string ascii)
{
foreach (char c in ascii)
target.WriteByte(checked((byte)c));
}
internal static void AppendByte(MemoryStream target, ref uint codePoint, ref int len)
{
if (len != 0)
{
target.WriteByte(checked((byte)codePoint));
}
codePoint = 0;
len = 0;
}
internal static unsafe void AppendNormalized(MemoryStream target, ref uint codePoint, ref int len)
{
if (len == 0)
{
codePoint = 0;
return;
}
byte* b = stackalloc byte[10];
char c = checked((char)codePoint);
int count = Encoding.UTF8.GetBytes(&c, 1, b, 10);
for (int i = 0; i < count; i++)
{
target.WriteByte(b[i]);
}
}
internal static void AppendEscaped(MemoryStream target, char c)
{
uint codePoint;
switch (c)
{
// encoded as octal
case 'a': codePoint = '\a'; break;
case 'b': codePoint = '\b'; break;
case 'f': codePoint = '\f'; break;
case 'v': codePoint = '\v'; break;
case 't': codePoint = '\t'; break;
case 'n': codePoint = '\n'; break;
case 'r': codePoint = '\r'; break;
case '\\':
case '?':
case '\'':
case '\"':
codePoint = c;
break;
default:
codePoint = '?';
break;
}
int len = 1;
AppendNormalized(target, ref codePoint, ref len);
}
internal static bool GetHexValue(char c, out uint val, ref int len)
{
len++;
if (c >= '0' && c <= '9')
{
val = (uint)c - (uint)'0';
return true;
}
if (c >= 'a' && c <= 'f')
{
val = 10 + (uint)c - (uint)'a';
return true;
}
if (c >= 'A' && c <= 'F')
{
val = 10 + (uint)c - (uint)'A';
return true;
}
len--;
val = 0;
return false;
}
// the normalized output *includes* the slashes, but expands octal to 3 places;
// it is the job of codegen to change this normalized form to the target language form
internal static void ReadStringBytes(ref MemoryStream ms, string value)
{
const int STATE_NORMAL = 0, STATE_ESCAPE = 1, STATE_OCTAL = 2, STATE_HEX = 3;
int state = STATE_NORMAL;
if (value == null || value.Length == 0) return;
if (ms == null) ms = new MemoryStream(value.Length);
uint escapedCodePoint = 0;
int escapeLength = 0;
foreach (char c in value)
{
switch (state)
{
case STATE_ESCAPE:
if (c >= '0' && c <= '7')
{
state = STATE_OCTAL;
GetHexValue(c, out escapedCodePoint, ref escapeLength); // not a typo; all 1-char octal values are also the same in hex
}
else if (c == 'x')
{
state = STATE_HEX;
}
else if (c == 'u' || c == 'U')
{
throw new NotSupportedException("Unicode escape points: on my todo list");
}
else
{
state = STATE_NORMAL;
AppendEscaped(ms, c);
}
break;
case STATE_OCTAL:
if (c >= '0' && c <= '7')
{
uint x;
GetHexValue(c, out x, ref escapeLength);
escapedCodePoint = (escapedCodePoint << 3) | x;
if (escapeLength == 3)
{
AppendByte(ms, ref escapedCodePoint, ref escapeLength);
state = STATE_NORMAL;
}
}
else
{
// not an octal char - regular append
if (escapeLength == 0)
{
// include the malformed \x
AppendAscii(ms, @"\x");
}
else
{
AppendByte(ms, ref escapedCodePoint, ref escapeLength);
}
state = STATE_NORMAL;
goto case STATE_NORMAL;
}
break;
case STATE_HEX:
{
uint x;
if (GetHexValue(c, out x, ref escapeLength))
{
escapedCodePoint = (escapedCodePoint << 4) | x;
if (escapeLength == 2)
{
AppendByte(ms, ref escapedCodePoint, ref escapeLength);
state = STATE_NORMAL;
}
}
else
{
// not a hex char - regular append
AppendByte(ms, ref escapedCodePoint, ref escapeLength);
state = STATE_NORMAL;
goto case STATE_NORMAL;
}
}
break;
case STATE_NORMAL:
if (c == '\\')
{
state = STATE_ESCAPE;
}
else
{
uint codePoint = (uint)c;
int len = 1;
AppendNormalized(ms, ref codePoint, ref len);
}
break;
default:
throw new InvalidOperationException();
}
}
// append any trailing escaped data
AppendByte(ms, ref escapedCodePoint, ref escapeLength);
}
internal static bool ConsumeBoolean(this Peekable<Token> tokens)
{
var token = tokens.Read();
token.Assert(TokenType.AlphaNumeric);
tokens.Consume();
if (string.Equals("true", token.Value, StringComparison.OrdinalIgnoreCase)) return true;
if (string.Equals("false", token.Value, StringComparison.OrdinalIgnoreCase)) return false;
throw token.Throw("Unable to parse boolean");
}
static TokenType Identify(char c)
{
if (c == '"' || c == '\'') return TokenType.StringLiteral;
if (char.IsWhiteSpace(c)) return TokenType.Whitespace;
if (char.IsLetterOrDigit(c)) return TokenType.AlphaNumeric;
switch (c)
{
case '_':
case '.':
case '-':
return TokenType.AlphaNumeric;
}
return TokenType.Symbol;
}
public static IEnumerable<Token> RemoveCommentsAndWhitespace(this IEnumerable<Token> tokens)
{
int commentLineNumber = -1;
bool isBlockComment = false;
foreach (var token in tokens)
{
if (isBlockComment)
{
// swallow everything until the end of the block comment
if (token.Is(TokenType.Symbol, "*/"))
isBlockComment = false;
}
else if (commentLineNumber == token.LineNumber)
{
// swallow everything else on that line
}
else if (token.Is(TokenType.Whitespace))
{
continue;
}
else if (token.Is(TokenType.Symbol, "//"))
{
commentLineNumber = token.LineNumber;
}
else if (token.Is(TokenType.Symbol, "/*"))
{
isBlockComment = true;
}
else
{
yield return token;
}
}
}
static bool CanCombine(TokenType type, int len, char prev, char next)
=> type != TokenType.Symbol
|| (len == 1 && prev == '/' && (next == '/' || next == '*'))
|| (len == 1 && prev == '*' && next == '/');
public static IEnumerable<Token> Tokenize(this TextReader reader, string file)
{
var buffer = new StringBuilder();
int lineNumber = 0, offset = 0;
string line;
string lastLine = null;
while ((line = reader.ReadLine()) != null)
{
lastLine = line;
lineNumber++;
int columnNumber = 0, tokenStart = 1;
char lastChar = '\0', stringType = '\0';
TokenType type = TokenType.None;
bool isEscaped = false;
foreach (char c in line)
{
columnNumber++;
if (type == TokenType.StringLiteral)
{
if (c == stringType && !isEscaped)
{
yield return new Token(buffer.ToString(), lineNumber, tokenStart, type, line, offset++, file);
buffer.Clear();
type = TokenType.None;
}
else
{
buffer.Append(c);
isEscaped = !isEscaped && c == '\\'; // ends an existing escape or starts a new one
}
}
else
{
var newType = Identify(c);
if (newType == type && CanCombine(type, buffer.Length, lastChar, c))
{
buffer.Append(c);
}
else
{
if (buffer.Length != 0)
{
yield return new Token(buffer.ToString(), lineNumber, tokenStart, type, line, offset++, file);
buffer.Clear();
}
type = newType;
tokenStart = columnNumber;
if (newType == TokenType.StringLiteral)
{
stringType = c;
}
else
{
buffer.Append(c);
}
}
}
lastChar = c;
}
if (buffer.Length != 0)
{
yield return new Token(buffer.ToString(), lineNumber, tokenStart, type, lastLine, offset++, file);
buffer.Clear();
}
}
}
internal static bool TryParseSingle(string token, out float val)
{
if (token == "nan")
{
val = float.NaN;
return true;
}
if (token == "inf")
{
val = float.PositiveInfinity;
return true;
}
if (token == "-inf")
{
val = float.NegativeInfinity;
return true;
}
return float.TryParse(token, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val);
}
internal static bool TryParseDouble(string token, out double val)
{
if(token == "nan")
{
val = double.NaN;
return true;
}
if(token == "inf")
{
val = double.PositiveInfinity;
return true;
}
if(token == "-inf")
{
val = double.NegativeInfinity;
return true;
}
return double.TryParse(token, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out val);
}
}
}