I wrote a parser to act as a lexer. This lexer parses a file and returns a list of tokens, each of which is a case class or object that extends a common trait.
I am now trying to write a parser for the output of the lexer, but I have hit a very confusing snag. The parser is happy to implicitly cast my case objects, but throws a fit if I even try to call apply(classHere)
manually.
The following is a simplified version of my code:
// CODE
trait Token
case class StringWrapperIgnoresCase(val string: String) {
private case class InnerWrapper(s: String)
lazy val lower = string.toLowerCase
override lazy val hashCode = InnerWrapper(lower).hashCode
override def equals(that: Any) =
that.isInstanceOf[StringWrapperIgnoresCase] &&
lower == that.asInstanceOf[StringWrapperIgnoresCase].lower
}
case class ID(val text: String)
extends StringWrapperIgnoresCase(text)
with Token {
override def toString = "ID(" + text + ")"
}
case object PERIOD extends Token
object Parser extends Parsers {
type Elem = Token
def doesWork: Parser[Token] = PERIOD
def doesNotWork: Parser[Token] = ID
}
The compiler reports the following message about doesNotWork
:
// ERROR MESSAGE
type mismatch; found : alan.parser.ID.type (with underlying type object alan.parser.ID) required: alan.parser.Parser.Parser[alan.parser.Token]
How can I fix this?
Update: It wasn't clear to me from your question exactly what you were asking, but now that you've specified that you want a parser that matches any ID
in your answer, here's a more idiomatic solution:
val id: Parser[ID] = accept("ID", { case i: ID => i })
Here you've provided a description of what the parser wants (for error messages) and a partial function with ID
s as its domain. You could also use the acceptIf
version that xiefei provides in a comment on your answer.
When you refer to a case class (as opposed to a case object) without a parameter list, you get the automatically generated companion object, which is not an instance of the class itself. Consider the following:
sealed trait Foo
case class Bar(i: Int) extends Foo
case object Baz extends Foo
Now Baz: Foo
is just fine, but Bar: Foo
will give an error very similar to what you're seeing.
Note also that what's happening here isn't strictly casting. The Parsers
trait has a method with the following signature:
implicit def accept(e: Elem): Parser[Elem]
When you write this:
def doesWork: Parser[Token] = PERIOD
You're trying to type an Elem
as a Parser[Elem]
, and the implicit conversion kicks in (see section 7.3 of the spec for more information about implicit conversions). When you write this, on the other hand:
def doesNotWork: Parser[Token] = ID
You're trying to type the ID
companion object (which has type ID.type
, not ID
or Token
, and therefore not Elem
) as a Parser[Elem]
, and there's no implicit conversion that makes this possible.
You're probably better off writing out accept(PERIOD)
and accept(ID("whatever"))
, for now, at least, and obeying the deprecation warning that says the following when you try to compile your code:
Case-to-case inheritance has potentially dangerous bugs which are unlikely to be fixed.