I've seen several other SO posts relevant here, suggesting that inheritance using traits is the only way out of this problem, but I can't see how to use that here.
I'm writing an assembler which has a directive that allows you to change the model of processor, thereby affecting which set of opcodes are parsable. I have two parser classes, one handling all the directive keywords, and one handling the instructions. (There will be more, for the different processor models). When the 'cpu' directive is parsed, that chooses the appropriate instruction parser. Here's a very cut-down illustration:
import scala.util.parsing.combinator.JavaTokenParsers
class ComposingParser {
sealed abstract class Statement {
}
case class Dinst(value: String) extends Statement
case class Keyword(value: String) extends Statement
class InstructionParser extends JavaTokenParsers {
def directOpcode: Parser[Statement] = j | ldlp | pfix
private def j: Parser[Dinst] = """(?i)J""".r ^^ ( x => Dinst(x.toUpperCase) )
private def ldlp: Parser[Dinst] = """(?i)LDLP""".r ^^ ( x => Dinst(x.toUpperCase) )
private def pfix: Parser[Dinst] = """(?i)PFIX""".r ^^ ( x => Dinst(x.toUpperCase) )
}
class KeywordParser extends JavaTokenParsers {
def program: Parser[Statement] = keys | instruction // the main, top-level parser
def keys: Parser[Keyword] = start | end
private def start: Parser[Keyword] = """(?i)START""".r ^^ ( x => Keyword(x.toUpperCase) )
private def end: Parser[Keyword] = """(?i)END""".r ^^ ( x => Keyword(x.toUpperCase) )
private def instruction: Parser[Statement] = {
val ip = new InstructionParser // will be dynamically instantiating different parsers for different instruction sets so can't use traits
ip.directOpcode
// Error:(46, 16) type mismatch;
// found : ip.Parser[ComposingParser.this.Statement]
// required: KeywordParser.this.Parser[ComposingParser.this.Statement]
// ip.directOpcode
}
// I can't use traits as in https://stackoverflow.com/questions/2650254/scala-how-to-combine-parser-combinators-from-different-objects
// I can't see how to apply the solution from https://stackoverflow.com/questions/40166258/reuse-parser-within-another-parser-with-scala-parser-combinators
// Is it possible to convert an 'ip.Parser[Statement]' into a 'KeywordParser.this.Parser[Statement]' ?
}
}
directOpcode and instruction both return Parser[Statement], why can't I combine them like this? Can self-type annotations help here? Thanks in advance for any assistance you can provide... (or illustrations showing how the solutions posted in the other SO posts cited might help).
From the question, it's not obvious why you "cannot use traits". At least your concrete example does work with traits just fine:
// uses `$ivy`-imports, either run with Ammonite, or remove the import and
// compile using SBT.
import $ivy.`org.scala-lang.modules:scala-parser-combinators_2.12:1.1.1`
import scala.util.parsing.combinator.JavaTokenParsers
class ComposingParser {
sealed abstract class Statement {
}
case class Dinst(value: String) extends Statement
case class Keyword(value: String) extends Statement
trait InstructionParser extends JavaTokenParsers {
def directOpcode: Parser[Statement] = j | ldlp | pfix
private def j: Parser[Dinst] = """(?i)J""".r ^^ ( x => Dinst(x.toUpperCase) )
private def ldlp: Parser[Dinst] = """(?i)LDLP""".r ^^ ( x => Dinst(x.toUpperCase) )
private def pfix: Parser[Dinst] = """(?i)PFIX""".r ^^ ( x => Dinst(x.toUpperCase) )
}
trait InstructionParser2 extends JavaTokenParsers {
def directOpcode2: Parser[Statement] = j | ldlp | pfix
private def j: Parser[Dinst] = """(?i)J""".r ^^ ( x => Dinst(x.toUpperCase) )
private def ldlp: Parser[Dinst] = """(?i)LDLP""".r ^^ ( x => Dinst(x.toUpperCase) )
private def pfix: Parser[Dinst] = """(?i)PFIX""".r ^^ ( x => Dinst(x.toUpperCase) )
}
class KeywordParser extends JavaTokenParsers
with InstructionParser
with InstructionParser2 {
def program: Parser[Statement] = keys | instruction // the main, top-level parser
def keys: Parser[Keyword] = start | end
private def start: Parser[Keyword] = """(?i)START""".r ^^ ( x => Keyword(x.toUpperCase) )
private def end: Parser[Keyword] = """(?i)END""".r ^^ ( x => Keyword(x.toUpperCase) )
private def instruction: Parser[Statement] = {
if (math.random < 0.5) directOpcode else directOpcode2
}
}
}