Superpower Parser:处理组合器中子解析器的部分匹配?

Superpower Parser: Deal with partial match of a sub parser in a combinator?

所以我为专有文件类型编写了一个解析器。我已经完成了 95%,但是我的解析器在文件的最后一行失败,即 #。这是对其他几个解析器的部分匹配。看起来它试图将行解析为 PropertyList 但失败导致整个解析器失败。

我该怎么做才能解决这个问题?

mcve如下,Fiddle这里是https://dotnetfiddle.net/f30sN9

using System;
using Superpower;
using Superpower.Model;
using Superpower.Parsers;
using System.Collections.Generic;
                    
public class Program
{
    public static void Main()
    {
        var result1 = TagTXTParser.firstLine.TryParse(sampleFirstLine);
        Console.WriteLine(result1);
        var result2 = TagTXTParser.propertyList.TryParse(samplePropertyList);
        Console.WriteLine(result2);
        var result8 = TagTXTParser.record.TryParse(sampleRecord);
        Console.WriteLine(result8);
        
        var result9 = TagTXTParser.TagRecordsFileParser.TryParse(sampleFile);
        Console.WriteLine(result9);
    }
    
    public static string sampleFirstLine = "// 895.34 - Tags\n";
    public static string sampleSectionHeading = "#Parameters\n";
    public static string sampleItemList = "<[0]>|1|0|0|0|0|0.000000|0.000000|0.000000| | | |\n";
    public static string samplePropertyList = "#4|NavDisabled|0|1| | |0|0| |\n";
    public static string sampleSection1 = "#Parameters\n<[0] >| 1 | 0 | 0 | 0 | 0 | 0.000000 | 0.000000 | 0.000000 | | | | \n";
    public static string sampleSection2 = "#Parameters\n<[0] >| 1 | 0 | 0 | 0 | 0 | 0.000000 | 0.000000 | 0.000000 | | | | \n<[1] >| 1 | 0 | 0 | 0 | 0 | 0.000000 | 0.000000 | 0.000000 | | | | \n";
    public static string sampleSection3 = "#Parameters\n";
    public static string sampleRecord = "#3|ScreenNumber|0|1| | |1|0| |\n#Parameters\n<[0] >| 1 | 0 | 0 | 0 | 0 | 0.000000 | 0.000000 | 0.000000 | | | |\n#Alarm\n#History\n#ItemParameters\n<[0] >| 1 | 1 | \n";
    public static string sampleFile = 
@"// 8.10 - Application Tags
#1|SelectedWindowOnNavBar|0|1| | |1|0| |
#Parameters
<[0]>|1|0|0|0|0|0.000000|0.000000|0.000000| | | |
#Alarm
#History
#ItemParameters
<[0]>|1|1|
#2|ScreenName|0|3| | |1|0| |
#Parameters
<[0]>|1|0|0|0|0|0.000000|0.000000|0.000000| | | |
#Alarm
#History
#ItemParameters
<[0]>|1|1|
#3|ScreenNumber|0|1| | |1|0| |
#Parameters
<[0]>|1|0|0|0|0|0.000000|0.000000|0.000000| | | |
#Alarm
#History
#ItemParameters
<[0]>|1|1|
#4|NavDisabled|0|1| | |0|0| |
#Parameters
<[0]>|1|0|0|0|0|0.000000|0.000000|0.000000| | | |
#Alarm
#History
#ItemParameters
<[0]>|1|1|
#5|PLC_1_IPAddress|0|3| | |1|0| |
#Parameters
<[0]>|1|1|0|0|0|0.000000|0.000000|0.000000| | | |
#Alarm
#History
#ItemParameters
<[0]>|1|1|
#6|NumOfMeters|0|3| | |1|0| |
#Parameters
<[0]>|1|1|0|0|0|0.000000|0.000000|0.000000| | | |
#Alarm
#History
#ItemParameters
<[0]>|1|1|
#7|ComputerName|0|3| | |1|0| |
#Parameters
<[0]>|1|0|0|0|0|0.000000|0.000000|0.000000| | | |
#Alarm
#History
#ItemParameters
<[0]>|1|1|
#8|TZPosition|0|1| | |0|0| |
#Parameters
<[0]>|1|0|0|0|0|0.000000|0.000000|0.000000| | | |
#Alarm
#History
#ItemParameters
<[0]>|1|1|
#9|NewTime|0|3| | |1|0| |
#Parameters
<[0]>|1|0|0|0|0|0.000000|0.000000|0.000000| | | |
#Alarm
#History
#ItemParameters
<[0]>|1|1|
#";
        
}

