(注意:我已经阅读了其他问题,例如this,但我无法弄清楚这一点。)
我写了这个语法:
start = call
ident = [a-z]+
spaces = [ ]+
call = f:ident spaces g:(call / ident) {
return f + "(" + g + ")";
}
使用此输入
a b c d
它返回
"a(b(c(d)))"
我想要
"a(b)(c)(d)"
我认为这个左递归规则可以给我这样的东西,但是PEG.js不支持左递归。
call = f:(call / ident) spaces g:ident {
return f + "(" + g + ")";
}
在这种情况下如何消除左递归?
PS:你可以在online PEG.js demo
上测试一下答案 0 :(得分:7)
好问题。首先将您的第一个ident
与其他所有内容分开,因为它会得到特殊处理(没有括号)。接下来,遵循规则来处理将收集括号内的值的spaces ident
递归。循环包装ident
文本并附加递归收集的任何新文本。
以下是规则的简写版本(请注意tail
是一个单独的规则):
head: ident tail?; //the "head" ident is separated
tail: spaces ident tail?; //each "tail" ident is looped over
以下是PEG脚本:
start = head
ident = [a-z]+
spaces = [ ]+
head = head:ident tail:tail? {
return head + tail;
}
tail = spaces next:ident tail:tail? {
return "(" + next + ")" + tail
}
编辑:以下是一种替代方案,可以在一条规则中完成工作,与您的规则更相似。
start = head
ident = [a-z]+
spaces = [ ]+
head = head:ident tail:(spaces next:ident{return "(" + next + ")" })* {
return head + tail.join("")
}
两个脚本的a b c d
输出为"a(b)(c)(d)"
。
答案 1 :(得分:5)
如果我理解正确,你的问题不会留下递归,而是解析树的结构。
你已经正确地消除了左递归,但不幸的是,摆脱左递归的唯一方法是消除原始分析树中的左递归。这个东西的大多数理论只是匹配正确的字符串集。你仍然匹配同一组字符串,所以理论很高兴,但你想要一个左递归解析树。有关该问题的更多信息on wikipedia。
AFAIK,你不能让PEG解析器的原始输出为左递归。但是,您可以使用输出执行任何操作。因此将其解析为数组,然后进行后处理以使其具有良好的左结构。
使用简化(无空格,无多字符标识符)语法:
start = call
id = [a-z]
call
= arr:id+ {
var acc = arr[0]
for (i = 1; i < arr.length; i++) {
acc = [acc, arr[i]]
}
return acc;
}
将abcd
解析为[ [ [ 'a', 'b' ], 'c' ], 'd' ]
。我只是使用+
而不是递归,然后遍历生成我们想要的结构的结果数组。维基百科有关于left recursion with a PEG的一些注释。
假设你想要数据结构。如果您只想要parens,请将此操作替换为:
var acc = arr[0]
for (i = 1; i < arr.length; i++) {
acc = acc + '(' + arr[i] + ')'
}
return acc;
提供a(b)(c)(d)
。
要将空格和多字符ID放回,您可以这样做:
start = call
id = [a-z]+
_ = [ ]+
call
= a:id as:arg* {
arr = [a].concat(as)
var acc = arr[0]
for (i = 1; i < arr.length; i++) {
acc = acc + '(' + arr[i] + ')'
}
return acc;
}
arg = _ a:id {return a}
答案 2 :(得分:0)
您可以重新构建调用非终端,并使用 + 运算符将其重复部分放在一个单独的规则中,这样:
start = call
ident = i:[a-z]+ { return i.join(''); }
spaces = [ ]+
call = f:ident g:args+ {
return f + g.join('');
}
args = spaces a:ident { return "(" + a + ")"; }