Parsley
![GitHub license](https://camo.githubusercontent.com/816fa57e6311516fcfce5ab5a0865cd0a5da545f43b72ca0be52a927fc6756f8/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6a2d6d6965362f706172736c65792e737667)
What is Parsley?
Parsley is a very fast parser combinator library for Scala based on a Haskell-style Parsec API.
How do I use it?
![Maven Central](https://camo.githubusercontent.com/ab1281a507ebbe61fe87e9013fa96efaaa8fcc48a03d8cab91ea65963093887c/68747470733a2f2f696d672e736869656c64732e696f2f6d6176656e2d63656e7472616c2f762f636f6d2e6769746875622e6a2d6d6965362f706172736c65795f302e32373f6c6162656c3d6d6176656e2d63656e7472616c2d302e3237)
Parsley is distributed on Maven Central, and can be added to your project via:
libraryDependencies += "com.github.j-mie6" %% "parsley" % PARSLEY_VER
Documentation can be found here
Examples
import parsley.Parsley, Parsley._
import parsley.character.{char, string, digit}
import parsley.implicits.character.{charLift, stringLift}
val hello: Parsley[Unit] = void('h' *> ("ello" <|> "i") *> " world!")
hello.parse("hello world!") // returns Success(())
hello.parse("hi world!") // returns Success(())
hello.parse("hey world!") // returns a Failure
val natural: Parsley[Int] = digit.foldLeft1(0)((n, d) => n * 10 + d.asDigit)
natural.parse("0") // returns Success(0)
natural.parse("123") // returns Success(123)
For more see the Wiki!
What are the differences to Haskell's Parsec?
Mostly, this library is quite similar. However, due to Scala's differences in operator characters a few operators are changed:
(<$>)
is known as<#>
ormap
try
is known asattempt
(<$)
and($>)
are<#
and#>
respectively.
In addition, lift2
and lift3
are uncurried in this library: this is to provide better performance and easier usage with Scala's traditionally uncurried functions. There are also a few new operators in general to be found here!
How does it work?
Parsley represents parsers as an abstract-syntax tree AST, which is constructed lazily. As a result, Parsley is able to perform analysis and optimisations on your parsers, which helps reduce the burden on you, the programmer. This representation is then compiled into a light-weight stack-based instruction set designed to run fast on the JVM. This is what offers Parsley its competitive performance, but for best effect a parser should be compiled once and used many times (so-called hot execution).
To make recursive parsers work in this AST format, you must ensure that recursion is done by knot-tying: you should define all recursive parsers with val
and introduce lazy val
where necessary for the compiler to accept the definition.
Bug Reports
![Test Coverage](https://camo.githubusercontent.com/cabef0925e7ddf0326dbf0506d9cd67ea08ee01f132ff21cd2ea79691c56350f/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636c696d6174652f636f7665726167652d6c65747465722f6a2d6d6965362f506172736c6579)
If you encounter a bug when using Parsley, try and minimise the example of the parser (and the input) that triggers the bug. If possible, make a self contained example: this will help me to identify the issue without too much issue.
References
- This work is based on my Master's Thesis (2018) which can be found here
- This work spawned a paper at the Scala Symposium at ICFP 2018: Garnishing Parsec with Parsley