This library adds a parser interpolator to make cats-parse slightly easier to use.
Prefix | Description | Inputs | Output |
---|---|---|---|
p | Parser Applicative | all Parser |
Parser |
p0 | Parser0 Applicative | all Parser0 |
Parser0 |
pm | Parser Monad | Parser , then either afterwards |
Parser |
libraryDependencies += "net.andimiller" %% "cats-parse-interpolator" % "0.1.0"
You then import the library, and probably cats.parse too
import net.andimiller.cats.parse.interpolator._
import cats.parse._
import cats.implicits._ // if you need extra syntax
Then you can create a parser with a p
prefix like so:
p"hello world".parseAll("hello world")
// res0: Either[Error, Unit] = Right(value = ())
You can interpolate other parsers into it:
{
val name = Parser.charsWhile(_.isLetter)
val number = Parser.charsWhile(_.isDigit).map(_.toInt)
p"$name = $number".parseAll("bob = 10")
}
// res1: Either[Error, Tuple2[String, Int]] = Right(value = ("bob", 10))
And it will even type the output correctly as a tuple of all subparsers.
There is also an interpreter for Parser0
if you need that:
{
val name = Parser.charsWhile0(_.isLetter).map(s => Option(s).filter(_.nonEmpty))
val number = Parser.charsWhile0(_.isDigit).map(s => if (s.isEmpty) 0 else s.toInt)
p0"$name = $number".parseAll(" = ")
}
// res2: Either[Error, Tuple2[Option[String], Int]] = Right(value = (None, 0))
For a simple example, say we want to parse these:
val calculatorInputs = List(
"(+ 2 2)",
"(+ 2 (* 3 6))",
"42"
)
Into these:
enum Expr:
case Number(value: Int)
case Plus(left: Expr, right: Expr)
case Multiply(left: Expr, right: Expr)
Standard | Interpolator |
---|---|
calculatorInputs.map {
Parser.recursive[Expr] { recurse =>
val number = Numbers.digits.map(_.toInt).map(Expr.Number)
val plus = (
Parser.string("(+ ") *> recurse <* Parser.string(" "),
recurse <* Parser.string(")")
).mapN(Expr.Plus)
val multiply = (
Parser.string("(* ") *> recurse <* Parser.string(" "),
recurse <* Parser.string(")")
).mapN(Expr.Multiply)
number.orElse(plus).orElse(multiply)
}.parseAll
}
// res3: List[Either[Error, Expr]] = List(
// Right(value = Plus(left = Number(value = 2), right = Number(value = 2))),
// Right(
// value = Plus(
// left = Number(value = 2),
// right = Multiply(left = Number(value = 3), right = Number(value = 6))
// )
// ),
// Right(value = Number(value = 42))
// ) |
calculatorInputs.map {
Parser.recursive[Expr] { recurse =>
val number = Numbers.digits.map(_.toInt).map(Expr.Number)
val plus = p"(+ $recurse $recurse)".map(Expr.Plus)
val multiply = p"(* $recurse $recurse)".map(Expr.Multiply)
number.orElse(plus).orElse(multiply)
}.parseAll
}
// res4: List[Either[Error, Expr]] = List(
// Right(value = Plus(left = Number(value = 2), right = Number(value = 2))),
// Right(
// value = Plus(
// left = Number(value = 2),
// right = Multiply(left = Number(value = 3), right = Number(value = 6))
// )
// ),
// Right(value = Number(value = 42))
// ) |
For a more complex parser, here's a parser for env variables, taking input like:
val envVars = "FOO=abcC,BAR=def"
Standard | Interpolator |
---|---|
{
val keyStartChars = ('a' to 'z').toSet ++ ('A' to 'Z').toSet ++ "_".toSet
val keyRestChars = keyStartChars ++ ('0' to '9').toSet
val key = for {
head <- Parser.charIn(keyStartChars).string
rest <- Parser.charsWhile0(keyRestChars).string
} yield head + rest
val value = Parser.charsWhile(c => !" ,".toSet(c))
val pair = for {
k <- key
_ <- Parser.char('=')
v <- value
} yield (k -> v)
pair.repSep(Parser.char(','))
}.parseAll(envVars)
// res5: Either[Error, NonEmptyList[Tuple2[String, String]]] = Right(
// value = NonEmptyList(head = ("FOO", "abcC"), tail = List(("BAR", "def")))
// ) |
{
val keyStartChars = ('a' to 'z').toSet ++ ('A' to 'Z').toSet ++ "_".toSet
val keyStart = Parser.charIn(keyStartChars).string
val keyRest = Parser.charsWhile0(keyStartChars ++ ('0' to '9').toSet).string
val key = pm"$keyStart$keyRest".string
val value = Parser.charsWhile(c => !" ,".toSet(c))
p"$key=$value".repSep(Parser.char(','))
}.parseAll(envVars)
// res6: Either[Error, NonEmptyList[Tuple2[String, String]]] = Right(
// value = NonEmptyList(head = ("FOO", "abcC"), tail = List(("BAR", "def")))
// ) |