我正在使用Instaparse来解析像:
这样的表达式 $(foo bar baz $(frob))
成像:
[:expr "foo" "bar" "baz" [:expr "frob"]]
我差不多了,但是模糊不清。这是我的语法的简化版本,即repros,试图依赖负向前瞻。
(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
拥有它。
更新从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"]]]]]]
这是我写的聊天机器人yetibot的解析器。它取代了之前基于正则表达式的混乱解析。
答案 0 :(得分:2)
我真的不知道instaparser,所以我只是阅读了足够的文档来给我一种虚假的安全感。我也没有测试,我真的不知道你的要求是什么。
特别是,我不知道:
1)$()是否可以嵌套(我认为你的语法不可能,但我觉得这很奇怪)
2)()是否可以包含空格而不被解析为多个单词
3)()是否可以包含$()
你需要清楚这样的事情才能写出语法(或者,当它发生时,要求建议)。
更新:根据评论修改语法。我删除了$
(
和)
的作品,因为它们似乎没必要,这样角括号也更容易处理。
以下是基于回答上述问题“是,不,是”以及关于正则表达式格式的一些随机假设。 (我不完全清楚角括号是如何工作的,但我认为用括号输出你想要的方式并不容易;我决定将它们作为单个元素输出。如果我弄清楚了什么,我我会编辑它。)
<sequence> = element (<space> element)*
<element> = expr | paren_sequence | word
expr = <'$'> <'('> sequence <')'>
<word> = !('$'? '(') #'([^ $()]|\$[^(])+'
<paren_sequence> = '(' sequence ')'
<space> = #'\\s+'
希望有所帮助。
答案 1 :(得分:2)
您必须进行两项更改才能使两个示例都正常工作。
1)添加负面背后隐藏
首先,您需要<word>
的正则表达式中的负 lookbehind 。这样,它会将)
的所有出现次数作为最后一个字符:
<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"]
希望这有帮助。