TypeReferenceParser.cs
244 lines
| 7.1 KiB
| text/x-csharp
|
CSharpLexer
cin
|
r289 | using System; | |
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Text.RegularExpressions; | |||
namespace Implab.ServiceHost.Unity { | |||
internal class TypeReferenceParser { | |||
enum TokenType { | |||
None, | |||
Word, | |||
Dot, | |||
Comma, | |||
OpenList, | |||
CloseList, | |||
OpenArray, | |||
CloseArray, | |||
Plus, | |||
Eof | |||
} | |||
readonly Regex _tokens = new Regex(@"\G(?:([\w]+)|\s*([\+\.{},\[\]])\s*)", RegexOptions.Compiled); | |||
TokenType m_token; | |||
string m_tokenValue; | |||
int m_pos; | |||
int m_tokenPos; | |||
readonly string m_text; | |||
TokenType Token { get { return m_token; } } | |||
string TokenValue { get { return m_tokenValue; } } | |||
int TokenPos { get { return m_tokenPos; } } | |||
public TypeReferenceParser(string text) { | |||
Safe.ArgumentNotEmpty(text, nameof(text)); | |||
m_text = text; | |||
} | |||
bool ReadToken() { | |||
if (m_pos >= m_text.Length) { | |||
m_token = TokenType.Eof; | |||
m_tokenValue = null; | |||
return false; | |||
} | |||
var m = _tokens.Match(m_text, m_pos); | |||
if (m.Success) { | |||
m_tokenPos = m_pos; | |||
m_pos += m.Length; | |||
if (m.Groups[1].Success) { | |||
m_token = TokenType.Word; | |||
m_tokenValue = m.Groups[1].Value; | |||
} else if (m.Groups[2].Success) { | |||
m_tokenValue = null; | |||
switch (m.Groups[2].Value) { | |||
case "{": | |||
m_token = TokenType.OpenList; | |||
break; | |||
case "}": | |||
m_token = TokenType.CloseList; | |||
break; | |||
case ".": | |||
m_token = TokenType.Dot; | |||
break; | |||
case ",": | |||
m_token = TokenType.Comma; | |||
break; | |||
case "[": | |||
m_token = TokenType.OpenArray; | |||
break; | |||
case "]": | |||
m_token = TokenType.CloseArray; | |||
break; | |||
case "+": | |||
m_token = TokenType.Plus; | |||
break; | |||
} | |||
} | |||
return true; | |||
} | |||
throw new FormatException($"Failed to parse '{m_text}' at pos {m_pos}"); | |||
} | |||
public TypeReference Parse() { | |||
var result = ReadTypeReference(); | |||
if (Token != TokenType.Eof) | |||
ThrowUnexpectedToken(); | |||
return result; | |||
} | |||
string[] ReadQTypeName() { | |||
var parts = new List<string>(); | |||
string current = null; | |||
bool stop = false; | |||
while ((!stop) && ReadToken()) { | |||
switch (Token) { | |||
case TokenType.Word: | |||
if (current != null) | |||
ThrowUnexpectedToken(); | |||
current = TokenValue; | |||
break; | |||
case TokenType.Dot: | |||
if (current == null) | |||
ThrowUnexpectedToken(); | |||
parts.Add(current); | |||
current = null; | |||
break; | |||
default: | |||
stop = true; | |||
break; | |||
} | |||
} | |||
if (current != null) | |||
parts.Add(current); | |||
if (parts.Count == 0) | |||
return null; | |||
return parts.ToArray(); | |||
} | |||
string ReadNQTypeName() { | |||
ReadToken(); | |||
if (Token != TokenType.Word) | |||
ThrowUnexpectedToken(); | |||
return TokenValue; | |||
} | |||
TypeReference ReadTypeReference() { | |||
var parts = ReadQTypeName(); | |||
if (parts == null) | |||
return null; | |||
var genericParameters = ReadGenericParams(); | |||
var typeReference = TypeReference.Create( | |||
string.Join(".", parts, 0, parts.Length - 1), | |||
parts[parts.Length - 1], | |||
genericParameters.Length | |||
); | |||
if (genericParameters.Length > 0 && genericParameters.All(x => x != null)) | |||
typeReference = typeReference.MakeGenericType(genericParameters); | |||
typeReference = ReadArraySpec(typeReference); | |||
if(Token == TokenType.Plus) | |||
return ReadNestedType(typeReference); | |||
return typeReference; | |||
} | |||
TypeReference ReadNestedType(TypeReference declaringType) { | |||
var name = ReadNQTypeName(); | |||
if(string.IsNullOrEmpty(name)) | |||
throw new FormatException("Nested type name can't be empty"); | |||
ReadToken(); | |||
var genericParameters = ReadGenericParams(); | |||
var typeReference = declaringType.Create( | |||
name, | |||
genericParameters.Length | |||
); | |||
if (genericParameters.Length > 0 && genericParameters.All(x => x != null)) | |||
typeReference = typeReference.MakeGenericType(genericParameters); | |||
typeReference = ReadArraySpec(typeReference); | |||
if(Token == TokenType.Plus) | |||
return ReadNestedType(typeReference); | |||
return typeReference; | |||
} | |||
TypeReference[] ReadGenericParams() { | |||
if (Token == TokenType.OpenList) { | |||
var genericParameters = ReadTypeReferenceList(); | |||
if (Token != TokenType.CloseList) | |||
ThrowUnexpectedToken(); | |||
ReadToken(); | |||
return genericParameters; | |||
} | |||
return Array.Empty<TypeReference>(); | |||
} | |||
TypeReference ReadArraySpec(TypeReference typeReference) { | |||
while (Token == TokenType.OpenArray) { | |||
var rank = CountRank(); | |||
if (Token != TokenType.CloseArray) | |||
ThrowUnexpectedToken(); | |||
typeReference = typeReference.MakeArrayType(rank); | |||
ReadToken(); | |||
} | |||
return typeReference; | |||
} | |||
int CountRank() { | |||
int rank = 0; | |||
do { | |||
rank++; | |||
} while(ReadToken() && Token == TokenType.Comma); | |||
return rank; | |||
} | |||
TypeReference[] ReadTypeReferenceList() { | |||
var list = new List<TypeReference>(); | |||
do { | |||
var typeReference = ReadTypeReference(); | |||
list.Add(typeReference); | |||
} while (Token == TokenType.Comma); | |||
return list.ToArray(); | |||
} | |||
void ThrowUnexpectedToken() { | |||
throw new FormatException($"Unexpected '{Token}' at pos {TokenPos}: -->{m_text.Substring(TokenPos, Math.Min(m_text.Length - TokenPos, 10))}"); | |||
} | |||
} | |||
} |