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 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 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(); 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))}"); } } }