手动解析一个简单的语法

Manually parsing a simple grammar

我需要实现一个简单的解析器来解析以下字符串: a[b[c,d],e],f[g,h[i],j[k,l]],...

在上面的示例中,有一个由一个或多个 OBJECTS 组成的 GROUP。 每个对象都有嵌套的 OBJECTS(b in a AND h,j in f) 加上嵌套的 VALUES(c,d,e,g,i,k,l)。

所以,它是这样的:

GROUP : OBJECT (,OBJECT)*
OBJECT: NAME '[' OBJECTorVALUE (OBJECTorVALUE)* ']'
OBJECTorVALUE: OBJECT | VALUE
VALUE : NAME

手动解析此类语法的最简单方法是什么?

我试着用递归下降解析器解析它,它是可行的,但看起来很奇怪,因为你必须解析 OBJECTorVALUE 的产生式:

OBJECTorVALUE: NAME '[' blabla ']'
OBJECTorVALUE: NAME

因此,要使其成为 LL 文法(由 rec-descent 解析),我们应该将其解析为

OBJECTorVALUE: NAME Z
Z: eps | '[' blabla ']'

现在 rec-desc 解析器变得有些不必要的复杂性并且感觉不自然,所以我必须引入一种 rec-desc 方法来在名称后查找 Z。所以,而不是简单的方法

parseGroup -> parseObjects 
parseObj -> parseName consume[ parseObjectValueGroup consume ]
parseObjectValueGroup -> parseObjectValues
parseObjectValue -> look('[') -> parseObj OR parseValue

我也一样,但最后的语句变成了

parseObjectValue -> parseName parseZ
parseZ -> look('[') -> parseObjWithoutName OR return empty
parseObjWithoutName -> ...

我觉得这看起来很乱,所以我们可以在这里使用更简单的东西吗?我的意思是它是该死的简单语法,甚至可以通过字符串拆分来解析,而且看起来非常简单。也许简单 LR 解析器 (SLR) 在这种情况下会更具可读性?

我实际上认为尝试解析这种自上而下的方法并不会那么糟糕。如果你试着把它写成正式语法,它看起来比实际情况要糟糕得多。例如,伪代码可能如下所示:

ParseGroup():
    result = empty list
    while true:
        read token 'next'; it's some identifier.
        if (there's no next token) {
           append 'next' to result.
           break;
        }
        else if (the next token is '[') {
           let inner = ParseGroup();
           read token; it's a ']';
           append 'next[inner]' to result.
        } else {
           append 'next' to result.
        }
        read token; it's a comma.
        append ',' to result.
    }
}

这基本上是为修改后的语法略作修改的 LL(1) 解析器。

这真是一个非常简单的语法。你的语法不完整,因为 NAME 没有定义。 ABNF (RFC 5234) 语法中定义的语法是这样的(假设是 NAME 规则):

group   = element *("," WS element)
element = name WS ["[" WS group "]" WS]
name    = 1*(%x61-7A / %x41-5A)
WS      = *(%x20 / %x9 / %xA / %xD)

你可以理解为:一组由逗号分隔的元素组成。每个元素都有一个名称,并且在它之后可能在乡绅括号中有一个子元素组。 'name' 是一个或多个英文字母字符。在语法的每个部分之间,允许使用白色 space(零个或多个):space(20 十六进制)、制表符(9 十六进制)、换行符(A 十六进制)或回车 return(D 十六进制)。如果你不想要白色space,完全删除WS规则即可。

这是语法,但还有语义:如果 element 只有名称,那么它是 value,否则它是 object。这是一个简单的消歧,你应该在解析完​​成后做。

如果语法这么简单,你不想自己写代码,我做了一个工具(Tunnel Grammar Studio)可以用C++代码为你生成这个语法。语法很简单,所以工具的演示应该足够了。您也可以在线 运行 语法。 TGS 生成迭代解析器,这意味着如果您的组 ps 嵌套太多,您不必担心堆栈溢出问题。

ps。除了十六进制值,您还可以编写 1*('a'-'z' / 'A'-'Z'),因为 TGS 具有 ABNF 语法的扩展,可以轻松编写语法。