我正在使用Instaparse来解析如下的表达式:
$(foo bar baz $(frob))
变成像:
[:expr "foo" "bar" "baz" [:expr "frob"]]
我差一点就明白了,但我对模棱两可有困难。下面是我语法的一个简化版本,试图依赖于消极的展望。
(def simple
(insta/parser
"expr = <dollar> <lparen> word (<space> word)* <rparen>
<word> = !(dollar lparen) #'.+' !(rparen)
<space> = #'\\s+'
<dollar> = <'$'>
<lparen> = <'('>
<rparen> = <')'>"))
(simple "$(foo bar)")哪些错误:
Parse error at line 1, column 11:
$(foo bar)
^
Expected one of:
")"
#"\s+"在这里,我说过一个词可以是任何字符,以支持如下的表达式:
$(foo () `bar` b-a-z)
注意,一个单词可以包含(),但不能包含$()。不知道如何在语法中表达这一点。问题似乎是<word>太贪婪了,消耗了最后一个),而不是让expr拥有它。
Update删除word中的空白:
(def simple2
(insta/parser
"expr = <dollar> <lparen> word (<space> word)* <rparen>
<word> = !(dollar lparen) #'[^ ]+' !(rparen)
<space> = #'\\s+'
<dollar> = <'$'>
<lparen> = <'('>
<rparen> = <')'>"))
(simple2 "$(foo bar)")
; Parse error at line 1, column 11:
; $(foo bar)
; ^
; Expected one of:
; ")"
; #"\s+"
(simple2 "$(foo () bar)")
; Parse error at line 1, column 14:
; $(foo () bar)
; ^
; Expected one of:
; ")"
; #"\s+"更新2更多测试用例
(simple2 "$(foo bar ())")
(simple2 "$((foo bar baz))")更新3全工作解析器
对于任何好奇的人来说,不属于这个问题范围的完整的工作解析器是:
(def parse
"expr - the top-level expression made up of cmds and sub-exprs. When multiple
cmds are present, it implies they should be sucessively piped.
cmd - a single command consisting of words.
sub-expr - a backticked or $(..)-style sub-expression to be evaluated inline.
parened - a grouping of words wrapped in parenthesis, explicitly tokenized to
allow parenthesis in cmds and disambiguate between sub-expression
syntax."
(insta/parser
"expr = cmd (<space> <pipe> <space> cmd)*
cmd = words
<sub-expr> = <backtick> expr <backtick> | nestable-sub-expr
<nestable-sub-expr> = <dollar> <lparen> expr <rparen>
words = word (<space>* word)*
<word> = sub-expr | parened | word-chars
<word-chars> = #'[^ `$()|]+'
parened = lparen words rparen
<space> = #'[ ]+'
<pipe> = #'[|]'
<dollar> = <'$'>
<lparen> = '('
<rparen> = ')'
<backtick> = <'`'>"))示例用法:
(parse "foo bar (qux) $(clj (map (partial * $(js 45 * 2)) (range 10))) `frob`")分析:
[:expr [:cmd [:words "foo" "bar" [:parened "(" [:words "qux"] ")"] [:expr [:cmd [:words "clj" [:parened "(" [:words "map" [:parened "(" [:words "partial" "*" [:expr [:cmd [:words "js" "45" "*" "2"]]]] ")"] [:parened "(" [:words "range" "10"] ")"]] ")"]]]] [:expr [:cmd [:words "frob"]]]]]]这是我编写的一个聊天机器人的解析器,叶铁伯特。它取代了以前混乱的基于正则表达式的手工解析。
发布于 2013-08-16 22:58:34
我真的不知道instaparser,所以我只读了足够的文档,给了我一种错误的安全感。我也没有测试,我也不知道你的需求是什么。
特别是,我不知道:
( 1) $()是否可以嵌套(我认为你的语法使这不可能,但对我来说似乎很奇怪)
2) ()是否可以包含空格而不被解析为多个单词
3) ()是否可以包含$()
您需要清楚地了解这样的事情,以便编写语法(或者,如果碰巧的话,请您提供建议)。
更新:基于注释修改语法。我删除了$ (和)的产品,因为它们似乎没有必要,这样的话,尖括号就更容易处理了。
以下是基于回答上述问题“是的,不是的,是的”和一些关于regex格式的随机假设。(我不完全清楚尖括号是如何工作的,但我不认为让括号以您想要的方式输出是很容易的;我满足于将它们作为单个元素输出。如果我想出了什么,我会编辑它的。)
<sequence> = element (<space> element)*
<element> = expr | paren_sequence | word
expr = <'$'> <'('> sequence <')'>
<word> = !('$'? '(') #'([^ $()]|\$[^(])+'
<paren_sequence> = '(' sequence ')'
<space> = #'\\s+'希望这能帮点忙。
发布于 2013-08-17 11:26:49
为了让你的两个例子都起作用,你必须做两个改变。
1)添加负查找
首先,在对的正则表达式中,您需要一个负的查找。这样,它将删除所有出现的),作为最后一个字符:
<word> = !(dollar lparen) #'[^ ]+(?<!\\))' 因此,这将修复第一个测试用例:
(simple2 "$(foo bar)")
=> [:expr "foo" "bar"]2)为最后一个单词添加语法
现在,如果您运行第二个测试用例,它将失败:
(simple2 "$(foo () bar)")
=> Parse error at line 1, column 8:
$(foo () bar)
^
Expected one of:
")" (followed by end-of-string)
#"\s+"这是因为我们已经告诉我们的语法在所有的)实例中删除了最后一个<word>。现在我们必须告诉语法如何区分<word>的最后一个实例和其他实例。我们将通过添加特定的<lastword>语法来做到这一点,并使<word>的所有其他实例都是可选的。完整的语法如下所示:
(def simple2
(insta/parser
"expr = <dollar> <lparen> word* lastword <rparen>
<word> = !(dollar lparen) #'[^ ]+' <space>+
<lastword> = !(dollar lparen) #'[^ ]+(?<!\\))'
<space> = #'\\s+'
<dollar> = <'$'>
<lparen> = <'('>
<rparen> = <')'>")) 而且您的两个测试用例应该工作得很好:
(simple2 "$(foo bar)")
=> [:expr "foo" "bar"]
(simple2 "$(foo () bar)")
=> [:expr "foo" "()" "bar"]希望这能有所帮助。
https://stackoverflow.com/questions/18282535
复制相似问题