diff --git a/build.gradle.kts b/build.gradle.kts index 4b7b768..bc69bdb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,6 @@ dependencies { // https://mvnrepository.com/artifact/org.json/json implementation("org.json:json:20240303") - } tasks.test { diff --git a/src/main/kotlin/blitz/Either.kt b/src/main/kotlin/blitz/Either.kt index e511b51..905a803 100644 --- a/src/main/kotlin/blitz/Either.kt +++ b/src/main/kotlin/blitz/Either.kt @@ -1,9 +1,16 @@ package blitz -class Either( - val a: A?, - val b: B? +class Either private constructor( + aIn: A?, bIn: B? ) { + /** DO NOT SET MANUALLY!!! */ + @JvmField + var a: A? = aIn + + /** DO NOT SET MANUALLY!!! */ + @JvmField + var b: B? = bIn + override fun equals(other: Any?): Boolean = other is Either<*, *> && other.a == a && other.b == b @@ -13,14 +20,8 @@ class Either( fun assertB(): B = (b ?: throw Exception("Value of Either is not of type B!")) - val isA: Boolean = - a != null - - val isB: Boolean = - b != null - override fun toString(): String = - if (isA) "Either(${a!!})" + if (isA()) "Either(${a!!})" else "Either(${b!!})" override fun hashCode(): Int { @@ -30,14 +31,20 @@ class Either( } companion object { - inline fun ofA(a: A): Either = - Either(a, null) + fun unsafeCreate(a: A?, b: B?, pool: StupidObjPool>? = null): Either = + Either(a, b) - inline fun ofB(b: B): Either = - Either(null, b) + inline fun ofA(a: A, pool: StupidObjPool>? = null): Either = + unsafeCreate(a, null, pool) + + inline fun ofB(b: B, pool: StupidObjPool>? = null): Either = + unsafeCreate(null, b, pool) } } +inline fun Either.isA() = a != null +inline fun Either.isB() = b != null + inline fun Either.getAOr(prov: Provider): A = a ?: prov() @@ -45,35 +52,35 @@ inline fun Either.getBOr(prov: Provider): B = b ?: prov() inline fun Either.then(af: (A) -> R, bf: (B) -> R): R = - if (isA) af(a!!) else bf(b!!) + if (isA()) af(a!!) else bf(b!!) inline fun Either.mapA(transform: (A) -> RA): Either = - Either(a?.let(transform), b) + Either.unsafeCreate(a?.let(transform), b) inline fun Either.flatMapA(transform: (A) -> Either): Either = if (a != null) { - transform(a) + transform(a!!) } else this inline fun Either.flatMapB(transform: (B) -> Either): Either = if (b != null) { - transform(b) + transform(b!!) } else this @JvmName("flatMapA_changeType") inline fun Either.flatMapA(transform: (A) -> Either): Either = if (a != null) { - transform(a) + transform(a!!) } else Either.ofB(b!!) @JvmName("flatMapB_changeType") inline fun Either.flatMapB(transform: (B) -> Either): Either = if (b != null) { - transform(b) + transform(b!!) } else Either.ofA(a!!) inline fun Either.mapB(transform: (B) -> RB): Either = - Either(a, b?.let(transform)) + Either.unsafeCreate(a, b?.let(transform)) fun Either.flatten(): R where A: R, B: R = a ?: assertB() diff --git a/src/main/kotlin/blitz/StupidObjPool.kt b/src/main/kotlin/blitz/StupidObjPool.kt new file mode 100644 index 0000000..d38bf4b --- /dev/null +++ b/src/main/kotlin/blitz/StupidObjPool.kt @@ -0,0 +1,45 @@ +package blitz + +import blitz.collections.RefVec +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * all created objects are stored in this pool, and when you call [StupidObjPool.markClear], all objects are marked as free and can be re-used + * this is useful for when you need a ton of objects during a process and the process runs multiple times (caches the objects between executions) + */ +class StupidObjPool(initialCap: Int) { + @JvmField val _objs = RefVec(initialCap) + + @JvmField var _nextFreeId = 0 + + /** only one of constructor or initializer is called */ + @OptIn(ExperimentalContracts::class) + inline fun get(constructor: () -> T, initializer: (T) -> Unit): T { + contract { + callsInPlace(constructor, InvocationKind.AT_MOST_ONCE) + callsInPlace(initializer, InvocationKind.AT_MOST_ONCE) + } + + if (_nextFreeId < _objs.size) { + val o = _objs[_nextFreeId++] + initializer(o) + return o + } + + val o = constructor() + _objs.pushBack(o) + _nextFreeId ++ + return o + } + + fun clearPool() { + _objs.clear() + _nextFreeId = 0 + } + + fun markClear() { + _nextFreeId = 0 + } +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/collections/RefVec.kt b/src/main/kotlin/blitz/collections/RefVec.kt index e92656c..fbd122b 100644 --- a/src/main/kotlin/blitz/collections/RefVec.kt +++ b/src/main/kotlin/blitz/collections/RefVec.kt @@ -1,75 +1,73 @@ package blitz.collections -import kotlin.system.measureTimeMillis - @Suppress("UNCHECKED_CAST") class RefVec(private val initCap: Int = 0): Vec { override var size = 0 - private var cap = initCap - private var array: Array? = if (initCap > 0) arrayOfNulls(initCap) else null + @JvmField var _cap = initCap + @JvmField var _array: Array? = if (initCap > 0) arrayOfNulls(initCap) else null override fun clear() { size = 0 - if (array == null) return - if (array!!.size <= initCap) { - cap = array!!.size + if (_array == null) return + if (_array!!.size <= initCap) { + _cap = _array!!.size } else { - cap = 0 - array = null + _cap = 0 + _array = null } } - fun copyAsArray(): Array = - array?.copyOfRange(0, size) ?: emptyArray() + inline fun copyAsArray(): Array = + _array?.copyOfRange(0, size) ?: emptyArray() - fun copyIntoArray(arr: Array, destOff: Int = 0, startOff: Int = 0) = - array?.copyInto(arr, destOff, startOff, size) + inline fun copyIntoArray(arr: Array, destOff: Int = 0, startOff: Int = 0) = + _array?.copyInto(arr, destOff, startOff, size) - override fun copy(): RefVec = + override inline fun copy(): RefVec = RefVec(size).also { - it.array?.let { copyIntoArray(it) } + it._array?.let { copyIntoArray(it) } } override fun reserve(amount: Int) { - if (amount > 0 && cap - size >= amount) + if (amount > 0 && _cap - size >= amount) return - if (array == null) { - cap = size + amount - array = arrayOfNulls(cap) + if (_array == null) { + _cap = size + amount + _array = arrayOfNulls(_cap) } else { - array = array!!.copyOf(size + amount) - cap = size + amount + _array = _array!!.copyOf(size + amount) + _cap = size + amount } } override fun reserve(need: Int, totalIfRealloc: Int) { - if (need > 0 && cap - size >= need) + if (need > 0 && _cap - size >= need) return - if (array == null) { - cap = size + totalIfRealloc - array = arrayOfNulls(cap) + if (_array == null) { + _cap = size + totalIfRealloc + _array = arrayOfNulls(_cap) } else { - array = array!!.copyOf(size + totalIfRealloc) - cap = size + totalIfRealloc + _array = _array!!.copyOf(size + totalIfRealloc) + _cap = size + totalIfRealloc } } override fun popBack(): T = - array!![size - 1].also { + _array!![size - 1].also { reserve(-1) size -- } as T - override fun get(index: Int): T = - array!![index] as T + override inline fun get(index: Int): T = + (_array as Array)[index] as T override fun flip() { - array = array?.reversedArray() + _array = _array?.reversedArray() } override fun pushBack(elem: T) { reserve(1, 8) - array!![size] = elem + this[size] = elem size ++ } @@ -80,7 +78,7 @@ class RefVec(private val initCap: Int = 0): Vec { override fun next(): T { if (!hasNext()) throw NoSuchElementException() - return array!![index++] as T + return _array!![index++] as T } } @@ -88,13 +86,13 @@ class RefVec(private val initCap: Int = 0): Vec { joinToString(prefix = "[", postfix = "]") { it.toString() } override fun set(index: Int, value: T) { - array!![index] = value + (_array as Array)[index] = value } companion object { fun from(data: Array) = RefVec(data.size).also { - it.array?.let { data.copyInto(it) } + it._array?.let { data.copyInto(it) } it.size += data.size } @@ -103,9 +101,9 @@ class RefVec(private val initCap: Int = 0): Vec { data.forEach(bv::pushBack) } - fun of(vararg elements: T): RefVec = - RefVec(elements.size).also { - it.array?.let { elements.copyInto(it) } + inline fun of(vararg elements: T): RefVec = + RefVec(elements.size shl 2).also { + it._array?.let { elements.copyInto(it) } } } } \ No newline at end of file diff --git a/src/main/kotlin/blitz/parse/JSON.kt b/src/main/kotlin/blitz/parse/JSON.kt index b754376..e3b5655 100644 --- a/src/main/kotlin/blitz/parse/JSON.kt +++ b/src/main/kotlin/blitz/parse/JSON.kt @@ -1,37 +1,38 @@ package blitz.parse +import blitz.collections.RefVec import blitz.parse.comb2.* import org.json.JSONObject import kotlin.math.min import kotlin.system.measureNanoTime object JSON { + + val jsonBool: Parser = choose { + it(mapValue(seq("true".toList())) { Element.newBool(true) }) + it(mapValue(seq("false".toList())) { Element.newBool(false) }) + } + + val jsonNull: Parser = + mapValue(seq("null".toList())) { Element.newNull() } + + val jsonNum: Parser = + mapValue(floatLit, Element::newNum) + + val jsonString: Parser = + mapValue(stringLit, Element::newStr) + val jsonElement = futureRec { jsonElement: Parser -> - val jsonNum: Parser = - mapValue(floatLit(), ::Number) - - val jsonString: Parser = - mapValue(stringLit(), ::Str) - val jsonArray: Parser = thenIgnore( thenIgnore( thenOverwrite(just('['), - mapValue(delimitedBy(jsonElement, just(','))) - { Array(it.toList())}), - whitespaces()), + mapValue(delimitedBy(jsonElement, just(',')), Element::newArr)), + whitespaces), just(']') ) - val jsonBool: Parser = choose( - mapValue(seq("true".toList())) { Bool(true) }, - mapValue(seq("false".toList())) { Bool(false) }, - ) - - val jsonNull: Parser = - mapValue(seq("null".toList())) { Nul() } - val jsonObj: Parser = mapValue(thenIgnore(thenIgnore(thenOverwrite( just('{'), @@ -40,74 +41,99 @@ object JSON { thenIgnore( thenIgnore( thenOverwrite( - whitespaces(), - stringLit()), - whitespaces()), + whitespaces, + stringLit), + whitespaces), just(':')), jsonElement), just(','))), - whitespaces()), - just('}'))) { Obj(it.toMap()) } + whitespaces), + just('}'))) { Element.newObj(it.toMap()) } thenIgnore(thenOverwrite( - whitespaces(), - choose( - jsonArray, - jsonNum, - jsonString, - jsonObj, - jsonBool, - jsonNull - )), - whitespaces()) + whitespaces, + choose { + it(jsonArray) + it(jsonNum) + it(jsonString) + it(jsonObj) + it(jsonBool) + it(jsonNull) + }), + whitespaces) } - interface Element { - val arr get() = (this as Array).value - val num get() = (this as Number).value - val str get() = (this as Str).value - val obj get() = (this as Obj).value - val bool get() = (this as Bool).value + class Element( + @JvmField val kind: Int, + @JvmField val _boxed: Any? = null, + @JvmField val _num: Double = 0.0, + @JvmField val _bool: Boolean = false, + ) { + companion object { + const val NUM = 0 + const val BOOL = 1 + const val NULL = 2 + const val ARR = 3 + const val STR = 4 + const val OBJ = 5 - fun isArr() = this is Array - fun isNum() = this is Number - fun isStr() = this is Str - fun isObj() = this is Obj - fun isBool() = this is Bool - fun isNul() = this is Nul + inline fun newNum(v: Double): Element = + Element(NUM, _num = v) + inline fun newBool(v: Boolean): Element = + Element(BOOL, _bool = v) + inline fun newNull(): Element = + Element(NULL) + inline fun newArr(v: RefVec): Element = + Element(ARR, _boxed = v) + inline fun newStr(v: String): Element = + Element(STR, _boxed = v) + inline fun newObj(v: Map): Element = + Element(OBJ, _boxed = v) + } + } + + inline fun Element.uncheckedAsNum(): Double = + _num + inline fun Element.uncheckedAsBool(): Boolean = + _bool + inline fun Element.uncheckedAsArr(): RefVec = + _boxed as RefVec + inline fun Element.uncheckedAsStr(): String = + _boxed as String + inline fun Element.uncheckedAsObj(): Map = + _boxed as Map + + inline fun Element.asNum(): Double { + require(kind == Element.NUM) { "Element is not a Number" } + return _num } - data class Array(val value: List): Element { - override fun toString(): String = - value.joinToString(separator = ", ", prefix = "[", postfix = "]") + inline fun Element.asBool(): Boolean { + require(kind == Element.BOOL) { "Element is not a Boolean" } + return _bool } - data class Number(val value: Double): Element { - override fun toString(): String = - value.toString() + inline fun Element.asArr(): RefVec { + require(kind == Element.ARR) { "Element is not an Array" } + return _boxed as RefVec } - data class Str(val value: String): Element { - override fun toString(): String = - "\"$value\"" + inline fun Element.asStr(): String { + require(kind == Element.STR) { "Element is not a String" } + return _boxed as String } - data class Obj(val value: Map): Element { - override fun toString(): String = - value.map { (k, v) -> "\"$k\": $v" }.joinToString(separator = ", ", prefix = "{", postfix = "}") + inline fun Element.asObj(): Map { + require(kind == Element.OBJ) { "Element is not an Object" } + return _boxed as Map } - - data class Bool(val value: Boolean): Element { - override fun toString(): String = - value.toString() + fun parse(string: String): ParseResult { + val ctx = ParseCtx(string.toList(), 0) + val v = jsonElement(ctx) + return v } - - class Nul: Element - - fun parse(string: String): ParseResult = - jsonElement(ParseCtx(string.toList(), 0)) } fun main() { diff --git a/src/main/kotlin/blitz/parse/comb2/Parser.kt b/src/main/kotlin/blitz/parse/comb2/Parser.kt index e55f694..f6f466f 100644 --- a/src/main/kotlin/blitz/parse/comb2/Parser.kt +++ b/src/main/kotlin/blitz/parse/comb2/Parser.kt @@ -2,12 +2,11 @@ package blitz.parse.comb2 import blitz.* import blitz.collections.RefVec -import blitz.collections.contents import blitz.str.charsToString data class ParseCtx( - val input: List, - var idx: Int + @JvmField val input: List, + @JvmField var idx: Int ) { fun loadFrom(old: ParseCtx) { idx = old.idx @@ -15,25 +14,39 @@ data class ParseCtx( } data class ParseError( - val loc: Int, - val message: String?, + @JvmField val loc: Int, /** can be -1 */ + @JvmField val message: String?, ) -typealias ParseResult = Either> +typealias ParseResult = Either typealias Parser = (ParseCtx) -> ParseResult inline fun mapValue(crossinline self: Parser, crossinline fn: (M) -> O): Parser = - { self(it).mapA { fn(it) } } + { + val r = self(it) as Either + r.a?.let { + r.a = fn(it as M) + } + r as Either + } -inline fun mapErrors(crossinline self: Parser, crossinline fn: (RefVec) -> RefVec): Parser = - { self(it).mapB { fn(it) } } +inline fun mapErrors(crossinline self: Parser, crossinline fn: (ParseError) -> ParseError): Parser = + { + val r = self(it) + r.b?.let { r.b = fn(it) } + r + } inline fun then(crossinline self: Parser, crossinline other: Parser): Parser> = { ctx -> - self(ctx).flatMapA<_,_,Pair> { first -> - other.invoke(ctx) - .mapA { first to it } - } + val r0 = self(ctx) as ParseResult + r0.a?.let { first -> + val r1 = other(ctx) + r1.a?.let { second -> + (r1 as ParseResult).a = Pair(first, second) + (r1 as ParseResult>) + } ?: (r1 as ParseResult>) + } ?: (r0 as ParseResult>) } inline fun thenOverwrite(crossinline self: Parser, crossinline other: Parser): Parser = @@ -57,42 +70,59 @@ inline fun orElseVal(crossinline self: Parser, value: O): Pars inline fun orElse(crossinline self: Parser, crossinline other: Parser): Parser where O: R = { val old = it.idx - self(it).mapB { err -> + self(it).mapB { _ -> it.idx = old other.invoke(it) - .mapB { err.pushBack(it); err } }.partiallyFlattenB() } +/** Use the other choose that takes a function whenever possible because of perf */ fun choose(possible: Iterable>): Parser = { ctx -> - val errors = RefVec(possible.count()) var res: O? = null for (p in possible) { val old = ctx.idx val t = p.invoke(ctx) - if (t.isA) { + if (t.isA()) { res = t.a!! break } else { ctx.idx = old - errors.pushBack(t.b!!) } } res?.let { Either.ofA(it) } - ?: Either.ofB(errors) + ?: Either.ofB(ParseError(ctx.idx, "none of the possible parsers match")) } -fun choose(vararg possible: Parser): Parser = +inline fun choose(crossinline fn: (run: (Parser) -> Unit) -> Unit): Parser = + { ctx -> + var res: O? = null + fn { p -> + if (res == null) { + val old = ctx.idx + val t = p.invoke(ctx) + if (t.isA()) { + res = t.a!! + } else { + ctx.idx = old + } + } + } + res?.let { Either.ofA(it) } + ?: Either.ofB(ParseError(ctx.idx, "none of the possible parsers match")) + } + +/** Use the other choose that takes a function whenever possible because of perf */ +inline fun choose(vararg possible: Parser): Parser = choose(possible.toList()) inline fun repeated(crossinline what: Parser): Parser> = { ctx -> - val out = RefVec(0) + val out = RefVec(16) while (true) { val old = ctx.idx val t = what(ctx) - if (t.isA) { + if (t.isA()) { out.pushBack(t.a!!) } else { ctx.idx = old @@ -107,7 +137,7 @@ inline fun repeatedNoSave(crossinline what: Parser): Parser repeatedNoSave(crossinline what: Parser): Parser verifyValue(crossinline self: Parser, crossinline verif: (O) -> String?): Parser = { ctx -> self(ctx).flatMapA<_,_,_> { - verif(it)?.let { Either.ofB(RefVec.of(ParseError(ctx.idx, it))) } + verif(it)?.let { Either.ofB(ParseError(ctx.idx, it)) } ?: Either.ofA(it) } } @@ -126,7 +156,7 @@ inline fun verifyValue(crossinline self: Parser, crossinline v inline fun verifyValueWithSpan(crossinline self: Parser>, crossinline fn: (O) -> String?): Parser = { ctx -> self(ctx).flatMapA<_,_,_> { (span, v) -> - fn(v)?.let { Either.ofB(RefVec.of(ParseError(span.first, it))) } + fn(v)?.let { Either.ofB(ParseError(span.first, it)) } ?: Either.ofA(v) } } @@ -137,7 +167,7 @@ inline fun location(crossinline fn: (Int) -> O): Parser = inline fun location(): Parser = location { it } -inline fun withSpan(crossinline p: Parser): Parser> = +fun withSpan(p: Parser): Parser> = mapValue(then(then(location(), p), location())) { (beginAndV, end) -> (beginAndV.first..end) to beginAndV.second } @@ -148,17 +178,17 @@ inline fun value(value: O): Parser = fun chain(parsers: List>): Parser> = { ctx -> val results = RefVec(parsers.size) - val errs = RefVec(0) + var errs: ParseError? = null for (p in parsers) { val r = p.invoke(ctx) - if (r.isA) { + if (r.isA()) { results.pushBack(r.a!!) } else { - errs.pushBack(r.b!!) + errs = r.b!! break } } - if (errs.size != 0) Either.ofB(errs) + if (errs != null) Either.ofB(errs) else Either.ofA(results) } @@ -168,19 +198,34 @@ inline fun seq(want: List): Parser> = inline fun filter(msg: String, crossinline filter: (I) -> Boolean): Parser = { ctx -> if (ctx.idx >= ctx.input.size) { - Either.ofB(RefVec.of(ParseError(ctx.idx, "unexpected end of file"))) + Either.ofB(ParseError(ctx.idx, "unexpected end of file")) } else { val i = ctx.input[ctx.idx++] if (filter(i)) Either.ofA(i) - else Either.ofB(RefVec.of(ParseError(ctx.idx - 1, msg))) + else Either.ofB(ParseError(ctx.idx - 1, msg)) } } -inline fun just(want: I): Parser = - filter("expected $want") { it == want } +private class JustParse(wantIn: I): Parser { + @JvmField val want = wantIn + @JvmField val uef: ParseResult = Either.ofB(ParseError(-1, "unexpected end of file")) + @JvmField val exdiff: ParseResult = Either.ofB(ParseError(-1, "expected $wantIn")) + @JvmField val eitherOfWant: ParseResult = Either.ofA(want) + override fun invoke(ctx: ParseCtx): ParseResult { + return if (ctx.idx >= ctx.input.size) uef + else { + val i = ctx.input[ctx.idx++] + if (i == want) eitherOfWant + else exdiff + } + } +} + +fun just(wantIn: I): Parser = + JustParse(wantIn) inline fun oneOf(possible: Iterable): Parser = - filter("expected one of ${possible.contents}") { it in possible } + filter("expected different") { it in possible } inline fun future(crossinline prov: Provider>): Parser = { prov()(it) } @@ -197,9 +242,9 @@ fun regex(pattern: Regex, fn: (groups: MatchGroupCollection) -> O): Par pattern.matchAt(ctx.input.charsToString(), ctx.idx)?.let { ctx.idx = it.range.last + 1 Either.ofA(fn(it.groups)) - } ?: Either.ofB(RefVec.of( + } ?: Either.ofB( ParseError(ctx.idx, "regular expression \"$pattern\" does not apply") - )) + ) } fun regex(pattern: Regex) = regex(pattern) { it[0]!!.value } diff --git a/src/main/kotlin/blitz/parse/comb2/Predef.kt b/src/main/kotlin/blitz/parse/comb2/Predef.kt index cbc1052..c77e949 100644 --- a/src/main/kotlin/blitz/parse/comb2/Predef.kt +++ b/src/main/kotlin/blitz/parse/comb2/Predef.kt @@ -5,30 +5,33 @@ import blitz.str.charsToString import kotlin.math.absoluteValue import kotlin.math.sign -fun whitespaces(): Parser = - repeatedNoSave(oneOf("\n\t\r\b ".toList())) +private fun isWhitespace(it: Char) = + it == ' ' || it == '\n' || it == '\t' || it == '\r' || it == '\b' +val whitespaces: Parser = + repeatedNoSave(filter("expected whitespace", ::isWhitespace)) -fun digit(): Parser = - oneOf("0123456789".toList()) +val digit: Parser = + filter("expected digit") { it >= '0' && it <= '9' } -fun uintLit(): Parser> = - verifyValueWithSpan(withSpan(repeated(digit()))) +val uintLit: Parser> = + verifyValueWithSpan(withSpan(repeated(digit))) { if (it.size == 0) "need digits after sign in num lit" else null } -fun intLit(): Parser = - mapValue(then(choose(mapValue(just('+')) { +1 }, - mapValue(just('-')) { -1 }, - value(+1)), - uintLit())) +val intLit: Parser = + mapValue(then(choose { + it(mapValue(just('+')) { +1 }) + it(mapValue(just('-')) { -1 }) + it(value(+1)) + }, uintLit)) { (sign, v) -> sign * v.charsToString().toInt() } -fun floatLit(): Parser = +val floatLit: Parser = mapValue( then( thenIgnore( - intLit(), + intLit, just('.')), - orElseVal(uintLit(), RefVec.of('0')))) + orElseVal(uintLit, RefVec.of('0')))) { (pre, post) -> var p = post.charsToString().toDouble() while (p.absoluteValue >= 1) { @@ -38,22 +41,26 @@ fun floatLit(): Parser = (pre.toDouble().absoluteValue + p) * pre.toDouble().sign } -fun escapeChar(): Parser = +val escapeChar: Parser = thenOverwrite(just('\\'), - mapErrors(choose(just('"'), - just('\''), - just('\\'), - mapValue(just('n')) { '\n' }, - mapValue(just('r')) { '\r' }, - mapValue(just('b')) { '\b' }, - mapValue(just('t')) { '\t' })) - { RefVec.of(ParseError(it[0].loc, "invalid escape sequence")) } + mapErrors(choose { + it(just('"')) + it(just('\'')) + it(just('\\')) + it(mapValue(just('n')) { '\n' }) + it(mapValue(just('r')) { '\r' }) + it(mapValue(just('b')) { '\b' }) + it(mapValue(just('t')) { '\t' }) + }) + { ParseError(it.loc, "invalid escape sequence") } ) -fun stringLit(): Parser = +val stringLit: Parser = mapValue(thenIgnore(then(just('"'), - repeated(choose(escapeChar(), - filter("a") { it != '"' }))), + repeated(choose{ + it(escapeChar) + it(filter("a") { it != '"' }) + })), just('"'))) { (_, str) -> str.charsToString() }