rubyregexparslet

Parslet: recognise anything but a given keyword


I'm trying to write a Ruby/Parslet parser for Handlebars but I'm getting stuck with the {{ else }} keyword. To explain brieflt for those who do not use Handlebars, an if/else is written this way:

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

but it becomes tricky as the inlining and the helpers can use the same syntax, for example:

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

So I first made a rule to recognise the replacements:

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

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

Which match anything like {{name}} or {{people.name}}. The problem of course is that is also matches the {{ else }} block. Here is how I've written the rule to match an if/else block:

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
}

(note: docurly is {{, dccurly is }} and block can be more or less anything)

So my need now is to rewrite the `identifier``rule so it matches any word but not "else".

Thanks in advance, Vincent


Solution

  • One way to do this is to use the absent? lookahead modifier. foo.absent? will match if the atom or rule foo does not match at this point, and does so without consuming any input.

    With this in hand, you could write the identifier rule as

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