TypeReferenceParser.cs
244 lines
| 7.1 KiB
| text/x-csharp
|
CSharpLexer
|
|
r267 | using System; | |
| using System.Collections.Generic; | |||
|
|
r278 | using System.Linq; | |
|
|
r267 | using System.Text.RegularExpressions; | |
| namespace Implab.ServiceHost.Unity { | |||
|
|
r268 | internal class TypeReferenceParser { | |
|
|
r267 | enum TokenType { | |
| None, | |||
| Word, | |||
| Dot, | |||
| Comma, | |||
| OpenList, | |||
| CloseList, | |||
|
|
r277 | OpenArray, | |
| CloseArray, | |||
|
|
r278 | Plus, | |
|
|
r267 | Eof | |
| } | |||
|
|
r278 | readonly Regex _tokens = new Regex(@"\G(?:([\w]+)|\s*([\+\.{},\[\]])\s*)", RegexOptions.Compiled); | |
|
|
r267 | ||
| TokenType m_token; | |||
| string m_tokenValue; | |||
| int m_pos; | |||
|
|
r268 | int m_tokenPos; | |
|
|
r267 | readonly string m_text; | |
| TokenType Token { get { return m_token; } } | |||
| string TokenValue { get { return m_tokenValue; } } | |||
|
|
r268 | int TokenPos { get { return m_tokenPos; } } | |
|
|
r267 | 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) { | |||
|
|
r268 | m_tokenPos = m_pos; | |
|
|
r267 | 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; | |||
|
|
r277 | case "[": | |
| m_token = TokenType.OpenArray; | |||
| break; | |||
| case "]": | |||
| m_token = TokenType.CloseArray; | |||
| break; | |||
|
|
r278 | case "+": | |
| m_token = TokenType.Plus; | |||
| break; | |||
|
|
r267 | } | |
| } | |||
| return true; | |||
| } | |||
| throw new FormatException($"Failed to parse '{m_text}' at pos {m_pos}"); | |||
| } | |||
|
|
r268 | public TypeReference Parse() { | |
| var result = ReadTypeReference(); | |||
|
|
r277 | if (Token != TokenType.Eof) | |
|
|
r268 | ThrowUnexpectedToken(); | |
| return result; | |||
|
|
r267 | } | |
|
|
r278 | string[] ReadQTypeName() { | |
|
|
r267 | 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(); | |||
| } | |||
|
|
r278 | string ReadNQTypeName() { | |
| ReadToken(); | |||
| if (Token != TokenType.Word) | |||
| ThrowUnexpectedToken(); | |||
| return TokenValue; | |||
| } | |||
|
|
r267 | TypeReference ReadTypeReference() { | |
|
|
r278 | var parts = ReadQTypeName(); | |
|
|
r267 | if (parts == null) | |
| return null; | |||
|
|
r278 | 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 | |||
| ); | |||
|
|
r267 | ||
|
|
r278 | 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(); | |||
|
|
r267 | } | |
| return typeReference; | |||
| } | |||
|
|
r278 | int CountRank() { | |
| int rank = 0; | |||
| do { | |||
| rank++; | |||
| } while(ReadToken() && Token == TokenType.Comma); | |||
| return rank; | |||
|
|
r277 | } | |
|
|
r267 | TypeReference[] ReadTypeReferenceList() { | |
|
|
r268 | var list = new List<TypeReference>(); | |
|
|
r267 | ||
|
|
r268 | do { | |
| var typeReference = ReadTypeReference(); | |||
| list.Add(typeReference); | |||
| } while (Token == TokenType.Comma); | |||
| return list.ToArray(); | |||
|
|
r267 | } | |
| void ThrowUnexpectedToken() { | |||
|
|
r268 | throw new FormatException($"Unexpected '{Token}' at pos {TokenPos}: -->{m_text.Substring(TokenPos, Math.Min(m_text.Length - TokenPos, 10))}"); | |
|
|
r267 | } | |
| } | |||
| } |