public class TagTXTParser
{
    public record TagProperies(int Id, string Name, List<string> others);
    public record TagRecordSection(List<string>[] items);

    public class TagRecord
    {
        public int Id { get; set; } //** first element of First line
        public string Name { get; set; }



        public List<string> Properties { get; set; }
        public TagRecordSection Parameters { get; set; }
        public TagRecordSection Alarm { get; set; }
        public TagRecordSection History { get; set; }
        public TagRecordSection ItemParameters { get; set; }
    }

    public static TextParser<char> hash = Character.EqualTo('#');
    public static TextParser<TextSpan> eol = Span.EqualTo('\n').Or(Span.EqualTo("\r\n"));
    public static TextParser<char> pipe = Character.EqualTo('|');

    public static TextParser<string> text = Character.ExceptIn('#', '|', '\n', '\r').Many().Select(x => new string(x));

    public static TextParser<string> firstLine = from _ in Span.EqualTo("//")
        from rest in text
        from end in eol
        select new string(rest.ToCharArray());

    public static TextParser<char[]> heading =
        from h in hash
        from name in Character.Letter.AtLeastOnce()
        from end in eol
        select name;

    public static TextParser<List<string>> itemList = //from _ in eol
        from items in text.ManyDelimitedBy<string, char>(pipe)
        from end in eol
        select new List<string>(items);



    public static TextParser<TagProperies> propertyList =
        from _ in hash
        from id in Character.Digit.AtLeastOnce()
        from endOfId in pipe
        from name in text
        from endOfName in pipe
        from otherItems in itemList
        select new TagProperies(Convert.ToInt32(new string(id)), name, otherItems);


    public static TextParser<TagRecordSection> section =
        from _ in heading
        from items in itemList.Many()
        select new TagRecordSection(items);


    public static TextParser<TagRecord> record =
        from props in propertyList
        from sections in section.Repeat(4)
        select new TagRecord()
    {
        Id = props.Id,
        Name = props.Name,
        Properties = props.others,
        Parameters = sections[0],
        Alarm = sections[1],
        History = sections[2],
        ItemParameters = sections[3]
    };

    public static TextParser<TagRecord[]> TagRecordsFileParser = 
        from _ in firstLine
        from records in record.Many()
        from end in hash
        select records;
}

您需要在 TagRecordsFileParser 中添加对 .Try() 的调用:

public static TextParser<TagRecord[]> TagRecordsFileParser =
    from _ in firstLine
    from records in record.Try().Many() // <--- HERE
    from end in hash
    select records;

原因是因为当你刚刚调用 record.Many() 时,它会遍历你的 9 条记录,然后看到另一个散列,所以它认为它是另一个 record。解析器“消耗”了散列,但事实证明它根本不是一条记录,这导致它失败。调用 record.Try().Many() 告诉 Superpower 尝试解析尽可能多的 record,但如果其中一个失败,不要让整个解析器失败,只需回溯已经消耗的内容,然后继续其余的解析器定义。

您要做的另一件事是在解析输入时添加对 .AtEnd() 的调用:

var result9 = TagTXTParser.TagRecordsFileParser.AtEnd().TryParse(sampleFile);

这样,解析器只有在成功解析您的整个输入时才会成功。这将使您的输入始终必须以单个 # 字符结尾。否则,如果文件以 #x 之类的结尾,解析器将成功,我认为您不想要。