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

3
我想写一个Ruby/Parslet解析器以解析Handlebars,但我卡在了{{ 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是}},块可以是更多或更少的任何内容)

所以我现在需要重写`identifier`规则,使它匹配任何单词但不包括“else”。

提前感谢, 文森特


哦,让我先逃脱.. :( - Arup Rakshit
这些答案中有用的吗? - Nigel Thorne
很遗憾,我还不能确定。我们的项目发生了一些优先级变化,我没有太多时间来处理这个部分的项目,所以我不得不采用我最初找到的快速且简单的解决方案(将{{ else }}作为任何其他标识符并在代码中进行处理(如果找到{{ else }}标识符,则将每个块分成两个部分)。 我希望有一天我能再次有时间处理这个项目的这一部分,并尝试其中一种解决方案... - Vincent
2个回答

0

这取决于您要匹配的语法。如果您不在{{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) }

该程序接受除了"else"之外的所有字符串,即"任何不以else开头的字符串,或者以else开头且至少有一个字符的字符串"。

注意:这让我想到"为什么else如此特殊?"我们是否应该在这里排除所有关键字?

如果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 结尾的想法耦合在一起,这并不严格正确(也无法处理空格)。因此,“路径”不是放置这些内容的正确位置。
如果我们试图阻止替换匹配 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}}" 的出现。


0
一种方法是使用 absent? 前瞻修饰符。如果原子或规则 foo 在此时不匹配,则 foo.absent? 将匹配,并且在不消耗任何输入的情况下执行。
有了这个,您可以将 identifier 规则编写为
rule(:identifier)
    { (else_kw >> dccurly).absent? >> match['a-zA-Z0-9_'].repeat(1) }

1
很久没有回到欧芹项目了,但你的解决方案效果非常好 :) - Vincent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接