Как я могу сделать условную проверку с помощью комбинаторов парсера - PullRequest
4 голосов
/ 22 июня 2011

Я пытался написать простой движок шаблонов html (для удовольствия) и хочу разобрать структуру вроде этой

A.обычные строки: HTML

B.если строка начинается с $, тогда просмотрите ее как строку кода Java

$ if (isSuper) {
    <span>Are you wearing red underwear?</span>
$ }

C.если ${} переносит несколько строк, весь код в нем должен быть кодом Java.

D.если строка начинается с $include, выполните какой-то трюк в этой строке (вызовите другой шаблон)

$include anotherTemplate(id, name)

, это создаст новый экземпляр anotherTemplate и вызовет render() метод

E.и было бы больше «команд», отличных от $include, таких как $def, $val.

Как я могу выразить это в комбинаторах синтаксического анализа?По сути это условный форк

для 1. и 2. Я получил что-то вроде этого:

'$' ~> ( '{' ~> upto('}') <~ '}' |  not('{') <~ newline )

, где upto заимствовано из парсера Scalate Scamel (который я только начинаючитать и не совсем понимаю)

Я использовал not('{'), чтобы различить $.... строку кода с блоком ${...}.Но это громоздко и не распространяется на другие «команды»

Так как я могу это сделать?

1 Ответ

6 голосов
/ 15 мая 2012

Ваше использование not является избыточным.Метод | реализует упорядоченный выбор ;вторая вещь пробуется, только если первая потерпела неудачу.Это должно сработать:

def directive: Parser[Directive] =
  ( '$' ~>
    ( '{' ~> javaStuff <~ '}'
    | "include" ~> includeDirective
    | "def"     ~> defDirective
    | "val"     ~> valDirective
    | javaDirective
    )
  | htmlDirective
  )

def templateFile: Parser[List[Directive]] = (directive <~ '\n').*

Для более быстрого разбора и более качественных сообщений об ошибках вы должны "фиксировать" ваши парсеры как можно чаще.Я думаю, что это то, что вы пытались получить, когда использовали not('{').

Прямо сейчас, если приведенный выше парсер видит '$', за которым следует '{', а затем нет см. javaStuff, он вернется и рассмотрит каждую из четырех оставшихся '$' -альтернатив в порядке (include, def, val и, наконец, javaDirective), а затем вернется кдо '$', чтобы попробовать htmlDirective, до сбоя с ошибочным сообщением об ошибке.Но если мы видим '{', мы знаем, что ни одна из других альтернатив не может быть успешной, так почему мы должны их проверять?Аналогично, строка, начинающаяся с '$', никогда не может быть htmlDirective.

Мы хотим, чтобы такие вещи, как '{', были точками без возврата;если синтаксический анализатор after- '{' завершается с ошибкой и хочет вернуться назад, мы должны остановить его на своем пути и передать сбой, вызывающий возврат, непосредственно пользователю как ошибку.

Способ сделать это с помощью commit.Эта функция / комбинатор, когда применяется к парсеру p, смотрит на ParseResult, выходящий из p, и изменяет его на Error (сигнал отказа от цели), если он изначально был Failure (сигнал возврата), в противном случае он остается неизменным.При правильном использовании commit синтаксический анализатор directive становится:

def directive: Parser[Directive] =
  ( '$' ~> commit( '{' ~> commit(javaStuff <~ '}')
                 | "include" ~> commit(includeDirective)
                 | "def"     ~> commit(defDirective)
                 | "val"     ~> commit(valDirective
                 | javaDirective
                 )
  | htmlDirective
  )

Когда я впервые научился использовать библиотеку синтаксического анализа, я нашел действительно полезным взглянуть на исходный код для Parsers;это делает некоторые вещи более понятными.

(Несколько других советов: цель append и ParseResult#append состоит в том, чтобы решить, какой сбой из последовательности разбора альтернатив следует распространить на пользователя. Просто пока проигнорируйте их. Также я бы не сталне беспокойтесь слишком о >> / flatMap / into, пока не начнете больше практиковаться, когда придет время, прочитайте объяснение Даниэля Собрала . Наконец, мне никогда не приходилось использовать|||, и вы, вероятно, тоже не будете. Удачного разбора!)

Надеюсь, это поможет.

...