实现转义序列解析器

4

我希望能够解析一个自定义字符串格式,用于持久化对象图的状态。这是一个ASP.NET场景,我想要在客户端(JavaScript)和服务器(C#)上都使用简单的工具。

我的格式大致如下:

{Name1|Value1|Value2|...|ValueN}{Name2|Value1|...}{...}{NameN|...}

在这个格式中,我有3个分隔符,{}|。此外,由于这些字符在名称/值中是可行的,我使用非常常见的\定义了一个转义序列,使得\{\}\|都被解释为它们自己的普通版本,当然\\是一个反斜杠。所有这些都很标准。
最初,我尝试使用正则表达式来尝试解析对象的字符串表示形式,类似于(?<!\\)\{(.*?)(?<!\\)\}。请记住,\{}在正则表达式中都是保留字。当然,这样就可以正确解析出像{category|foo\}|bar\{}这样的内容。然而,我意识到它无法处理像{category|foo|bar\\}这样的内容。
我只用了一两分钟时间去尝试这个(?<!(?<!\\)\\)\{(.*?)(?<!(?<!\\)\\)\},并且认识到这种方法不可能,因为你需要无限数量的负向回顾来处理潜在的无限数量的转义序列。当然,我很少会有超过一两个级别的情况,所以我可能可以硬编码它。然而,我认为这是一个常见的问题,应该有一个定义明确的解决方案。
我的下一个尝试是尝试编写一个定义好的解析器,其中我实际上扫描输入缓冲区,并以向前的方式消耗每个字符。我还没有完成这个,但它似乎过于复杂了,我觉得我一定会错过一些显而易见的东西。我的意思是,我们拥有解析器,就像我们拥有计算机语言一样长久。
所以我的问题是:解码像这样具有可能的转义序列的输入缓冲区的最简单、有效和优雅的方法是什么?

我不太确定我理解你的问题。你是指想要在{、|和}之前加上反斜杠来去除它们的特殊含义吗?也就是说,"{foo|ba}r|baz}"应该匹配"{foo|ba}"还是整个字符串? - Ben Blank
它应该匹配完整字符串{foo|ba}r|bas},并且捕获应该是foo|ba}r|bas。 - Peter Oehlert
2个回答

7
(?<!\\)(?:\\\\)*\{(.*?(?<!\\)(?:\\\\)*)\}

(?<!\\)将防止此点之前的任何\

(?:\\\\)*允许任意数量的转义\

\{匹配一个左括号。

(开始一个捕获组。

.*?匹配内容,包括任何|

(?<!\\)将防止此点之前的任何\

(?:\\\\)*允许任意数量的转义\

)结束捕获组。

\}匹配一个右括号。


1
太棒了。我没有意识到你可以有连续的反向引用,所以我的尝试实现在“非固定长度反向引用”问题上失败了。 - BrodieG
1
@Brodie,你所说的是向后查找,而不是反向引用。OP在问题中犯了同样的错误,但我已经修正了它。 - Alan Moore

2

这种解析器很容易进行最小状态跟踪。以下内容只花费了我几分钟时间,相当丑陋,甚至进行了一点错误检查。 :)

可以说,这是一种比复杂的正则表达式更易读的方法,尽管前者更加简洁。

struct RECORD
{
    public string[] Entries;
}
struct FILE
{
    public RECORD[] Records;
}

static FILE parseFile(string input)
{
    List<RECORD> records = new List<RECORD>();
    List<string> entries = new List<string>();
    bool escaped = false;
    bool inRecord = false;
    StringBuilder sb = new StringBuilder();
    foreach (char c in input)
    {
        switch (c)
        {
            case '|':
                if (escaped)
                {
                    sb.Append('|');
                    escaped = false;
                }
                else if (inRecord)
                {
                    entries.Add(sb.ToString());
                    sb = new StringBuilder();
                }
                else
                    throw new Exception("Invalid sequence");
                break;
            case '{':
                if (escaped)
                {
                    sb.Append('{');
                    escaped = false;
                }
                else if (inRecord)
                    throw new Exception("Invalid sequence");
                else
                {
                    inRecord = true;
                    sb = new StringBuilder();
                }
                break;
            case '}':
                if (escaped)
                {
                    sb.Append('}');
                    escaped = false;
                }
                else if (inRecord)
                {
                    inRecord = false;
                    entries.Add(sb.ToString());
                    sb = new StringBuilder();
                    records.Add(new RECORD(){Entries = entries.ToArray()});
                    entries.Clear();
                }
                else
                    throw new Exception("Invalid sequence");
                break;
            case '\\':
                if (escaped)
                {
                    sb.Append('\\');
                    escaped = false;
                }
                else if (!inRecord)
                    throw new Exception("Invalid sequence");
                else
                    escaped = true;
                break;
            default:
                if (escaped)
                    throw new Exception("Unrecognized escape sequence");
                else
                    sb.Append(c);
                break;
        }
    }
    if (inRecord)
        throw new Exception("Invalid sequence");
    return new FILE() { Records = records.ToArray() };
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接