Search code examples
scalaimplicitscala-3fastparsegiven

How to test fastparse parsers in a separate class?


I'm writing a parser, something like,

object MyParser:
  def int[$: P]: P[Int] = digit.rep(1).!.map(s => s.toInt)
  def digit[$: P]: P[Unit] = CharIn("0-9")

Now I'd like to test this in a test class. But I'd like to be able to test each parser separately, so even though in the context of the big parser, the parsers don't have to match the whole string, I'd like to write tests that try to, so partial matches will come out as failures.

class MyParserTest extends AnyFunSuite with Matchers:
  def parseAll[$: P, T](p: => P[T]): P[T] = Start ~ p ~ End

  test("int parses correctly") {
    fastparse.parse("1234", parseAll(MyParser.int)) shouldBe Success(1234, 4)
  }

(see the code in Scastie)

This gives the error

No given instance of type fastparse.ParsingRun[$] was found for an implicit parameter 
of method int in object MyParser

where:    $ is a type variable

which goes away if I just try

fastparse.parse("1234", MyParser.int)

I've tried importing MyParser.given in the test, defining parseAll in MyParser rather than MyParserTest, and general keyboard flailing to no avail.

Is there a way to wrap the parsers in Start and End when I'm testing them, or is there an implicit somewhere that I just can't get access to?


Solution

  • Try using Scala 2 syntax (still working in Scala 3 so far)

    fastparse.parse("1234", implicit p => parseAll(MyParser.int(/*using*/ p)))
    

    https://scastie.scala-lang.org/DmytroMitin/MrFZ0EhiSPeFDHd1IyBhrA

    fastparse.parse("1234", implicit p => parseAll(MyParser.int))
    

    https://scastie.scala-lang.org/DmytroMitin/MrFZ0EhiSPeFDHd1IyBhrA/28

    Possible Scala 3 syntaxes are a little awkward:

    fastparse.parse("1234", p => {given P[_] = p; parseAll(MyParser.int(/*using*/ p))})
    

    https://scastie.scala-lang.org/DmytroMitin/MrFZ0EhiSPeFDHd1IyBhrA/11

    fastparse.parse("1234", p => {given P[_] = p; parseAll(MyParser.int)})
    

    https://scastie.scala-lang.org/DmytroMitin/MrFZ0EhiSPeFDHd1IyBhrA/21

    fastparse.parse("1234", { case p @ given P[_] => parseAll(MyParser.int(/*using*/ p))})
    

    https://scastie.scala-lang.org/DmytroMitin/MrFZ0EhiSPeFDHd1IyBhrA/13

    fastparse.parse("1234", { case given P[_] => parseAll(MyParser.int)})
    

    https://scastie.scala-lang.org/DmytroMitin/MrFZ0EhiSPeFDHd1IyBhrA/20

    fastparse.parse("1234", p => parseAll(MyParser.int(/*using*/ p))(/*using*/ p))
    

    https://scastie.scala-lang.org/DmytroMitin/MrFZ0EhiSPeFDHd1IyBhrA/16

    It would be nice if fastparse.parse("1234", p ?=> parseAll(MyParser.int(p))) worked, but this would require support on fastparse side.


    correct Scala 3 syntax for providing a given from a higher order function argument

    https://github.com/scala/scala3/discussions/12007