Статьи

Scala Parser Combinators => Победа

Parser combinators — это классная концепция, очень хороший инструмент в вашем наборе инструментов, если вы работаете с внешними DSL. Я играл с ними немного недавно. Комбинирование различных парсеров с использованием функций высшего порядка — это весело, особенно если вы используете Scala.

Комбинаторы Parser предоставляются в виде библиотеки в Scala на базовом языке. Давайте использовать пример, чтобы пройтись по деталям …

Проблема: HTTP-заголовок accept позволяет клиенту запрашивать, какой тип контента он предпочитает. Для этого упражнения давайте просто разберем заголовок в списке объектов AcceptHeader. Сортировка списка по фактору качества (или значению q) тривиальна и не связана с этим обсуждением, поэтому пропускаем.

Согласно спецификации HTTP грамматика для заголовка Accept —

Accept = «Accept» «:» # (media-range [accept-params])
media-range = («* / *»
| (type «/» «*»)
| (type «/» subtype)
) * ( «;» параметр)
accept-params = «;» «q» «=» qvalue * (accept-extension)
accept-extension = «;» токен [«=» (токен | строка в кавычках)

Пример такого заголовка: application / html; q = 0,8, text / *, text / xml, application / json; q = 0,9

Подход

Теперь цель состоит в том, чтобы проанализировать значение заголовка в списке объектов AcceptHeader, где AcceptHeader определен как класс case:

case class AcceptHeader(mediaType: String, mediaSubType: String, qualityFactor: Float)

Ниже приведен возможный подход к разбору заголовка accept с использованием метода комбинатора:

import util.parsing.combinator._

object AcceptHeaderParser extends JavaTokenParsers {
lazy val accept: Parser[List[AcceptHeader]] = rep1sep(acceptEntry, ",")
lazy val acceptEntry: Parser[AcceptHeader] = (mediaType <~ "/") ~ mediaSubType ~ opt(qualityFactor) ^^ {
case t ~ st ~ Some(q) => AcceptHeader(t, st, q.toFloat)
case t ~ st ~ None => AcceptHeader(t, st, 1.0F)
}
lazy val wordRegex = """[\w+\-*]*""".r
lazy val mediaType = wordRegex
lazy val mediaSubType = wordRegex
lazy val qualityFactor = ";" ~> "q" ~> "=" ~> floatingPointNumber

def parse(input: String): List[AcceptHeader] = parseAll(accept, input).getOrElse(Nil)
}

Примечание. В этом примере я не реализовал accept-extension, определенный в грамматике спецификации.

Теперь давайте посмотрим на различные аспекты кода:
[нажмите на картинку, чтобы увеличить]

  • Сначала посмотрите на ленивый val acceptEntry:

    • mediaType <~ «/» указывает, что результат разбора косой черты («/») не имеет значения и переносит результат только слева (это относится к mediaType).
    • ~ это метод в признаке Parsers, который обозначает последовательный комбинатор.
    • Метод opt обозначает необязательное значение для коэффициента качества (q).
    • ^^ — это метод в черте Parsers — он имеет парсер слева и функцию справа (в данном случае выполняется сопоставление регистров). Если попытка синтаксического анализа слева успешна, она применяет функцию справа к результату анализа.
  • Последующие строки раскрываются и определяют каждый из парсеров, определенных в acceptEntry

    • Регулярное выражение определено для типа и подтипа мультимедиа с учетом буквенно-цифровых значений, дефиса (-) и звездочки (*).
    • Для qualityFactor: «;» ~> «q» ~> «=» ~> плавающая точкаNumber — игнорирует все проанализированные результаты слева, так как мы заинтересованы только в том, чтобы узнать, каково значение q, которое определяется как floatingPointNumber
  • Теперь вернитесь к первой строке, которая говорит, что accept — это rep1sep (acceptEntry, «,»). rep1sep — это метод в черте Parsers. Мы говорим, что запись accept будет повторяться один или несколько раз, и каждая запись будет разделена запятой («,)

Вы можете проверить функциональность через

object AcceptHeaderTest {
def main(args: Array[String]) {
println(AcceptHeaderParser.parseAndOrder(""" application/html;q=0.8, text/*, text/xml, application/json;q=0.9 """))
}
}

Вывод: список (AcceptHeader (приложение, html, 0.8), AcceptHeader (текст, *, 1.0), AcceptHeader (текст, xml, 1.0), AcceptHeader (приложение, json, 0.9))

Мы просто поцарапали поверхность здесь. Debasish Гоши «s DSL , в действии посвятил главу для синтаксического анализа комбинаторов, которые помогли мне совсем немного в продвижении моего понимания. (Очень рекомендую книгу Гоша, если вы думаете о реализации DSL).

 

От http://www.suryasuravarapu.com/2011/04/scala-parser-combinators-win.html