diff --git a/src/main/kotlin/blitz/Either.kt b/src/main/kotlin/blitz/Either.kt index dbe9b8c..42be82f 100644 --- a/src/main/kotlin/blitz/Either.kt +++ b/src/main/kotlin/blitz/Either.kt @@ -62,6 +62,12 @@ class Either private constructor( fun Either.flatten(): R where A: R, B: R = getAOrNull() ?: getB() +fun Either>.partiallyFlatten(): Either = + mapA> { Either.ofA(it) }.flatten() + +fun Either, B>.partiallyFlatten(): Either = + mapB> { Either.ofB(it) }.flatten() + fun Either>.mapBA(fn: (BA) -> BAN): Either> = mapB { it.mapA(fn) } diff --git a/src/main/kotlin/blitz/Obj.kt b/src/main/kotlin/blitz/Obj.kt index 425e742..5321fb5 100644 --- a/src/main/kotlin/blitz/Obj.kt +++ b/src/main/kotlin/blitz/Obj.kt @@ -30,6 +30,10 @@ fun Obj.map(transform: (I) -> O): Obj = interface MutObj { var v: T + inline fun modify(fn: (T) -> T) { + v = fn(v) + } + companion object { fun of(v: T): MutObj = object : MutObj { diff --git a/src/main/kotlin/blitz/parse/comb2/Parser.kt b/src/main/kotlin/blitz/parse/comb2/Parser.kt new file mode 100644 index 0000000..0a2178d --- /dev/null +++ b/src/main/kotlin/blitz/parse/comb2/Parser.kt @@ -0,0 +1,146 @@ +package blitz.parse.comb2 + +import blitz.Either +import blitz.partiallyFlatten + +data class ParseCtx( + val input: List, + var idx: Int +) { + fun loadFrom(old: ParseCtx) { + idx = old.idx + } +} + +data class ParseError( + val loc: Int, + val message: String?, +) + +typealias ParseResult = Either> +typealias Parser = (ParseCtx) -> ParseResult + +inline fun Parser.mapValue(crossinline fn: (M) -> O): Parser = + { invoke(it).mapA { fn(it) } } + +inline fun Parser.mapErrors(crossinline fn: (List) -> List): Parser = + { invoke(it).mapB { fn(it) } } + +fun Parser.then(other: Parser): Parser> = + { ctx -> + invoke(ctx).mapA { first -> + other.invoke(ctx) + .mapA { first to it } + }.partiallyFlatten() + } + +fun Parser.thenIgnore(other: Parser): Parser = + { ctx -> + invoke(ctx).mapA { first -> + other.invoke(ctx) + .mapA { first } + }.partiallyFlatten() + } + +fun Parser.orElse(other: Parser): Parser = + { + val old = it.copy() + this(it).mapB { err -> + it.loadFrom(old) + other.invoke(it) + .mapB { err + it } + }.partiallyFlatten() + } + +fun Parser.repeated(): Parser> = + { ctx -> + val out = mutableListOf() + var ret: List? = null + while (true) { + val old = ctx.copy() + val t = invoke(ctx) + if (t.isA) { + out += t.getA() + } else { + ctx.loadFrom(old) + ret = t.getB() + break + } + } + if (ret == null) { + Either.ofA(out) + } else Either.ofB(ret) + } + +fun Parser.delimitedBy(delim: Parser): Parser> = + thenIgnore(delim) + .repeated() + .then(this) + .mapValue { (a, b) -> a + b } + .orElse(value(listOf())) + +inline fun Parser.verifyValue(crossinline verif: (O) -> String?): Parser = + { ctx -> + invoke(ctx).mapA> { + verif(it)?.let { Either.ofB(listOf(ParseError(ctx.idx, it))) } + ?: Either.ofA(it) + }.partiallyFlatten() + } + +inline fun Parser>.verifyValueWithSpan(crossinline fn: (O) -> String?): Parser = + { ctx -> + invoke(ctx).mapA> { (span, v) -> + fn(v)?.let { Either.ofB(listOf(ParseError(span.first, it))) } + ?: Either.ofA(v) + }.partiallyFlatten() + } + +fun Parser.errIfNull(msg: String = "parser value was null internally"): Parser = + verifyValue { if (it == null) msg else null } + .mapValue { it!! } + +inline fun location(crossinline fn: (Int) -> O): Parser = + { Either.ofA(fn(it.idx)) } + +fun location(): Parser = + location { it } + +fun withSpan(p: Parser): Parser> = + location() + .then(p) + .then(location()) + .mapValue { (beginAndV, end) -> + (beginAndV.first..end) to beginAndV.second + } + +fun value(value: O): Parser = + { Either.ofA(value) } + +fun whitespaces(): Parser = + regex("\\s+") + +fun just(want: I): Parser = + { ctx -> + val i = ctx.input[ctx.idx ++] + if (i == want) Either.ofA(i) + else Either.ofB(listOf(ParseError(ctx.idx - 1, "expected $want"))) + } + +/** group values 0 is the entire match */ +fun regex(pattern: Regex, fn: (groups: MatchGroupCollection) -> O): Parser = + { ctx -> + pattern.matchAt(ctx.input.toString(), ctx.idx)?.let { + ctx.idx = it.range.last + 1 + Either.ofA(fn(it.groups)) + } ?: Either.ofB(listOf( + ParseError(ctx.idx, "regular expression \"$pattern\" does not apply") + )) + } + +fun regex(pattern: Regex) = regex(pattern) { it[0]!!.value } + +/** group values 0 is the entire match */ +fun regex(pattern: String, fn: (groups: MatchGroupCollection) -> O): Parser = + regex(Regex(pattern), fn) + +fun regex(pattern: String) = regex(pattern) { it[0]!!.value } \ No newline at end of file