3

我正在使用 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拥有它。


更新从单词中删除的空格:

(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。它取代了以前基于正则表达式的手动解析的混乱。

4

2 回答 2

2

好吧,您必须进行两项更改才能使您的两个示例都能正常工作。

1)添加负面的Lookbehind

首先,您需要在正则表达式中对<word>. 这样,它将删除所有出现的)作为最后一个字符:

 <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"]

希望这可以帮助。

于 2013-08-17T11:26:49.270 回答
2

我真的不知道 instaparser,所以我只是阅读了足够多的文档来给我一种虚假的安全感。我也没有测试,我真的不知道你的要求是什么。

特别是,我不知道:

1) $() 是否可以嵌套(我认为你的语法使这不可能,但对我来说似乎很奇怪)

2) () 是否可以包含空格而不被解析为一个以上的单词

3) () 是否可以包含 $()

为了编写语法(或者碰巧寻求建议),您需要清楚这样的事情。

更新根据评论修改了语法。我删除了 和 的产生$ ()因为它们似乎没有必要,这样尖括号感觉更容易处理。

以下内容基于回答上述问题“是,否,是”以及关于正则表达式格式的一些随机假设。(我并不完全清楚尖括号是如何工作的,但我认为让括号以你想要的方式输出并不容易;我决定将它们作为单个元素输出。如果我弄清楚了一些事情,我会编辑它。)

<sequence> = element (<space> element)*
<element> = expr | paren_sequence | word
expr = <'$'> <'('> sequence <')'>
<word> = !('$'? '(') #'([^ $()]|\$[^(])+'
<paren_sequence> = '(' sequence ')' 
<space> = #'\\s+'

希望那些对你有帮助。

于 2013-08-16T22:58:34.913 回答