This repository contains some utilies for parser combinators in the Kotlin programming language. The OSS license can be found in the LICENSE.md file of the repository.
This library is available on JitPack.io. Make sure to add the following Maven repository in your root build.gradle file :
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
You can now add the library modules in your application build.gradle file :
dependencies {
implementation "com.github.alexandrepiveteau.parser-combinators-kotlin:parser-combinators:1.1.0"
implementation "com.github.alexandrepiveteau.parser-combinators-kotlin:parser-combinators-primitives:1.1.0"
}
The library contains a single module, versioned using semantic versioning :
- parser-combinators - Offers some primitives for building
Parser
instances, and combinators for manipulatingParser
instances. - parser-combinators-primitives - Offers some functions for building
Parser
on primitive data types in Kotlin.
A Parser<Input, Output, Error>
is a structure accepting a Input
as input, and returning an Either<Error, Pair<Output, Input>>
, where the Error
case of the Either
represents a problem that occurred while parsing the input, and the Value
case of the Either
represents a pair of the parser output, and the remaining Input
that has not been parsed yet. A parser combinator is a function that accepts one or multiple parsers as input and outputs a different parser.
Each Parser
instance is just an immutable wrapper around a parsing function. Therefore, it is easily possible to create your own instances of Parser
from scratch. Nevertheless, some default Parser
implementations are provided :
/*
* This Parser will always fail, no matter what input sequence is provided to it. This can
* be useful when combining multiple Parsers together.
*/
val l = Parser.fail<String, Int, Throwable> { IllegalArgumentException("This parser always fail.") }
/*
* This Parser uses a lazily evaluated lambda as its argument. It can easily be used to
* recursively call itself. The lambda has the type () -> Parser<Int, String> in this
* example.
*/
val e = Parser.lazy<String, Int, String> { TODO("Make a recursive call, lazily evaluated.") }
/*
* This Parser always succeeds. It will produce a Parser<Unit, T> instance, and therefore
* has can be mapped to produce a "default" value of any type.
*/
val x = Parser.succeed<String, String>().map { 34 } // Parser<String, Int, String>
Multiple combinators are provided as extension functions in the library. For instance, the map { }
function (used at the end of the previous snippet) is a combinator that transforms the value produced by a Parser
. These high-level functions can be used to build high-level Parser
objects, which can for instance contain some custom types.
The following parser combinators are provided as extension functions (in each one of these, the first argument is always the current Parser
instance) :
map(f: (O1) -> O2)
- Returns aParser
that transforms the value produced using a mapping function.flatMap(f: (O1) -> Either<E, O2>)
- Returns aParser
that, likemap {}
, transforms the value produced using a mapping function. It can also transform the value into anEither.Error
, which will make theParser
fail. This can be used when you want to validate your model with some logic that can't be easily built into parsers otherwise.and(other: Parser<I, O2, E>)
- Returns aParser
that pairs the responses of the two combined parsers.after(other: Parser<I, O2, E>)
- Same asand
, but returns only the second value of the pair.before(other: Parser<I, O2, E>)
- Same asand
, but returns only the first value of the pair.or(other: Parser<I, O2, E>)
- Tries the firstParser
instance, and, if it fails, tries theother
instance. Returns aParser
formed of anEither
based on the result of the parsing.flatOr(other: Parser<I, O, E>)
- Same asor
, but because bothParser
have the same type, the resultingEither
can safely be flattened.loop()
- Returns aParser
that applies itself repeatedly, until it fails. Returns aList
of the results of each iteration.