Skip to content

Understanding the API

Jamie Willis edited this page Jan 10, 2021 · 12 revisions

Main Classes and Packages

In Parsley, everything resides within the parsley package, and the major entry point is parsley.Parsley. There are a few modules of note:

  • parsley.Parsley : contains some of the basic and primitive combinators (at least those that aren't methods on parsers).
  • parsley.Combinator : contains handy combinators, this should be your first port of call when you want to do something but are not sure a combinator exists for it. At the very least, the eof combinator is very common.
  • parsley.Char : contains a variety of combinators which deal with characters, key ones include char, satisfy and string.
  • parsley.Implicits : contains the very useful implicit conversion combinators. In particular, importing charLift and stringLift allows you write character and string literals as if they were parsers themselves.
  • parsley.ExpressionParser : contains the machinery needed to generate expression parsers for you based, at its simplest, on a table of operators in order of precedence. This is well worth a look.

Other than these, the class parsley.TokenParser contains useful functionality when instantiated with a parsley.LanguageDef instance. However, most of the combinators it provides as part of this package are tailored to the original Parsec library, so integer literals and string literals are parsed according to the Haskell specification. However, many of the combinators are fully configurable, and, in many cases, have been heavily optimised.

parsley.Parsley: Implicit Library Design

You may notice, if you visit the Parsley[A] class itself that it doesn't have many methods at all: only those that run the parser as well as a handful of methods that you are probably unlikely to use. Where are the combinators?! Well, to support recursive parsers in the nicest way possible, Parsley needs to keep the combinators as lazy as possible. Method invocation is strict in the receiver, which is too strict for Parsley's needs. To that end, Parsley uses implicit classes to provide the functionality via extension methods (and at the same time creates a lazy receiver). These implicit classes can be found in the parsley.Parsley object and are as follows:

  • parsley.Parsley.LazyChooseParsley : defines the ternary combinator ?:, where the receiver is the right-hand side (this is the effect of a trailing : in Scala's names).
  • parsley.Parsley.MapParsley : contains the <#> combinator but where the function is on the left-hand side.
  • parsley.Parsley.LazyParsley : this is the place to go and find the vast majority of the operator and method style combinators. It's so important it needs its own subsection!

parsley.Parsley.LazyParsley: The Parsley Workhorse

The first thing to note about LazyParsley is that its type is perhaps a bit strange. It provides additional functionality to a value of type P. This seems strange at first, but observe that the class itself also takes an implicit function of type P => Parsley[A]. This means that LazyParsley can provide the combinator functionality to any type, so long as that type P is implicitly convertible to a parser. Let's take a concrete example:

import parsley.Parsley.LazyParsley
import parsley.Char.char
import parsley.Implicits.charLift

val p/*: Parsley[Int]*/ = char('a') #> 7
val q/*: Parsley[Int]*/ = 'a' #> 7
println('c'.runParser("c"))

How is it possible that #> worked on Char? Well, the Scala compiler will elaborate this into the following program:

import parsley.Parsley.LazyParsley
import parsley.Char.char
import parsley.Implicits.charLift

val p = new LazyParsley(char('a'))(identity[Parsley[Char]]) #> 7
val q = new LazyParsley('a')(charLift) #> 7
println(charLift('c').runParser("c"))

Fear not: the LazyParsley is a so-called value class (which means it extends AnyVal), and so the object isn't actually constructed at runtime. It's more like LazyParsley.extension_#>(charLift('a'), 7) in principal. This code works because both parsley.Implicits.charLift and parsley.Parsley.LazyParsley were imported and thus in scope for implicit resolution. However, you may write your own implicit conversion from other types (or even for Char) to add additional functionality and tailor to your needs. You can also write your own method based combinators using your own implicit class similar to this one.

Don't worry too much if you don't understand how this works under the hood, just be aware that the "operator" combinators are provided by the LazyParsley class, and you can find them within its documentation.