Parslet:识别给定关键字以外的任何内容

Parslet: recognise anything but a given keyword

我正在尝试为 Handlebars 编写一个 Ruby/Parslet 解析器,但我被 {{ else }} 关键字卡住了。 为了给不使用Handlebars的人解释一下,if/else是这样写的:

{{#if my_condition}}
  show something
{{else}}
  show something else
{{/if}}

但它变得棘手,因为内联和助手可以使用相同的语法,例如:

Name: {{ name }}
Address: {{ address }}

所以我首先制定了一个规则来识别替换:

rule(:identifier)  { match['a-zA-Z0-9_'].repeat(1) }
rule(:path)        { identifier >> (dot >> identifier).repeat }

rule(:replacement) { docurly >> space? >> path.as(:item) >> space? >> dccurly}

匹配 {{name}}{{people.name}} 之类的任何内容。 问题当然是它也匹配 {{ else }} 块。以下是我编写规则以匹配 if/else 块的方式:

rule(:else_kw) {str('else')}
rule(:if_block) {
  docurly >>
  str('#if') >>
  space >>
  path.as(:condition) >>
  space? >>
  dccurly >>
  block.as(:if_body) >>
  (
    docurly >>
    else_kw >>
    dccurly >>
    block.as(:else_body)
  ).maybe >>
  docurly >>
  str('/if') >>
  dccurly
}

(注意:docurly 是 {{,dccurly 是 }},block 可以是或多或少的任何东西)

所以我现在需要重写 `identifier`` 规则,使其匹配任何单词但不匹配 "else"。

提前致谢, 文森特

一种方法是使用 absent? 前瞻修饰符。如果原子或规则 foo 此时 匹配,并且不消耗任何输入,foo.absent? 将匹配。

有了这个,您可以将 identifier 规则写成

rule(:identifier)
    { (else_kw >> dccurly).absent? >> match['a-zA-Z0-9_'].repeat(1) }

这取决于您要匹配的语法。如果您不在 {{if}} {{/if}} 对中,应该将 {{else}} 视为有效标识符还是语法错误?如果你有一条带有 a.else.b 的路径,那应该有效吗?

如果 a.else.b 无效,您可以执行以下操作:

rule(:identifier)
    { (else_kw).absent? >> match['a-zA-Z0-9_'].repeat(1) | else_kw >> match['a-zA-Z0-9_'].repeat(1) }

通过说 "any string not starting with else, OR strings starting with else that have at least one more character".

接受除 "else" 之外的所有字符串

注意:这让我觉得 "Why is else so special?" 我们应该在此处排除所有关键字吗?

如果 a.else.b 有效,则不能在标识符级别排除它。然后更准确地说你的 path 不能是 "else".

如果你说:

rule(:path)        { else_kw.absent? >> (identifier >> (dot >> identifier).repeat) }

这将排除任何以 'else' 开头的标识符,例如"elsewise.option"

所以.. absent? 也需要匹配一些东西来显示你的块已经结束。

rule(:path)        { (else_kw >> dccurly).absent? >> (identifier >> (dot >> identifier).repeat) }

这里的问题是我们现在将路径与它以 dccurly 结尾的想法结合起来,这并不严格正确(并且不处理空格)。所以 "path" 不是放置这些东西的正确位置。

如果我们试图阻止替换匹配 else,那会更容易。

rule(:replacement) { docurly >> space? >> (else_kw >> space? >> dccurly).absent? >> path.as(:item) >> space? >> dccurly}

这会阻止替换匹配 else,但会允许 elsewise.somethingelse.something

如果你不想要 "else.something" 那么你需要这样的东西:

rule(:replacement) { docurly >> space? >> (else_kw >> (space | dccurly | dot)).absent? >> path.as(:item) >> space? >> dccurly}

这样 "else " "else." 和 "else}}" 都被阻止了。