Search code examples
scalatypesinner-classesparser-combinators

Composing scala parser combinators at runtime


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).


Solution

  • 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 
            }
        }
    }