Cats Parse
an introduction
What is a parser?!?
{
"hello": "world"
}
final case class Hello(name: String)
Hello("world")
What is cats??
Functional Programming Abstractions
parser combinators
p1 and p2 or p3 and p4
p1 and p2 or p3 and p4
p1 ~ p2 orElse p3 ~ p4
~ product
*> product right
<* product left
~ product
*> product right
<* product left
"ignore" LHS
"ignore" RHS
Fundamentals
January Scala Monthly
sealed abstract class Binary extends Product with Serializable
object Binary {
case object Zero extends Binary
case object One extends Binary
}
val one: Parser[Binary] = {
char('0').as(Binary.Zero) orElse char('1').as(Binary.One)
}
"1"
Binary.One
import cats.parse.Rfc5234._
final case class LetterAndNumber(value: String) extends AnyVal
val two: Parser[LetterAndNumber] = {
(alpha ~ digit).string.map(LetterAndNumber)
}
"B2"
LetterAndNumber("B2")
final case class BinaryList(value: NonEmptyList[Binary]) extends AnyVal
val three: Parser[BinaryList] = {
val binary = char('0').as(Binary.Zero) orElse char('1').as(Binary.One)
binary.rep.map(BinaryList)
}
"1011"
BinaryList(NEL.of(One, Zero, One, One))
final case class Name(value: String) extends AnyVal
val four: Parser[Name] = {
val allowedChars = List(char('\''), alpha, sp)
(alpha ~ oneOf(allowedChars).rep).string.map(Name)
}
"Sam O'Brien"
Name("Sam O'Brien")
final case class Score(left: Int, right: Int)
val five: Parser[Score] = {
val multiDigit = digit.rep.string.map(_.toInt)
((multiDigit <* char('-').surroundedBy(sp)) ~ multiDigit).map(Score.tupled)
}
"123 - 456"
Score(123, 456)
sealed abstract class MyTuple extends Product with Serializable
object MyTuple {
final case class Two(one: String, two: String) extends MyTuple
final case class Three(one: String, two: String, three: String) extends MyTuple
}
val six: Parser[MyTuple] = {
val comma = char(',')
val item = alpha.rep.string
val two = ((item <* comma) ~ item).map(MyTuple.Two.tupled)
val three = (item ~ item.surroundedBy(comma) ~ item).map {
case ((a, b), c) => MyTuple.Three(a, b, c)
}
three.backtrack orElse two
}
"one,2,three"
MyTuple.Three("one", "2", "three")
final case class UserName(value: String) extends AnyVal
val seven: Parser[UserName] = {
val name = alpha.rep.string
((name.soft ~ char('.')).rep0.with1 ~ name).string.map(UserName)
}
"jess.day.one"
UserName("jess.day.one")
"schmidt"
def eight(allowedChars: List[Char]): Parser[String] = {
charIn(allowedChars).rep.string
}
Allowed chars: List('a', 'b', 'c')
"abc"
Input string: "abc"
final case class CarType(make: String, model: Option[String])
val nine: Parser[CarType] = {
val make = alpha.rep.string
val model = alpha.rep.string.?
((make <* sp.?) ~ model).map(CarType.tupled)
}
"Nissan Versa"
CarType("Nissan", Some("Versa"))
"Nissan"
The Parser's Gambit
January Scala Monthly Challenge
1. Nf3 Nf6 2. c4 g6 3. Nc3 Bg7 4. d4 O-O 5. Bf4 d5 6. Qb3 dxc4 7. Qxc4 c6 8. e4
Nbd7 9. Rd1 Nb6 10. Qc5 Bg4 11. Bg5 Na4 12. Qa3 Nxc3 13. bxc3 Nxe4 14. Bxe7 Qb6
15. Bc4 Nxc3 16. Bc5 Rfe8+ 17. Kf1 Be6 18. Bxb6 Bxc4+ 19. Kg1 Ne2+ 20. Kf1 Nxd4+
21. Kg1 Ne2+ 22. Kf1 Nc3+ 23. Kg1 axb6 24. Qb4 Ra4 25. Qxb6 Nxd1 26. h3 Rxa2 27.
Kh2 Nxf2 28. Re1 Rxe1 29. Qd8+ Bf8 30. Nxe1 Bd5 31. Nf3 Ne4 32. Qb8 b5 33. h4 h5
34. Ne5 Kg7 35. Kg1 Bc5+ 36. Kf1 Ng3+ 37. Ke1 Bb4+ 38. Kd1 Bb3+ 39. Kc1 Ne2+ 40.
Kb1 Nc3+ 41. Kc1 Rc2# 0-1
Portable Game Notation (PGN)
< 100 lines of code to parse 🤯
Cats Parse
By lewisjkl
Cats Parse
- 661