Skip to content

Commit

Permalink
Merge pull request #15 from alllex/and-tuples
Browse files Browse the repository at this point in the history
And tuples
  • Loading branch information
alllex committed Aug 1, 2023
2 parents b3883a8 + fd3c9c5 commit 1e47171
Show file tree
Hide file tree
Showing 15 changed files with 922 additions and 272 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ object FasterJsonGrammar : Grammar<Json>() {
private val jsonNum by numToken map { Json.Num(it.text.toDouble()) }
private val jsonStr by str map { Json.Str(it) }

private val keyValue by str * -colon and ref(::jsonValue)
private val keyValue by str and -colon and ref(::jsonValue) map { it.toPair() }
private val jsonObj by -lbrace * separated(keyValue, comma) * -rbrace map { Json.Obj(it.toMap()) }

private val jsonArr by -lbracket * separated(ref(::jsonValue), comma) * -rbracket map { Json.Arr(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ object NaiveJsonGrammar : Grammar<Json>() {
private val jsonNum by regexToken("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?") map { Json.Num(it.text.toDouble()) }
private val jsonStr by str map { Json.Str(it) }

private val keyValue by str * -colon and ref(::jsonValue)
private val jsonObj by -lbrace * parser { split(keyValue, comma) } * -rbrace map { Json.Obj(it.toMap()) }
private val keyValue by str * -colon and ref(::jsonValue) map { it.toPair() }
private val jsonObj by -lbrace * separated(keyValue, comma) * -rbrace map { Json.Obj(it.toMap()) }

private val jsonArr by -lbracket * parser { split(jsonValue, comma) } * -rbracket map { Json.Arr(it) }
private val jsonArr by -lbracket * separated(ref(::jsonValue), comma) * -rbracket map { Json.Arr(it) }
private val jsonValue: Parser<Json> by jsonNull or jsonTrue or jsonFalse or jsonNum or jsonStr or jsonArr or jsonObj
override val root by jsonValue
}
27 changes: 0 additions & 27 deletions src/commonMain/kotlin/me/alllex/parsus/parser/Grammar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package me.alllex.parsus.parser

import me.alllex.parsus.token.EofToken
import me.alllex.parsus.token.Token
import kotlin.jvm.JvmName
import kotlin.reflect.KProperty

/**
Expand Down Expand Up @@ -110,32 +109,6 @@ abstract class Grammar<out V>(
return parseOrThrow(input)
}

/**
* Creates a parser that runs the given parser, but always returns `Unit`.
*
* @see times
*/
operator fun Parser<*>.unaryMinus(): Parser<Unit> = ignored(this)

/**
* Creates a parser that ignores the result of the first parser and returns the result of the second parser.
*/
@JvmName("ignoredTimesParser")
operator fun <T> Parser<Unit>.times(p: Parser<T>): Parser<T> = parser(firstTokens) {
this@times()
p()
}

/**
* Creates a parser that runs both parsers, but only returns the result of the first parser.
*/
@JvmName("parserTimesIgnored")
operator fun <T> Parser<T>.times(ignored: Parser<Unit>): Parser<T> = parser(firstTokens) {
this@times().also {
ignored()
}
}

override fun toString(): String {
return "Grammar(${_tokens.size} tokens, root = $root)"
}
Expand Down
41 changes: 41 additions & 0 deletions src/commonMain/kotlin/me/alllex/parsus/parser/TupleParser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package me.alllex.parsus.parser


internal class TupleParser<out T>(
internal val parsers: List<Parser<Any?>>,
internal val transform: (List<Any?>) -> T,
) : ParserImpl<T>(
null,
firstTokens = parsers.first().firstTokens
) {

val arity: Int get() = parsers.count { it !is SkipParser<*> }

override suspend fun ParsingScope.parse(): T {
val items = buildList {
for (parser in parsers) {
if (parser is SkipParser<*>) {
parser()
} else {
val item = parser()
add(item)
}
}
}

return transform(items)
}

override fun toString(): String {
return "TupleParser(parsers=$parsers)"
}

}

internal fun Parser<*>.tryUnwrapTupleParsers(): List<Parser<Any?>> =
if (this is TupleParser<*>) parsers else listOf(this)

internal fun <T> retuple(parser1: Parser<Any?>, parser2: Parser<Any?>, transform: (List<Any?>) -> T): TupleParser<T> {
val newParsers = parser1.tryUnwrapTupleParsers() + parser2
return TupleParser(newParsers, transform)
}
36 changes: 0 additions & 36 deletions src/commonMain/kotlin/me/alllex/parsus/parser/combinators.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
package me.alllex.parsus.parser

/**
* Creates a parser from a pair of parsers, returning a pair of their results.
*/
infix fun <A, B> Parser<A>.and(p: Parser<B>): Parser<Pair<A, B>> = parser(firstTokens) {
val a = this@and()
val b = p()
a to b
}

/**
* Applies given function to the result of [this] parser.
*
Expand Down Expand Up @@ -47,33 +38,6 @@ fun <T> Parser<T>.between(left: Parser<*>, right: Parser<*>): Parser<T> = parser
result
}

/**
* Returns a new parser that applies the given [parser], but ignores the result.
*
* ```kotlin
* object : Grammar<String> {
* val title by regexToken("Mrs?\\.?\\s+")
* val surname by regexToken("\\w+")
* override val root by ignored(title) * surname
* }
* ```
*/
fun ignored(parser: Parser<*>): Parser<Unit> = parser map Unit

/**
* Returns a new parser that tries to apply the given [parser]
* and fallbacks to returning null in case of failure.
*
* ```kotlin
* object : Grammar<Pair<String?, String>> {
* val title by regexToken("Mrs?\\.?\\s+")
* val surname by regexToken("\\w+")
* override val root by optional(title) * surname
*/
fun <T : Any> optional(parser: Parser<T>): Parser<T?> = parser {
poll(parser)
}

fun <T : Any> repeated(p: Parser<T>, atLeast: Int, atMost: Int = -1): Parser<List<T>> =
parser(if (atLeast > 0) p.firstTokens else emptySet()) {
repeat(p, atLeast, atMost)
Expand Down
12 changes: 0 additions & 12 deletions src/commonMain/kotlin/me/alllex/parsus/parser/parsers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,6 @@ suspend fun <R> ParsingScope.choose(p: Parser<R>, ps: List<Parser<R>>): R {
fail(NoViableAlternative(startOffset))
}

suspend fun <R : Any> ParsingScope.tryOrNull(p: Parser<R>): R? = tryParse(p).getOrElse { null }

suspend fun <R : Any> ParsingScope.poll(p: Parser<R>): R? = tryOrNull(p)

/**
* Executes given parser, ignoring the result.
*/
suspend fun ParsingScope.skip(p: Parser<*>): IgnoredValue {
p() // execute parser, but ignore the result
return IgnoredValue
}

/**
* Returns true if the parser executes successfully (consuming input) and false otherwise (not consuming any input).
*/
Expand Down
99 changes: 99 additions & 0 deletions src/commonMain/kotlin/me/alllex/parsus/parser/skipCombinators.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package me.alllex.parsus.parser


/**
* Creates a new parser that applies the given [parser], but ignores the result and returns `Unit`.
*
* ```kotlin
* object : Grammar<String> {
* val title by regexToken("Mrs?\\.?\\s+")
* val surname by regexToken("\\w+")
* override val root by ignored(title) and surname
* }
* ```
*/
fun ignored(parser: Parser<*>): Parser<Unit> = SkipParser.of(parser)

/**
* Creates a new parser that applies the given [parser], but ignores the result and returns `Unit`.
*
* ```kotlin
* object : Grammar<String> {
* val title by regexToken("Mrs?\\.?\\s+")
* val surname by regexToken("\\w+")
* override val root by ignored(title) and surname
* }
* ```
*/
operator fun Parser<*>.unaryMinus(): Parser<Unit> = ignored(this)

/**
* Creates a new parser that tries to apply the given [parser]
* and fallbacks to returning null in case of failure.
*
* ```kotlin
* object : Grammar<Tuple2<String?, String>> {
* val title by regexToken("Mrs?\\.?")
* val ws by regexToken("\\s+")
* val surname by regexToken("\\w+")
* override val root by maybe(title) * -maybe(ws) * surname
*/
fun <T : Any> maybe(parser: Parser<T>): Parser<T?> = optional(parser)

/**
* Creates a new parser that tries to apply the given [parser]
* and fallbacks to returning null in case of failure.
*
* ```kotlin
* object : Grammar<Tuple2<String?, String>> {
* val title by regexToken("Mrs?\\.?\\s+")
* val surname by regexToken("\\w+")
* override val root by optional(title) * surname
*/
fun <T : Any> optional(parser: Parser<T>): Parser<T?> = parser {
poll(parser)
}

/**
* Runs the [parser] and returns its result or null in case of failure.
*/
suspend fun <R : Any> ParsingScope.tryOrNull(parser: Parser<R>): R? = tryParse(parser).getOrElse { null }

/**
* Runs the [parser] and returns its result or null in case of failure.
*/
suspend fun <R : Any> ParsingScope.poll(parser: Parser<R>): R? = tryOrNull(parser)

/**
* Executes given parser, ignoring the result.
*/
suspend fun ParsingScope.skip(p: Parser<*>): IgnoredValue {
p() // execute parser, but ignore the result
return IgnoredValue
}


/**
* A wrapping parser that is used as a marker to denote parsed value that is ignored, e.g. by [TupleParser].
*/
@Suppress("UNCHECKED_CAST")
internal class SkipParser<out T> private constructor(
private val parser: Parser<T>
) : ParserImpl<Unit>(firstTokens = parser.firstTokens) {

@Suppress("UNUSED_VARIABLE")
override suspend fun ParsingScope.parse() {
val ignored = parser()
}

override fun toString(): String {
return "SkipParser(parser=$parser)"
}

companion object {
fun <T> of(parser: Parser<T>): Parser<Unit> = when (parser) {
is SkipParser<*> -> parser as SkipParser<Unit>
else -> SkipParser(parser)
}
}
}
Loading

0 comments on commit 1e47171

Please sign in to comment.