Scala: парсинг соответствующего токена - PullRequest
5 голосов
/ 27 марта 2012

Я играю с игрушечным парсером HTML, чтобы помочь мне познакомиться с библиотекой Scala для разбора комбинаторов:

import scala.util.parsing.combinator._ 
sealed abstract class Node                                                                    
case class TextNode(val contents : String)  extends Node                                      
case class Element(                                                                           
  val tag : String,                                                                           
  val attributes : Map[String,Option[String]],                                                
  val children : Seq[Node]                                                                    
)  extends Node                                                                               

object HTML extends RegexParsers {                                                            
  val node: Parser[Node] = text | element                                                     
  val text: Parser[TextNode] = """[^<]+""".r ^^ TextNode                                      
  val label: Parser[String] = """(\w[:\w]*)""".r                                              
  val value : Parser[String] = """("[^"]*"|\w+)""".r                                   
  val attribute : Parser[(String,Option[String])] = label ~ (                                 
        "=" ~> value ^^ Some[String] | "" ^^ { case _ => None }                        
      ) ^^ { case (k ~ v) => k -> v }                                                         
  val element: Parser[Element] = (                                                            
    ("<" ~> label ~ rep(whiteSpace ~> attribute) <~ ">" )                                     
      ~ rep(node) ~                                                                           
    ("</" ~> label <~ ">")                                                                    
  ) ^^ {                                                                                      
    case (tag ~ attributes ~ children ~ close) => Element(tag, Map(attributes : _*), children)
  }                                                                                           
}                                                                                             

Я хочу понять, что мне нужен какой-то способ убедиться, что мое открытие и закрытиетеги совпадают.

Я думаю, для этого мне нужен какой-то flatMap комбинатор ~ Parser[A] => (A => Parser[B]) => Parser[B], поэтому я могу использовать открывающий тег для создания синтаксического анализатора для закрывающего тега.Но я не вижу ничего подходящего для этой подписи в библиотеке .

Как правильно это сделать?

Ответы [ 3 ]

5 голосов
/ 28 марта 2012

Вы можете написать метод, который принимает имя тега и возвращает синтаксический анализатор для закрывающего тега с таким именем:

object HTML extends RegexParsers {                   
  lazy val node: Parser[Node] = text | element
  val text: Parser[TextNode] = """[^<]+""".r ^^ TextNode
  val label: Parser[String] = """(\w[:\w]*)""".r
  val value : Parser[String] = """("[^"]*"|\w+)""".r
  val attribute : Parser[(String, Option[String])] = label ~ (
        "=" ~> value ^^ Some[String] | "" ^^ { case _ => None }
      ) ^^ { case (k ~ v) => k -> v }

  val openTag: Parser[String ~ Seq[(String, Option[String])]] =
    "<" ~> label ~ rep(whiteSpace ~> attribute) <~ ">"

  def closeTag(name: String): Parser[String] = "</" ~> name <~ ">"

  val element: Parser[Element] = openTag.flatMap {
    case (tag ~ attrs) =>
      rep(node) <~ closeTag(tag) ^^
        (children => Element(tag, attrs.toMap, children))
  }
}

Обратите внимание, что вам также нужно сделать node ленивым. Теперь вы получаете хорошее чистое сообщение об ошибке для несопоставленных тегов:

scala> HTML.parse(HTML.element, "<a></b>")
res0: HTML.ParseResult[Element] = 
[1.6] failure: `a' expected but `b' found

<a></b>
     ^

Я был немного более многословен, чем необходимо для ясности. Если вам нужна краткость, вы можете пропустить методы openTag и closeTag и написать element, например, так:

val element = "<" ~> label ~ rep(whiteSpace ~> attribute) <~ ">" >> {
  case (tag ~ attrs) =>
    rep(node) <~ "</" ~> tag <~ ">" ^^
      (children => Element(tag, attrs.toMap, children))
}

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

4 голосов
/ 28 марта 2012

В Parser есть flatMap, а также эквивалентный метод с именем into и оператор >>, которые могут быть более удобными псевдонимами (flatMap по-прежнему необходим при использовании в для пониманий).Это действительно верный способ сделать то, что вы ищете.

Кроме того, вы можете проверить соответствие тегов ^?.

3 голосов
/ 28 марта 2012

Вы смотрите не в том месте.Впрочем, это нормальная ошибка.Вы хотите метод Parser[A] => (A => Parser[B]) => Parser[B], но вы смотрели документы Parsers, а не Parser.

Смотрите здесь .

Есть flatMapтакже известный как into или >>.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...