view Implab.ServiceHost/Unity/TypeReferenceParser.cs @ 281:e0916ddc9950 v3 tip

code cleanup and refactoring
author cin
date Fri, 01 Jun 2018 21:35:24 +0300
parents 6691aff01de1
children
line wrap: on
line source

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

    }
}