Scala:解析匹配令牌

时间:2012-03-27 18:47:42

标签: parsing scala

我正在玩玩具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], 所以我可以使用开始标记来构造结束标记的解析器。但我没有看到任何与该签名in the library匹配的内容。

这样做的正确方法是什么?

3 个答案:

答案 0 :(得分:5)

您可以编写一个方法,该方法接受标记名称并返回具有该名称的结束标记的解析器:

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>
     ^
为了清楚起见,我比必要的更加冗长。如果您想要简洁,可以跳过openTagcloseTag方法,并像这样写element,例如:

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

我确信更简洁的版本是可能的,但在我看来,这甚至会走向不可读性。

答案 1 :(得分:4)

Parser上有一个flatMap,还有一个名为into和运算符>>的等效方法,这可能是更方便的别名(flatMap仍需要用于理解)。这确实是一种有效的方式来做你想要的。

或者,您可以检查代码是否与^?匹配。

答案 2 :(得分:3)

你看错了地方。不过,这是一个正常的错误。您需要一个方法Parser[A] => (A => Parser[B]) => Parser[B],但是您查看了Parsers的文档,而不是Parser

here

有一个flatMap,也称为into>>