scalafastparse

FastParse. How to enforce exactly once rule


I am trying to implement the following grammer using FastParse API.

  1. Expr can contain only Foo,Bar,Baz sub expressions
  2. Expr must contain atleast 1 sub expression Foo/Bar/Bar. It cannot be empty
  3. Foo/Bar/Baz can appear in any order inside Expr.
  4. Foo/Bar/Baz cannot repeat so you can use them only once

So a valid expression is Expr(Baz(10),Foo(10),Bar(10)) and invalid expression is Expr() or Expr(Bar(10),Bar(10))

So far I have written this code which can enforce and parse 1, 2, 3, rules. but rule no 4 is proving to be tricky.

import fastparse.noApi._
import fastparse.WhitespaceApi

object FastParsePOC {

   val White = WhitespaceApi.Wrapper{
      import fastparse.all._
      NoTrace(" ".rep)
   }

   def print(input: Parsed[(String, String, Seq[(String, String)])]) : Unit = {
      input match {
         case Parsed.Success(value, index) =>
            println(s"${value._1} ${value._2}")
            value._3.foreach{case (name, index) => println(s"$name $index")}
         case f @ Parsed.Failure(error, line, col) => println(s"Error: $error $line $col ${f.extra.traced.trace}")
      }
   }

   def main(args: Array[String]) : Unit = {
      import White._
      val base = P("(" ~ (!")" ~ AnyChar).rep(1).! ~ ")")
      val foo = P("Foo".! ~ base)
      val bar = P("Bar".! ~ base)
      val baz = P("Baz".! ~ base)
      val foobarbaz = (foo | bar | baz)
      val parser = P("Expr" ~ "(" ~ foobarbaz ~ ",".? ~ (foobarbaz).rep(sep=",") ~ ")")
      val input3 = "Expr(Baz(20),Bar(10),Foo(30))"
      val parsed = parser.parse(input3)
      print(parsed)
   }
}

Solution

  • You may check the "exactly-once" constraint with a filter call:

    test("foo bar baz") {
      val arg: P0 = P("(") ~ (!P(")") ~ AnyChar).rep(1) ~ ")"
    
      val subExpr: P[String] = (P("Foo") | P("Bar") | P("Baz")).! ~ arg
    
      val subExprList: P[Seq[String]] = subExpr.rep(min = 1, sep = P(",")).filter { list =>
        list.groupBy(identity[String]).values.forall(_.length == 1)
      }
    
      val expr: P[Seq[String]] = P("Expr") ~ "(" ~ subExprList ~ ")"
    
      expr.parse("Expr(Foo(10))").get.value
    
      expr.parse("Expr(Foo(10),Bar(20),Baz(30))").get.value
    
      intercept[Throwable] {
        expr.parse("Expr()").get.value
      }
    
      intercept[Throwable] {
        expr.parse("Expr(Foo(10),Foo(20))").get.value
      }
    }