Search code examples
scalaparser-combinators

Discarding several literals in a scala parser combinator with ~> <~


I'm parsing a language with a parser-combinator in Scala, and I'd like to express the CFG rule

stmt -> if (stmt) {stmt}

Naturally, I wish to discard the "if(){}" string literals when constructing my result. The two ways I've considered are ugly.

Method (A) requires us to duplicate the position of the string literals inside the "case" statement:

lazy val stmt: PackratParser[Stmt] =
    (
      // other rules...
      | "if"~"("~stmt~")"~"{"~stmt~"}" ^^ { case _~_~s1~_~_~s2~_ ⇒ If(s1, s2) }
    )

Method (B) requires confusing parenthesis due to the precedence of ~ and ~>.

lazy val stmt: PackratParser[Stmt] =
    (
      // other rules...
      | ("if"~>"("~>stmt)~(")"~>"{"~>stmt<~"}") ^^ { case s1~s2 ⇒ If(s1, s2) }
    )

Ideally I'd like to discard the "(",")" literals without needing parenthesis in my Scala code. Is there a clean way to do this?


Solution

  • The usual way to avoid precedence issues without lots of parentheses is to define some helpers, and that works here pretty nicely, to my eye:

    val cond       = "(" ~> stmt <~ ")"
    val thenClause = "{" ~> stmt <~ "}"
    val ifStmt = "if" ~> cond ~ thenClause ^^ { case s1 ~ s2 => If(s1, s2) }
    

    If this wasn't an option for some reason, I'd definitely avoid your first approach, and I'd adjust the second to make the precedence-managing parentheses match up more cleanly with the syntax—i.e., something like this:

    "if" ~> ("(" ~> stmt <~ ")") ~ ("{" ~> stmt <~ "}") ^^ {
       case s1 ~ s2 => If(s1, s2)
    }
    

    This isn't great, but it's not absolutely unreadable, either.