我试图通过在PEG.js playground中输入简单的语法来绕过PEG。
示例1:
"abcdef1234567ghijklmn8901opqrs"
所需的输出:["abcdef", "1234567",
"ghijklmn", "8901", "opqrs"]
实际输出:["abcdef", ["1234567", ["ghijklmn", ["8901", ["opqrs", ""]]]]]
这个例子非常有效,但是我可以让PEG.js不将结果数组嵌套到百万级别吗?我假设诀窍是在某个地方使用concat()
而不是join()
,但我找不到这个地方。
start
= Text
Text
= Numbers Text
/ Characters Text
/ EOF
Numbers
= numbers: [0-9]+ {return numbers.join("")}
Characters
= text: [a-z]+ {return text.join("")}
EOF
= !.
示例2:
与示例1相同的问题和代码,但将“字符”规则更改为以下内容,我预期会产生相同的结果。
Characters
= text: (!Numbers .)+ {return text.join("")}
结果输出为:
[",a,b,c,d,e,f", ["1234567", [",g,h,i,j,k,l,m,n", ["8901", [",o,p,q,r,s", ""]]]]]
为什么我会得到所有这些空的匹配?
示例3:
最后一个问题。这根本不起作用。我怎样才能使它工作?对于奖励积分,任何关于效率的指针?例如,如果可能,我应该避免递归吗?
我也很欣赏一个很好的PEG教程的链接。我已阅读(http://www.codeproject.com/KB/recipes/grammar_support_1.aspx),但正如您所见,我需要更多帮助...
'abcdefghijklmnop"qrstuvwxyz"abcdefg'
["abcdefghijklmnop", "qrstuvwxyz",
"abcdefg"]
"abcdefghijklmnop\"qrstuvwxyz\"abcdefg"
start
= Words
Words
= Quote
/ Text
/ EOF
Quote
= quote: ('"' .* '"') Words {return quote.join("")}
Text
= text: (!Quote . Words) {return text.join("")}
EOF
= !.
答案 0 :(得分:21)
我收到了PEG.js Google Group的回复,帮助我走上正轨。我正在发布所有三个问题的答案,希望它们可以作为像我这样的其他PEG初学者的基础教程。请注意,不需要递归。
一旦你理解了基本的PEG习语,这很简单。
start
= Text+
Text
= Numbers
/ Characters
Numbers
= numbers: [0-9]+ {return numbers.join("")}
Characters
= text: [a-z]+ {return text.join("")}
这里的问题是Peek表达式(& expr和!expr)的PEG.js解析器生成器中的一种特殊设计选择。两者都在不消耗任何字符的情况下向前查看输入流,因此我错误地认为它们没有返回任何内容。但是,它们都返回一个空字符串。我希望PEG.js的作者改变这种行为,因为(据我所知)这只是污染输出流的不必要的瑕疵。如果我错了,请纠正我!
无论如何,这是一个解决方法:
start
= Text+
Text
= Numbers
/ Words
Numbers
= numbers: [0-9]+ {return numbers.join("")}
Words
= text: Letter+ {return text.join("")}
Letter
= !Numbers text: . {return text}
问题是像('"' .* '"')
这样的表达式永远不会成功。 PEG始终是贪婪的,因此.*
将消耗输入流的其余部分,并且永远不会看到第二个引用。这是一个解决方案(顺便说一下,需要与示例2中相同的Peek解决方法)。
start
= Words+
Words
= QuotedString
/ Text
QuotedString
= '"' quote: NotQuote* '"' {return quote.join("")}
NotQuote
= !'"' char: . {return char}
Text
= text: NotQuote+ {return text.join("")}
答案 1 :(得分:0)
对于pegjs
的当前版本,您可以尝试:
输入:"abcdef1234567ghijklmn8901opqrs"
所需的输出:["abcdef", "1234567", "ghijklmn", "8901", "opqrs"]
{
/**
* Deeply flatten an array.
* @param {Array} arr - array to flatten
* @return {Array} - flattened array
*/
const flatten = (arr) => Array.isArray(arr) ? arr.reduce((flat, elt) => flat.concat(Array.isArray(elt) ? flatten(elt) : elt), []) : arr
}
start = result:string {
console.log(JSON.stringify(result))
return result
}
string = head:chars tail:( digits chars? )* {
return flatten([head,tail])
}
chars = [a-z]+ {
return text()
}
digits = $[0-9]+ {
return text()
}
应该很容易从上面的答案中推断出来。
输入:'abcdefghijklmnop"qrstuvwxyz"abcdefg'
所需的输出:["abcdefghijklmnop", "qrstuvwxyz", "abcdefg"]
{
/**
* Deeply flatten an array.
* @param {Array} arr - array to flatten
* @return {Array} - flattened array
*/
const flatten = (arr) => Array.isArray(arr) ? arr.reduce((flat, elt) => flat.concat(Array.isArray(elt) ? flatten(elt) : elt), []) : arr
}
start = result:string {
console.log(JSON.stringify(result))
return result
}
string = head:chars tail:quote_chars* {
return flatten([head,tail])
}
quote_chars = DQUOTE chars:chars {
return chars
}
chars = [a-z]+ {
return text()
}
DQUOTE = '"'
答案 2 :(得分:-2)
start
= alnums
alnums
= alnums:(alphas / numbers) {
return alnums;
}
alphas
= alphas:$(alpha+)
numbers
= numbers:$(number+)
number
= [0-9]
alpha
= [a-zA-Z]
忽略
> 'abcdefghijklmnop"qrstuvwxyz"abcdefg'.split('"')
[ 'abcdefghijklmnop',
'qrstuvwxyz',
'abcdefg' ]