将解析器组合器应用于案例类

时间:2015-07-10 14:44:07

标签: scala parser-combinators

我正在使用Scala的Parser Combinators来解析字符串(没有换行符,人为举例)。

字符串由许多不同的部分组成,我想分别提取并填充案例类。

case class MyRecord(foo: String, bar: String, baz: String, bam: String, bat: String)

object MyParser extends scala.util.parsing.combinator.RegexParsers {

  val foo: Parser[String] = "foo"
  val bar: Parser[String] = "bar"
  val baz: Parser[String] = "baz"
  val bam: Parser[String] = "bam"
  val bat: Parser[String] = "bat"

  val expression: Parser[MyRecord] =
    foo ~ bar ~ baz ~ bam ~ bat ^^ {
      case foo ~ bar ~ baz ~ bam ~ bat => MyRecord(foo, bar, baz, bam, bat)
    }

}

这非常有效,但是有没有办法将匹配结果的各个部分直接应用到案例类而不解构?

val expression: Parser[MyRecord] =
  foo ~ bar ~ baz ~ bam ~ bat ^^ MyRecord

更多信息:我解析的字符串很长很复杂(实际上,它是一个充满长复杂字符串的整个文件)所以改为regexp是不合适的问题。

1 个答案:

答案 0 :(得分:5)

可以使用Shapeless2库。对于给定的:

foldRight

您可以使用~的通用 import shapeless._ object f extends Poly2 { implicit def parser[T, U <: HList] = at[Parser[T], Parser[U]]{(a, b) => for {aa <- a; bb <- b} yield aa :: bb } } val p: Parser[Record] = (foo :: bar :: car :: HNil) .foldRight(success(HNil))(f).map(Generic[Record].from) intead组合解析器:

 scala> parseAll(p, "foo bar car").get
 res50: Record = Record(foo,bar,car)

结果:

~

P.S。内置scala功能的问题在于它们构建了基于::的类型化二叉树,这种树难以遍历并变为元组。 Shapeless解决了这个问题 - 它有自己的基于HList的二叉树,名为foldLeft,它是相似的但有一些有趣的操作,比如转换为元组或案例类(可能是基于宏的)。在这个例子中,我使用flatMap来构建Shapeless-hlist和for-comprehension(在解析器上扩展为foldLeft)以组合解析器,因为它们具有monadic性质。在无形状中,您必须将T的处理程序定义为通用含义集,它可以处理通用输入(如Uf)。

你可以重用我的implicit class as2[A, B](t: Parser[A ~ B]){ def ^^^^[T] (co: (A, B) => T) = t map {tt => val (a ~ b) = tt; co(a, b)} } implicit class as3[A, B, C](t: Parser[A ~ B ~ C]){ def ^^^^[T] (co: (A, B, C) => T) = t map {tt => val (a ~ b ~ c) = tt; co(a, b, c)} } ... implicit class as21 ... 对象以类型安全的方式组合任何解析器(你可以在这里组合甚至不同的类型 - 没关系)。

其次,不太通用的方式是:

scala> val p = foo ~ bar ~ car ^^^^ Record
p: MyParser.Parser[Record] = Parser ()

scala> parseAll(p, "foo bar car").get
res53: Record = Record(foo,bar,car)

用法:

public class MyHandler extends DefaultHandler {

    // Use a DEQUE to track the current position inthe xml.
    private Deque<String> position = new ArrayDeque<>();
    // My data.
    private StringBuilder data = new StringBuilder();

    private String element = null;

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        if (match()) {
            // Append to my buffer.
            data.append(ch, start, length);
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        // Ending a tag - pop it from end.
        position.removeLast();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        // Starting a tag - push it at the end.
        position.addLast(qName);
    }

    public String getElement() {
        return this.element;
    }

    // Specifically looking for the REQUEST.ELEMENT - not the REQUEST.LIST.ELEMENT
    private final String[] lookingFor = {"REQUEST", "ELEMENT"};

    private boolean match() {
        // Must be that deep.
        if (position.size() == lookingFor.length) {
            // Must match.
            Iterator<String> match = position.iterator();
            for (int i = 0; i < lookingFor.length; i++) {
                // Match?
                if (!match.next().equals(lookingFor[i])) {
                    return false;
                }
            }
        } else {
            // Wrong depth.
            return false;
        }
        // No mismatch -> match!
        return true;
    }

}

这不是很酷,但不需要外部库。