Понимание Тильды в комбинаторах парсеров Scala

Я довольно новичок в Scala и читаю о комбинаторах парсеров (Магия За Комбинаторами Парсеров, доменные языки в Scala) я наткнулся на определения методов, как это:

def classPrefix = "class" ~ ID ~ "(" ~ formals ~ ")"

Я читал через API doc scala.утиль.разбор.Парсеры, которые определяют метод с именем (Тильда), но я все еще не понимаю его использования в приведенном выше примере. В этом примере (tilde) является методом, который вызывается Ява.ленг.Строка, которая не имеет этого метода и вызывает сбой компилятора. Я знаю, что (Тильда) определяется как

case class ~ [+a, +b] (_1: a, _2: b)

но как это поможет в приведенном выше примере?

Я был бы рад, если бы кто-нибудь мог дать мне подсказку, чтобы понять, что здесь происходит. Большое спасибо заранее!

января

3 ответов


структура здесь немного сложная. Во-первых, обратите внимание, что вы всегда определяете эти вещи внутри подкласс парсер, например,class MyParser extends RegexParsers. Теперь вы можете отметить два неявных определения внутри RegexParsers:

implicit def literal (s: String): Parser[String]
implicit def regex (r: Regex): Parser[String]

что они будут делать, это взять любую строку или регулярное выражение и преобразовать их в парсер, который соответствует этой строке или этому регулярному выражению в качестве токена. Они неявны, поэтому они будут применяться в любое время, когда они необходимы (например, если вы вызываете метод на Parser[String] это String (или Regex) не имеет).

а что это Parser вещь? Это внутренний класс, определенный внутри Parsers, в supertrait для RegexParser:

class Parser [+T] extends (Input) ⇒ ParseResult[T]

похоже, что это функция, которая принимает входные данные и сопоставляет их с результатом. Ну, это имеет смысл! И вы можете увидеть документацию для него здесь.

теперь мы можем просто искать ~ способ:

def ~ [U] (q: ⇒ Parser[U]): Parser[~[T, U]]
  A parser combinator for sequential composition
  p ~ q' succeeds if p' succeeds and q' succeeds on the input left over by p'.

Итак, если мы видим что-то как

def seaFacts = "fish" ~ "swim"

что происходит, во-первых, "fish" нет ~ метод, поэтому он неявно преобразуется в Parser[String] что делает. The ~ метод тогда хочет аргумент типа Parser[U], и поэтому мы неявно преобразуем "swim" на Parser[String] (т. е. U ==String). Теперь у нас есть что-то, что будет соответствовать входу "fish", и того, что осталось на входе должно соответствовать "swim", и если оба случая, то seaFacts преуспеет в своем матче.


на ~ метод на парсере объединяет два парсера в одном, который применяет два исходных парсера последовательно и возвращает два результата. Это может быть просто (в Parser[T])

def ~[U](q: =>Parser[U]): Parser[(T,U)]. 

если вы никогда не объединяли более двух парсеров, это было бы нормально. Однако, если вы приковываете три из них,p1, p2, p3, С возвратом типа T1, T2, T3, потом p1 ~ p2 ~ p3, что означает p1.~(p2).~(p3) типа Parser[((T1, T2), T3)]. И если вы объедините пять из них, как в вашем например, это было бы Parser[((((T1, T2), T3), T4), T5)]. Затем, когда вы шаблон матч на результат, вы бы тоже все эти parantheses :

case ((((_, id), _), formals), _) => ...

это довольно неудобно.

затем идет умный синтаксический трюк. Когда класс case имеет два параметра, он может отображаться в infix, а не в позиции префикса в шаблоне. То есть, если у вас есть case class X(a: A, b: B), вы можете сопоставить шаблон с case X(a, b), но и case a X b. (Вот что делается с шаблоном x::xs в матче не пустой список,:: является классом case). Когда вы пишете делу a ~ b ~ c, это значит case ~(~(a,b), c), но гораздо приятнее, и приятнее, чем case ((a,b), c) тоже, что сложно сделать правильно.

так ~ метод в Parser возвращает Parser[~[T,U]] вместо Parser[(T,U)], так что вы можете легко подобрать рисунок в результате множественных ~. Кроме того, ~[T,U] и (T,U) в значительной степени то же самое, как изоморфные, как вы можете получить.

такое же имя выбрано для комбинирование метода в синтаксическом анализаторе и для типа результата, поскольку полученный код является естественным для чтения. Сразу видно, как каждая часть обработки результатов относится к элементам правила грамматики.

parser1 ~ parser2 ~ parser3 ^^ {case part1 ~ part2 ~ part3 => ...}

Тильда выбрана потому, что ее приоритет (он связывается плотно) хорошо играет с другими операторами парсера.

последний пункт, вспомогательные операторы ~> и <~ которые отбрасывают результат одного из операндов, обычно константы части в правиле, которые не содержат полезных данных. Так что лучше написать

"class" ~> ID <~ ")" ~ formals <~ ")"

и получить только значения ID и формалей в результате.


вы должны оформить заказ Парсеры.Парсер. Scala иногда определяет метод и класс case с тем же именем, чтобы помочь сопоставлению шаблонов и т. д., И это немного запутанно, если Вы читаете Scaladoc.

в частности, "class" ~ ID то же самое, что "class".~(ID). ~ - Это метод, который последовательно объединяет парсер с другим парсером.

здесь неявное преобразование определена в RegexParsers это автоматически создает парсер из String значение. Итак,"class" автоматически становится экземпляром Parser[String].

val ID = """[a-zA-Z]([a-zA-Z0-9]|_[a-zA-Z0-9])*"""r

RegexParsers также определяет другое неявное преобразование, которое автоматически создает парсер из Regex значение. Итак,ID автоматически становится экземпляром Parser[String] тоже.

путем объединения двух парсеров, "class" ~ ID возвращает Parser[String] это соответствует буквальному "class", а затем регулярному выражению ID появившись последовательно. Есть и другие методы, такие как | и |||. Для получения дополнительной информации прочитайте программирование в Scala.