An attempt to implement parser combinators in Kotlin, inspired by Parsec.
space
- any whitespaceupper
- any uppercase letterlower
- any lowercase letterletter
- any letterdigit
- any digit 0..9alphaNum
- any letter or digituint/int
- an integer (unsigned or signed)ulong/long
- a long (unsigned or signed)ufloat/float
- a floating-point number (decimal or e-notation, unsigned or signed)unumber/number
- a floating-point or integer numbereof
- end of inputskipSpaces
- skip arbitrary number of spaces
oneOf(list)
- any character in the provided listnoneOf(list)
- character should not be in the provided listchar(ch)
- character equal to the provided onestring(str)
- string equal to the provided onesatisfy(pred)
- character for which provided predicate returns true
List/Pair/Triple(parsers).chain()
- inverts the structure and returns a parser ofList/Pair/Triple
with chained resultscount(number, parser)
- repeatsparser
as many times as statedchoice(list)
- tries list of parsers in order, returning the first successchain(parsers)
- executes a list of parsers and returns a list of their resultsbetween(open, close, parser)
- runsopen
, thenparser
andclose
, returning the result ofparser
many(parser)
- repeatsparser
0+ times until error occursmany1(parser)
- repeatsparser
1+ times until error occursskipMany(parser)
- repeatsparser
0+ times but does not return any resultskipMany1(parser)
- repeatsparser
1+ times but does not return any resultparser sepBy sep
- repeatsparser
0+ times, separated bysep
parser sepBy1 sep
- repeatsparser
1+ times, separated bysep
parser endBy sep
- likesepBy
, but input must also end withsep
parser endBy1 sep
- likesepBy1
, but input must also end withsep
just(value)
- always returnsvalue
fail(message)
- always fails withmessage
option(value, parser)
- returnsvalue
ifparser
failsoptional(parser)
- tries to runparser
but does not return any result (does not fail)lookahead(parser)
- executesparser
without consuming any inputpA and pB
- returns both results of parserspA
andpB
as a pairpA andF pB
- (and-flat) returns both results of parserspA
andpB
flattened to a list withoutUnit
spA andL pB
- (and-left) returns result ofpA
(left parser) only ifpB
succeedspA andR pB
- (and-right) returns result ofpB
(right parser) only ifpA
succeedspA or pB
- tries to runpA
and if fails, returns result ofpB
parser.map { ... }
- apply function to the result ofparser
parser.flatMap { ... }
- use the successful result ofparser
and return a new parserparser.recoverWith { ... }
- use the error ofparser
and return a new parser
parser % "Error message"
- returns a parser with"Error message"
for error
In order to parse a string with some parser, parse(parser, input)
function should be used.
It returns a Result<T>
which can be either a Success
(contains the parsed value and rest of the input)
or an Error
(contains error message).
val input = "ABCD1234"
val result = parse(letter, input)
when (result) {
is Error -> println(result.message)
is Success -> println(result.value)
}
// should print 'A'
Another possibility is to use run(parser, input)
that directly returns the value (if succeeded) or null
(if error).
val input = "42A"
val result = run(int, input)
// should be 42
val tail = oneOf('+', '-', '*', '/') andF number
val tails = many1(tail).map { it.flatten() }
val expr = number andF tails
val result = run(expr, "12+6.5")
// should be [12, '+', 6.5]
In addition to the combinators and
/andL
/andR
/or
, as well as the modifier map
,
there is a way to build parsers in declarative style.
buildParser
creates a context which takes care of passing the rest of the input
from one parser to the other upon calling .ev()
, all while tracking the return value (Success
or Error
) of each one.
In case of the latter, execution is stopped and the error is returned.
This parser would take two numbers and sum them together.
val sum = buildParser {
val a = int.ev()
char('+').ev()
val b = uint.ev()
a + b
}
val result = parse(sum, "3+4")
// should be Success(value=7, rest=)
There is a possibility to create a parser for an expression that also evaluates it. It is only necessary to provide an operators table where all operators are listed and their functions defined. Operators table is a list of Operator lists ordered in descending precedence.
In this example we support incrementation, multiplication and addition/subtraction,
with ++
having the highest precedence.
val table: OperatorTable<Int> = listOf(
listOf( postfix("++") { it + 1 } ),
listOf( binary("*", Assoc.Left) { x, y -> x * y } ),
listOf(
binary("+", Assoc.Left) { x, y -> x + y },
binary("-", Assoc.Left) { x, y -> x - y }
)
)
val expr = buildExpressionParser(table, uint)
run(expr, "1+2*3") // should be 7
run(expr, "2++") // should be 3