Понимание Тильды в комбинаторах парсеров 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.