commonMain.tel.schich.parserkombinator.Parser.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of parser-kombinator Show documentation
Show all versions of parser-kombinator Show documentation
A simple parser combinator framework.
package tel.schich.parserkombinator
typealias Parser = (StringSlice) -> ParserResult
operator fun Parser.invoke(s: String) = this(StringSlice.of(s))
fun Parser.flatMap(f: (T) -> Parser): Parser = trace("flatMap()") { input ->
when (val result = this(input)) {
is ParserResult.Ok -> f(result.value)(result.rest)
is ParserResult.Error -> result
}
}
fun Parser.map(f: (T) -> U): Parser = { input ->
when (val result = this(input)) {
is ParserResult.Ok -> ParserResult.Ok(f(result.value), result.rest)
is ParserResult.Error -> result
}
}
fun Parser.andThenIgnore(next: Parser<*>): Parser = trace("andThenIgnore()") { input ->
when (val result = this(input)) {
is ParserResult.Ok -> when (val nextResult = next(result.rest)) {
is ParserResult.Ok -> ParserResult.Ok(result.value, nextResult.rest)
is ParserResult.Error -> ParserResult.Error(nextResult.message, input)
}
is ParserResult.Error -> result
}
}
infix fun Parser.then(next: Parser<*>): Parser = andThenIgnore(next)
infix fun Parser.concat(next: Parser): Parser = trace("concat()") { input ->
when (val first = this(input)) {
is ParserResult.Ok -> when (val second = next(first.rest)) {
is ParserResult.Ok -> {
val firstValue = first.value ?: ""
val secondValue = second.value ?: ""
ParserResult.Ok(input.subSlice(0, firstValue.length + secondValue.length), second.rest)
}
is ParserResult.Error -> ParserResult.Error(second.message, input)
}
is ParserResult.Error -> first
}
}
fun Parser.andThenTake(trailer: Parser): Parser = trace("andThenTake()") { input ->
when (val result = this(input)) {
is ParserResult.Ok -> when (val trailerResult = trailer(result.rest)) {
is ParserResult.Ok -> trailerResult
is ParserResult.Error -> ParserResult.Error(trailerResult.message, input)
}
is ParserResult.Error -> result
}
}
fun Parser.surroundedBy(prefix: Parser<*>, suffix: Parser<*>): Parser = trace("surroundedBy()") { input ->
when (val prefixResult = prefix(input)) {
is ParserResult.Error -> prefixResult
is ParserResult.Ok -> {
when (val result = this(prefixResult.rest)) {
is ParserResult.Error -> ParserResult.Error(result.message, input)
is ParserResult.Ok -> {
when (val suffixResult = suffix(result.rest)) {
is ParserResult.Error -> ParserResult.Error(suffixResult.message, input)
is ParserResult.Ok -> ParserResult.Ok(result.value, suffixResult.rest)
}
}
}
}
}
}
fun Parser.surroundedBy(parser: Parser<*>): Parser = surroundedBy(parser, parser)
infix fun Parser.or(other: Parser): Parser = takeFirst(this, other)
fun Parser.optional(): Parser = trace("optional()") { input ->
when (val result = this(input)) {
is ParserResult.Ok -> result
is ParserResult.Error -> ParserResult.Ok(null, input)
}
}
fun parseSeparated(a: Parser, separator: Parser<*>, b: Parser): Parser> =
a.andThenIgnore(separator).flatMap { aValue -> b.map { bValue -> Pair(aValue, bValue) } }
fun parseSeparatedList(parser: Parser, separator: Parser<*>, min: Int = 0, max: Int = -1): Parser> = trace("parseSeparatedList(min=$min, max=$max)") { input ->
when (max) {
in 0 until min -> ParserResult.Error("Min ($min) can't be larger than max ($max)!", input)
0 -> ParserResult.Ok(emptyList(), input)
else -> {
when (val first = parser(input)) {
is ParserResult.Error -> first
is ParserResult.Ok -> {
val output = mutableListOf()
var maxRemaining = max
var rest = first.rest
output.add(first.value)
maxRemaining -= 1
while (true) {
val separatorResult = separator(rest)
when (separatorResult) {
is ParserResult.Ok -> {
}
is ParserResult.Error -> {
break
}
}
when (val result = parser(separatorResult.rest)) {
is ParserResult.Ok -> {
rest = result.rest
output.add(result.value)
if (max >= 0 && output.size == max) {
break
}
}
is ParserResult.Error -> {
break
}
}
}
if (output.size < min) ParserResult.Error("only matched ${output.size} times, $min required!", input)
else ParserResult.Ok(output.toList(), rest)
}
}
}
}
}
fun takeFirst(vararg parsers: Parser): Parser = trace("takeFirst(#parsers=${parsers.size})") { input ->
parsers.asSequence().map { it(input) }.firstOrNull { it is ParserResult.Ok }
?: ParserResult.Error("No parser worked!", input)
}
fun parseRepeatedly(parser: Parser, min: Int = 0, max: Int = -1): Parser> = trace("parseRepeatedly(min=$min, max=$max, parser=...)") { input ->
when (max) {
in 0 until min -> ParserResult.Error("Min ($min) can't be larger than max ($max)!", input)
0 -> ParserResult.Ok(emptyList(), input)
else -> {
var rest = input
val output = mutableListOf()
while (true) {
when (val result = parser(rest)) {
is ParserResult.Ok -> {
output.add(result.value)
rest = result.rest
if (max >= 0 && output.size == max) {
break
}
}
is ParserResult.Error -> {
break
}
}
}
if (output.size < min) ParserResult.Error("only matched ${output.size} times, $min required!", input)
else ParserResult.Ok(output, rest)
}
}
}
fun takeWhile(predicate: (Char) -> Boolean): Parser = takeWhile(min = 0, max = -1, predicate = predicate)
private fun takeWhilePredicate(min: Int = 0, max: Int = -1, predicate: (Char) -> Boolean): Parser = { input ->
var len = 0
var left = max
while (left != 0 && len < input.length && predicate(input[len])) {
len += 1
left -= 1
}
if (len >= min) ParserResult.Ok(input.subSlice(0, len), input.subSlice(len))
else ParserResult.Error("$len were recognized, $min required!", input)
}
fun takeWhile(min: Int = 0, max: Int = -1, predicate: (Char) -> Boolean): Parser =
trace("takeWhile(min=$min, max=$max, predicate=$predicate)", takeWhilePredicate(min, max, predicate))
fun takeWhile(min: Int = 0, max: Int = -1, oneOf: Set): Parser =
trace("takeWhile(min=$min, max=$max, oneOf=${forTrace(oneOf)})", takeWhilePredicate(min, max, oneOf::contains))
fun takeWhile(min: Int = 0, max: Int = -1, c: Char): Parser =
trace("takeWhile(min=$min, max=$max, oneOf=${forTrace(c)})", takeWhilePredicate(min, max) { it == c })
fun takeUntil(min: Int = 0, max: Int = -1, predicate: (Char) -> Boolean) =
trace("takeUntil(min=$min, max=$max)", takeWhilePredicate(min, max) { !predicate(it) })
fun takeUntil(min: Int = 0, max: Int = -1, oneOf: Set) =
trace("takeUntil(min=$min, max=$max, oneOf=${forTrace(oneOf)})", takeWhile(min, max) { it !in oneOf })
private fun takeExactlyWhilePredicate(min: Int, max: Int, predicate: (Char) -> Boolean): Parser = { input ->
var len = 0
while (len < input.length && predicate(input[len])) {
len += 1
}
when {
len < min -> ParserResult.Error("$len were recognized, >= $min required!", input)
len > max -> ParserResult.Error("$len were recognized, <= $max required!", input)
else -> ParserResult.Ok(input.subSlice(0, len), input.subSlice(len))
}
}
fun takeExactlyWhile(min: Int, max: Int, oneOf: Set) =
trace("takeExactlyWhile(min=$min, max=$max, oneOf=${forTrace(oneOf)})", takeExactlyWhilePredicate(min, max, oneOf::contains))
private fun takeOneChar(predicate: (Char) -> Boolean): Parser = { input ->
if (input.isEmpty()) ParserResult.Error("no input left!", input)
else {
val c = input[0]
if (predicate(c)) ParserResult.Ok(input.subSlice(0, 1), input.subSlice(1))
else ParserResult.Error("not recognized!", input)
}
}
fun take(c: Char): Parser = trace("take('$c')", takeOneChar { it == c })
fun take(chars: Set): Parser =
trace("take(chars=${forTrace(chars)})", takeOneChar(chars::contains))
fun take(predicate: (Char) -> Boolean): Parser =
trace("take(predicate=...)", takeOneChar(predicate))
fun takeString(s: CharSequence): Parser = trace("take(s=\"${forTrace(s, "\"")}\")") { input ->
if (input.startsWith(s)) ParserResult.Ok(input.subSlice(0, s.length), input.subSlice(s.length))
else ParserResult.Error("$s was not recognized!", input)
}
fun takeString(strings: Set): Parser = trace("take(strings=${forTrace(strings)})") { input ->
val firstMatching = strings.firstOrNull { input.startsWith(it) }
if (firstMatching == null) ParserResult.Error("No string matched!", input)
else ParserResult.Ok(input.subSlice(0, firstMatching.length), input.subSlice(firstMatching.length))
}
fun entireSliceOf(parser: Parser<*>): Parser = trace("entireSliceOf()") { input ->
when (val result = parser(input)) {
is ParserResult.Ok -> ParserResult.Ok(input.subSlice(0, input.length - result.rest.length), result.rest)
is ParserResult.Error -> result
}
}
fun parseEntirely(parser: Parser): Parser = { input ->
when (val result = parser(input)) {
is ParserResult.Ok -> {
if (result.rest.isEmpty()) result
else ParserResult.Error("Parser did not recognize entire input: ${result.rest}", input)
}
is ParserResult.Error -> result
}
}