Parslet: узнавать что угодно, кроме заданного ключевого слова
Я пытаюсь написать парсер Ruby/Parslet для Handlebars, но я застреваю с {{ 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
}
(примечание: документально {{
дккурли есть }}
и блокировать можно более или менее все что угодно)
Теперь мне нужно переписать правило "identifier", чтобы оно соответствовало любому слову, но не "else".
Заранее спасибо Винсент
2 ответа
Это зависит от синтаксиса, который вы пытаетесь сопоставить. Если вы не были внутри {{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.something
, или же else.something
,
Если вы не хотите "else.something", то вам нужно что-то вроде этого:
rule(:replacement) { docurly >> space? >> (else_kw >> (space | dccurly | dot)).absent? >> path.as(:item) >> space? >> dccurly}
так что "еще" "еще". и "else}}" все запрещено.
Один из способов сделать это - использовать absent?
модификатор lookahead. foo.absent?
будет соответствовать, если атом или правило foo
не совпадает в этой точке, и делает это без использования какого-либо ввода.
Имея это в виду, вы можете написать identifier
править как
rule(:identifier)
{ (else_kw >> dccurly).absent? >> match['a-zA-Z0-9_'].repeat(1) }