Trying out a simple parser combinator, I'm running into compilation errors.
I would like to parse -- "Smith, Joe" into its Name object like Name(Joe, Smith). Simple enough, I guess.
Here is the code related with that:
import util.parsing.combinator._
class NameParser extends JavaTokenParsers {
lazy val name: Parser[Name] =
lastName <~ "," ~> firstName ^^ {case (l ~ f) => Name(f, l)}
lazy val lastName = stringLiteral
lazy val firstName = stringLiteral
}
case class Name(firstName:String, lastName: String)
开发者_运维技巧
And I'm testing it via
object NameParserTest {
def main(args: Array[String]) {
val parser = new NameParser()
println(parser.parseAll(parser.name, "Schmo, Joe"))
}
}
Getting a compilation error:
error: constructor cannot be instantiated to expected type;
found : NameParser.this.~[a,b]
required: java.lang.String
lazy val name: Parser[Name] = lastName <~ "," ~> firstName ^^ {case (l ~ f) => Name(f, l)}
What is that I am missing here?
In this line here:
lazy val name: Parser[Name] =
lastName <~ "," ~> firstName ^^ {case (l ~ f) => Name(f, l)}
you don't want to use both <~
and ~>
. You're creating a parser that matches ","
and firstName
and keeps only ","
, and then you're creating a parser that matches lastName
and the previous parser and keeps only lastName
.
You can replace it with this:
(lastName <~ ",") ~ firstName ^^ {case (l ~ f) => Name(f, l)}
However, although this will compile and combine the way you want, it won't parse what you want it to. I got this output when I tried:
[1.1] failure: string matching regex `"([^"\p{Cntrl}\\]|\\[\\/bfnrt]|\\u[a-fA-F0-9]{4})*"' expected but `S' found
Schmo, Joe
^
stringLiteral
expects something that looks like a string literal in code (something in quotation marks). (JavaTokenParsers
is meant to parse stuff that looks like Java.) This works:
scala> val x = new NameParser
x: NameParser = NameParser@1ea8dbd
scala> x.parseAll(x.name, "\"Schmo\", \"Joe\"")
res0: x.ParseResult[Name] = [1.15] parsed: Name("Joe","Schmo")
You should probably replace it with a regex that specifies what kind of strings you will accept for names. If you look at the documentation here, you'll see:
implicit def regex (r: Regex) : Parser[String]
A parser that matches a regex string
So you can just put a Regex
object there and it will be converted into a parser that matches it.
the ~>
combinator ignores the left side and the <~
combinator ignores the right side. So the result of lastName <~ "," ~> firstName
can never include the results of both firstName
and lastName
. Actually it is only the parse result of lastName
because "," ~> firstName
is ignored. You need to use sequential composition here:
lazy val name: Parser[Name] =
lastName ~ "," ~ firstName ^^ {case (l ~_~ f) => Name(f, l)}
Or if you want a prettier pattern match:
lazy val name: Parser[Name] =
lastName ~ ("," ~> firstName) ^^ {case (l ~ f) => Name(f, l)}
The code
lastName <~ "," ~> firstName
will end up throwing away the result of parsing firstName
. Because of the operator precedence rules in Scala, the statement is parsed as if it were parenthesized like so:
lastName <~ ("," ~> firstName)
but even if it were grouped differently you are still only dealing with three parsers and throwing away the result of two of them.
So you end up with a String
being passed into your mapping function, which is written to expect a ~[String, String]
instead. That's why you get the compiler error you do.
One helpful technique for troubleshooting this sort of thing is to add ascriptions to subexpressions:
lazy val name: Parser[Name] =
((lastName <~ "," ~> firstName): Parser[String ~ String]) ^^ { case l ~ f => Name(f, l) }
which can help you to determine where exactly reality and your expectations diverge.
精彩评论