使用Regex解析泛型类型信息

bvpmtnay  于 2023-10-22  发布在  其他
关注(0)|答案(1)|浏览(91)

我正在尝试使用Regex解析一些类型信息。
我有一个目标:

public class TypeInformation
{
    public TypeInformation(string typeName)
    {
        TypeName = typeName;
        GenericArgs = new();
    }

    public TypeInformation(string typeName, List<TypeInformation> genericArgs)
    {
        TypeName = typeName;
        GenericArgs = genericArgs;
    }

    public string TypeName { get; init; }
    public bool IsGeneric => GenericArgs.Count > 0;
    public List<TypeInformation> GenericArgs { get; }
}

所以,如果我们取一个Tuple<string, int>,它会给我们一个完整的类型名:

System.Tuple`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

所以格式看起来相当简单,我们有一个类型符,一个反引号,然后是一个类型参数数组,用方括号和逗号分隔。
我不相信dotnet正则表达式支持原生递归,所以为了做到这一点,我写了这个解析器类:

public class TypeInformationParser
{
    private static readonly Regex TypeParsingRegex = new(@"^(?<TypeName>[\w\.]+(?:`(?<GenericArgCount>\d+))?)(?:\[(?<GenericArgs>\[.+\],?)\])?", RegexOptions.Compiled);
    private static readonly Regex GenericDetailsRegex = new(@"(?:\[(?:(?<TypeName>.+?),.+?)\])+", RegexOptions.Compiled);
    public TypeInformation ParseType(string typeName)
    {
        Match res = TypeParsingRegex.Match(typeName);
        Group? genericArgCount = res.Groups["GenericArgCount"];
        Group? genericArgs = res.Groups["GenericArgs"];
        Group? tName = res.Groups["TypeName"];

        if (!genericArgCount.Success) // Not a generic, lets short-circuit
        {
            return new(tName.Value);
        }

        MatchCollection genericArgsMatches = GenericDetailsRegex.Matches(genericArgs.Value);

        List<TypeInformation> genericTypeInfo = genericArgsMatches.Cast<Match>().Select(match => ParseType(match.Groups["TypeName"].Value)).ToList();
        
        // Not strictly necessary...
        int expectedNumArgs = int.Parse(genericArgCount.Value);
        if (expectedNumArgs != genericTypeInfo.Count)
        {
            throw new("Number of parsed generic parameters doesn't match signature"); 
        }
        

        return new(tName.Value, genericTypeInfo);
    }
}

本质上,我首先解析顶层,然后尝试只提取泛型参数的类型符部分,并递归地解析它们。
到目前为止,我已经得到了这个测试,它只是为了测试获得三种不同类型泛型的外部类型的能力:单参数、多参数和多参数嵌套。

[Theory]
    [InlineData("DeserialisePreviouslyAsPoc.Tests.TestTypes.NewNamespace.MyGenericBlob`1[[DeserialisePreviouslyAsPoc.Tests.TestTypes.NewNamespace.MyBlob, DeserialisePreviouslyAsPoc.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]", "DeserialisePreviouslyAsPoc.Tests.TestTypes.NewNamespace.MyGenericBlob`1")]
    [InlineData("System.Tuple`3[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]", "System.Tuple`3")]
    [InlineData("System.Tuple`3[[System.Tuple`2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Decimal, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]", "System.Tuple`3")]
    public void CanParseTypeNameOfGenericTypes(string fullTypeString, string outerTypeName)
    {
        TypeInformationParser parser = new();

        var typeInformation = parser.ParseType(fullTypeString);
        typeInformation.TypeName.ShouldBe(outerTypeName);
    }

前两个工作得很好,虽然在这个测试中没有,但似乎已经正确地解析了其余的字段。

然而,第三个案例福尔斯失败了。
这似乎是因为GenericDetailsRegex["TypeName"]匹配System.Tuple2[[System.Int32`而不是完整类型(其他参数匹配正确)。
所以,我想问题可以归结为:我如何分隔出一个逗号分隔列表中的项目,其中每个项目都用方括号括起来,但也可以包含方括号和逗号使用正则表达式?
所以这个:

[System.Tuple`2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Decimal, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]

变成这样:

{
    [System.Tuple`2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],
    [System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],
    [System.Decimal, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]
}

我大概能想出从那里到哪里去:

{
    System.Tuple`2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]],
    System.String,
    System.Decimal
}

然后可以通过管道递归传递。
现在,我可以通过在遍历字符串时计算括号和诸如此类的东西来做到这一点,但这肯定是正则表达式可以处理的,因为我没有试图递归到结构中,只是匹配最外面的匹配括号。

0h4hbjxa

0h4hbjxa1#

我已经设法用两个正则表达式(问题的不同正则表达式),递归方法解决了这个问题:

public class TypeInformationParser
{
    private static readonly Regex TypeParsingRegex = new(@"^(?<TypeName>[\w\.]+(?:`(?<GenericArgCount>\d+))?)(?:\[(?:(?<Arg>\[(?>(?<c>\[)|(?<-c>\])|[^[\]]+)+\](?(c)(?!))),?\s*)+\])?", RegexOptions.Compiled);
    private static readonly Regex TypeDetailsRegex = new(@"^\[(?<TypeName>.+),(?:.+,){3}.+\]$");
    public TypeInformation ParseType(string typeName)
    {
        Match res = TypeParsingRegex.Match(typeName);
        Group? genericArgCount = res.Groups["GenericArgCount"];
        Group? genericArgs = res.Groups["Arg"];
        Group? tName = res.Groups["TypeName"];

        if (!genericArgCount.Success) // Not a generic, lets short-circuit
        {
            return new(tName.Value);
        }

        List<TypeInformation> genericTypeInfo = genericArgs.Captures.Cast<Capture>()
            .Select(match => TypeDetailsRegex.Match(match.Value))
            .Select(typeDetails => ParseType(typeDetails.Groups["TypeName"].Value))
            .ToList();

        // Not strictly necessary...
        int expectedNumArgs = int.Parse(genericArgCount.Value);
        if (expectedNumArgs != genericTypeInfo.Count)
        {
            throw new("Number of parsed generic parameters doesn't match signature"); // Throw custom exception type
        }
        

        return new(tName.Value, genericTypeInfo);
    }

第一个正则表达式匹配一个直接的类型名,或者,对于泛型类型,它使用方括号上的平衡捕获组将泛型参数提取到res.Groups["Arg"]中。
然后用TypeDetailsRegex解析这些参数,只得到类型名,然后递归解析。

相关问题