首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >带有子表达式语法的简单Instaparse解析器

带有子表达式语法的简单Instaparse解析器
EN

Stack Overflow用户
提问于 2013-08-16 21:41:13
回答 2查看 993关注 0票数 3

我正在使用Instaparse来解析如下的表达式:

$(foo bar baz $(frob))

变成像:

[:expr "foo" "bar" "baz" [:expr "frob"]]

我差一点就明白了,但我对模棱两可有困难。下面是我语法的一个简化版本,试图依赖于消极的展望。

代码语言:javascript
复制
(def simple
  (insta/parser
    "expr = <dollar> <lparen> word (<space> word)* <rparen>
     <word> = !(dollar lparen) #'.+' !(rparen)
     <space> = #'\\s+'
     <dollar> = <'$'>
     <lparen> = <'('>
     <rparen> = <')'>"))

(simple "$(foo bar)")

哪些错误:

代码语言:javascript
复制
Parse error at line 1, column 11:
$(foo bar)
          ^
Expected one of:
")"
#"\s+"

在这里,我说过一个词可以是任何字符,以支持如下的表达式:

$(foo () `bar` b-a-z)

注意,一个单词可以包含(),但不能包含$()。不知道如何在语法中表达这一点。问题似乎是<word>太贪婪了,消耗了最后一个),而不是让expr拥有它。

Update删除word中的空白:

代码语言:javascript
复制
(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更多测试用例

代码语言:javascript
复制
(simple2 "$(foo bar ())")
(simple2 "$((foo bar baz))")

更新3全工作解析器

对于任何好奇的人来说,不属于这个问题范围的完整的工作解析器是:

代码语言:javascript
复制
(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> = <'`'>"))

示例用法:

代码语言:javascript
复制
(parse "foo bar (qux) $(clj (map (partial * $(js 45 * 2)) (range 10))) `frob`")

分析:

代码语言:javascript
复制
[: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"]]]]]]

这是我编写的一个聊天机器人的解析器,叶铁伯特。它取代了以前混乱的基于正则表达式的手工解析。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-08-16 22:58:34

我真的不知道instaparser,所以我只读了足够的文档,给了我一种错误的安全感。我也没有测试,我也不知道你的需求是什么。

特别是,我不知道:

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

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

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

您需要清楚地了解这样的事情,以便编写语法(或者,如果碰巧的话,请您提供建议)。

更新:基于注释修改语法。我删除了$ ()的产品,因为它们似乎没有必要,这样的话,尖括号就更容易处理了。

以下是基于回答上述问题“是的,不是的,是的”和一些关于regex格式的随机假设。(我不完全清楚尖括号是如何工作的,但我不认为让括号以您想要的方式输出是很容易的;我满足于将它们作为单个元素输出。如果我想出了什么,我会编辑它的。)

代码语言:javascript
复制
<sequence> = element (<space> element)*
<element> = expr | paren_sequence | word
expr = <'$'> <'('> sequence <')'>
<word> = !('$'? '(') #'([^ $()]|\$[^(])+'
<paren_sequence> = '(' sequence ')' 
<space> = #'\\s+'

希望这能帮点忙。

票数 2
EN

Stack Overflow用户

发布于 2013-08-17 11:26:49

为了让你的两个例子都起作用,你必须做两个改变。

1)添加负查找

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

代码语言:javascript
复制
 <word> = !(dollar lparen) #'[^ ]+(?<!\\))' 

因此,这将修复第一个测试用例:

代码语言:javascript
复制
(simple2 "$(foo bar)")
=> [:expr "foo" "bar"]

2)为最后一个单词添加语法

现在,如果您运行第二个测试用例,它将失败:

代码语言:javascript
复制
(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>的所有其他实例都是可选的。完整的语法如下所示:

代码语言:javascript
复制
(def simple2
  (insta/parser
    "expr = <dollar> <lparen> word* lastword <rparen>
     <word>  = !(dollar lparen) #'[^ ]+' <space>+
     <lastword> = !(dollar lparen) #'[^ ]+(?<!\\))' 
     <space> = #'\\s+'
     <dollar> = <'$'>
     <lparen> = <'('>
     <rparen> = <')'>")) 

而且您的两个测试用例应该工作得很好:

代码语言:javascript
复制
(simple2 "$(foo bar)")
=> [:expr "foo" "bar"]

(simple2 "$(foo () bar)")
=> [:expr "foo" "()" "bar"]

希望这能有所帮助。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18282535

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档