parser combinator, json parser, future, other shit
This commit is contained in:
35
README.md
35
README.md
@@ -200,9 +200,42 @@ val out = MutMultiColoredMultiLineString(fill = ColoredChar(' '))
|
|||||||
out.box(x to y, 20 to 5, BoxDrawingCharSet.ROUND, Terminal.COLORS.WHITE.brighter.fg)
|
out.box(x to y, 20 to 5, BoxDrawingCharSet.ROUND, Terminal.COLORS.WHITE.brighter.fg)
|
||||||
println(out.toString())
|
println(out.toString())
|
||||||
```
|
```
|
||||||
|
### Progress bar
|
||||||
|
```kotlin
|
||||||
|
val prog = ProgressBar(100, ProgressBar.Style.BASIC)
|
||||||
|
repeat(100) {
|
||||||
|
prog.step(it)
|
||||||
|
Thread.sleep(10)
|
||||||
|
}
|
||||||
|
prog.step(100)
|
||||||
|
prog.end()
|
||||||
|
```
|
||||||
|
### Parser combinator
|
||||||
|
```kotlin
|
||||||
|
val jsonArray = parser {
|
||||||
|
it.require("[")
|
||||||
|
?.array(",") { elem ->
|
||||||
|
elem.whitespaces()
|
||||||
|
.map(NumParse.int)
|
||||||
|
?.whitespaces()
|
||||||
|
}
|
||||||
|
?.require("]")
|
||||||
|
}
|
||||||
|
|
||||||
|
println(jsonArray(Parsable("[ 1, 2,3 , 4]")))
|
||||||
|
```
|
||||||
|
### JSON parser
|
||||||
|
```kotlin
|
||||||
|
val json = """
|
||||||
|
{
|
||||||
|
"a": 1,
|
||||||
|
"b": { "1": 1, "2": 2 },
|
||||||
|
"c": [1,2]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
println(JSON.parse(json)!!.obj["b"]!!.obj["1"]!!.num)
|
||||||
|
```
|
||||||
### Either
|
### Either
|
||||||
No example yet
|
No example yet
|
||||||
|
|
||||||
### Tree
|
### Tree
|
||||||
No example yet
|
No example yet
|
14
src/main/kotlin/blitz/async/Async.kt
Normal file
14
src/main/kotlin/blitz/async/Async.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package blitz.async
|
||||||
|
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
// TODO: use coroutines?
|
||||||
|
fun <R> async(fn: () -> R): Future<R> {
|
||||||
|
lateinit var future: ThreadWaitingFuture<R>
|
||||||
|
val thread = Thread {
|
||||||
|
future.done(fn())
|
||||||
|
}
|
||||||
|
future = ThreadWaitingFuture(thread)
|
||||||
|
thread.start()
|
||||||
|
return future
|
||||||
|
}
|
61
src/main/kotlin/blitz/async/Future.kt
Normal file
61
src/main/kotlin/blitz/async/Future.kt
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package blitz.async
|
||||||
|
|
||||||
|
import blitz.Obj
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
open class Future<T> {
|
||||||
|
protected lateinit var value: Obj<T>
|
||||||
|
protected val then = mutableListOf<(T) -> Unit>()
|
||||||
|
protected val presentAtomic = AtomicBoolean(false)
|
||||||
|
|
||||||
|
val present get() = presentAtomic.get()
|
||||||
|
|
||||||
|
fun done(va: T) {
|
||||||
|
if (presentAtomic.get())
|
||||||
|
error("Can not set future twice!")
|
||||||
|
value = Obj.of(va)
|
||||||
|
presentAtomic.set(true)
|
||||||
|
then.forEach {
|
||||||
|
it(va)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun then(fn: (T) -> Unit): Future<T> {
|
||||||
|
if (presentAtomic.get())
|
||||||
|
fn(value.v)
|
||||||
|
else
|
||||||
|
then.add(fn)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <R> map(fn: (T) -> R): Future<R> {
|
||||||
|
val new = Future<R>()
|
||||||
|
then {
|
||||||
|
new.done(fn(it))
|
||||||
|
}
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: can do better
|
||||||
|
open fun await(iterSleepMS: Long = 10): T {
|
||||||
|
val thread = Thread {
|
||||||
|
while (true) Thread.sleep(iterSleepMS)
|
||||||
|
}
|
||||||
|
thread.start()
|
||||||
|
then { thread.interrupt() }
|
||||||
|
kotlin.runCatching { thread.join() }
|
||||||
|
return value.v
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
if (presentAtomic.get()) "Future(${value.v})" else "Future(?)"
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThreadWaitingFuture<T>(
|
||||||
|
private val on: Thread
|
||||||
|
): Future<T>() {
|
||||||
|
override fun await(iterSleepMS: Long): T {
|
||||||
|
on.join()
|
||||||
|
return value.v
|
||||||
|
}
|
||||||
|
}
|
10
src/main/kotlin/blitz/collections/Skip.kt
Normal file
10
src/main/kotlin/blitz/collections/Skip.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package blitz.collections
|
||||||
|
|
||||||
|
fun UnGettableIterator<Char>.skipSpaces(): UnGettableIterator<Char> {
|
||||||
|
var curr = next()
|
||||||
|
while (curr == ' ' || curr == '\n' || curr == '\r') {
|
||||||
|
curr = next()
|
||||||
|
}
|
||||||
|
unGet()
|
||||||
|
return this
|
||||||
|
}
|
80
src/main/kotlin/blitz/parse/JSON.kt
Normal file
80
src/main/kotlin/blitz/parse/JSON.kt
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package blitz.parse
|
||||||
|
|
||||||
|
import blitz.parse.comb.*
|
||||||
|
|
||||||
|
object JSON {
|
||||||
|
lateinit var jsonElement: Parser<Element>
|
||||||
|
val jsonNum = parser {
|
||||||
|
it.map(NumParse.float)?.mapSecond { n ->
|
||||||
|
Number(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val jsonString = parser {
|
||||||
|
it.require("\"")
|
||||||
|
?.untilRequire("\"") { str -> Str(str) }
|
||||||
|
}
|
||||||
|
val jsonArray = parser {
|
||||||
|
it.require("[")
|
||||||
|
?.array(",") { elem ->
|
||||||
|
elem.whitespaces()
|
||||||
|
.map(jsonElement)
|
||||||
|
?.whitespaces()
|
||||||
|
}
|
||||||
|
?.require("]")
|
||||||
|
?.mapSecond { x -> Array(x) }
|
||||||
|
}
|
||||||
|
val jsonObj = parser {
|
||||||
|
it.require("{")
|
||||||
|
?.array(",") { elem ->
|
||||||
|
elem.whitespaces()
|
||||||
|
.map(jsonString)
|
||||||
|
?.mapSecond { it.str }
|
||||||
|
?.whitespaces()
|
||||||
|
?.require(":")
|
||||||
|
?.whitespaces()
|
||||||
|
?.map(jsonElement)
|
||||||
|
?.whitespaces()
|
||||||
|
}
|
||||||
|
?.require("}")
|
||||||
|
?.mapSecond { x -> Obj(x.toMap()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
jsonElement = (jsonArray or jsonNum or jsonString or jsonObj).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
fun isArr() = this is Array
|
||||||
|
fun isNum() = this is Number
|
||||||
|
fun isStr() = this is Str
|
||||||
|
fun isObj() = this is Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Array(val value: List<Element>): Element {
|
||||||
|
override fun toString(): String =
|
||||||
|
value.joinToString(separator = ", ", prefix = "[", postfix = "]")
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Number(val value: Double): Element {
|
||||||
|
override fun toString(): String =
|
||||||
|
value.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Str(val value: String): Element {
|
||||||
|
override fun toString(): String =
|
||||||
|
"\"$value\""
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Obj(val value: Map<String, Element>): Element {
|
||||||
|
override fun toString(): String =
|
||||||
|
value.map { (k, v) -> "\"$k\": $v" }.joinToString(separator = ", ", prefix = "{", postfix = "}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parse(string: String): Element? =
|
||||||
|
jsonElement(Parsable(string))?.second
|
||||||
|
}
|
43
src/main/kotlin/blitz/parse/NumParse.kt
Normal file
43
src/main/kotlin/blitz/parse/NumParse.kt
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package blitz.parse
|
||||||
|
|
||||||
|
import blitz.parse.comb.*
|
||||||
|
|
||||||
|
object NumParse {
|
||||||
|
private val intBase = parser { it.require("0b")?.to(2) } or
|
||||||
|
parser { it.require("0x")?.to(16) } or
|
||||||
|
parser { it.require("0o")?.to(8) } or
|
||||||
|
constantParser(10)
|
||||||
|
|
||||||
|
private val sign = parser { it.require("+")?.to(1) } or
|
||||||
|
parser { it.require("-")?.to(-1) } or
|
||||||
|
constantParser(1)
|
||||||
|
|
||||||
|
val int = parser { s ->
|
||||||
|
s.map(sign)?.map(intBase)?.map { str, (sign, base) ->
|
||||||
|
val chars = when (base) {
|
||||||
|
2 -> "01"
|
||||||
|
8 -> "01234567"
|
||||||
|
10 -> "0123456789"
|
||||||
|
16 -> "0123456789abcdefABCDEF"
|
||||||
|
else -> error("wtf")
|
||||||
|
}
|
||||||
|
str.asLongAs(*chars.toCharArray()) {
|
||||||
|
it.toLongOrNull(base)?.times(sign)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val float = parser { s ->
|
||||||
|
s.map(sign)?.map { str, sign ->
|
||||||
|
str.asLongAs('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.') {
|
||||||
|
it.toDoubleOrNull()?.times(sign)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseInt(str: String): Long? =
|
||||||
|
NumParse.int(Parsable(str))?.second
|
||||||
|
|
||||||
|
fun parseDouble(str: String): Double? =
|
||||||
|
NumParse.float(Parsable(str))?.second
|
105
src/main/kotlin/blitz/parse/comb/Parser.kt
Normal file
105
src/main/kotlin/blitz/parse/comb/Parser.kt
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package blitz.parse.comb
|
||||||
|
|
||||||
|
import blitz.str.collectToString
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class Parsable(
|
||||||
|
val str: String
|
||||||
|
)
|
||||||
|
|
||||||
|
typealias Parser<T> = (Parsable) -> Pair<Parsable, T>?
|
||||||
|
|
||||||
|
fun <T> parser(fn: (Parsable) -> Pair<Parsable, T>?): Parser<T> =
|
||||||
|
fn
|
||||||
|
|
||||||
|
fun <T> Parser<T>.trim(): Parser<T> = parser {
|
||||||
|
it.whitespaces()
|
||||||
|
.map(this@trim)
|
||||||
|
?.whitespaces()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> constantParser(const: T): Parser<T> = { it to const }
|
||||||
|
|
||||||
|
infix fun <T> Parser<T>.or(other: Parser<T>): Parser<T> = {
|
||||||
|
this@or(it) ?: other(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Parsable.spaces(): Parsable {
|
||||||
|
return Parsable(str.trimStart(' '))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Parsable.whitespaces(): Parsable {
|
||||||
|
return Parsable(str.trimStart())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Parsable.require(what: String): Parsable? {
|
||||||
|
if (str.startsWith(what))
|
||||||
|
return Parsable(str.substring(what.length))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Parsable.untilRequire(c: String, map: (String) -> T?): Pair<Parsable, T>? {
|
||||||
|
return map(str.substringBefore(c))?.let { Parsable(str.substringAfter(c)) to it }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Parsable.asLongAs(vararg li: Char, map: (String) -> T?): Pair<Parsable, T>? {
|
||||||
|
val o = mutableListOf<Char>()
|
||||||
|
for (c in str) {
|
||||||
|
if (c in li)
|
||||||
|
o.add(c)
|
||||||
|
else
|
||||||
|
break
|
||||||
|
}
|
||||||
|
val out = str.substring(o.size)
|
||||||
|
return map(o.iterator().collectToString())?.let { Parsable(out) to it }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Parsable.map(parser: Parser<T>): Pair<Parsable, T>? =
|
||||||
|
parser(this)
|
||||||
|
|
||||||
|
fun <T, R> Pair<Parsable, T>.map(fn: (Parsable, T) -> Pair<Parsable, R>?): Pair<Parsable, R>? =
|
||||||
|
fn(first, second)
|
||||||
|
|
||||||
|
fun <A, B> Pair<Parsable, A>.map(parser: Parser<B>): Pair<Parsable, Pair<A, B>>? =
|
||||||
|
map { parsable, a ->
|
||||||
|
parser(parsable)?.let { r ->
|
||||||
|
r.first to (a to r.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Pair<Parsable, T>.mapFirst(fn: (Parsable) -> Parsable): Pair<Parsable, T> =
|
||||||
|
fn(first) to second
|
||||||
|
|
||||||
|
fun <T> Pair<Parsable, T>.mapFirstNullable(fn: (Parsable) -> Parsable?): Pair<Parsable, T>? =
|
||||||
|
fn(first)?.let { it to second }
|
||||||
|
|
||||||
|
fun <T, R> Pair<Parsable, T>.mapSecond(fn: (T) -> R): Pair<Parsable, R> =
|
||||||
|
first to fn(second)
|
||||||
|
|
||||||
|
fun <T> Pair<Parsable, T>.spaces(): Pair<Parsable, T> =
|
||||||
|
mapFirst { it.spaces() }
|
||||||
|
|
||||||
|
fun <T> Pair<Parsable, T>.whitespaces(): Pair<Parsable, T> =
|
||||||
|
mapFirst { it.whitespaces() }
|
||||||
|
|
||||||
|
fun <T> Pair<Parsable, T>.require(what: String): Pair<Parsable, T>? =
|
||||||
|
mapFirstNullable { it.require(what) }
|
||||||
|
|
||||||
|
fun <T> Parsable.array(sep: String, map: (Parsable) -> Pair<Parsable, T>?): Pair<Parsable, List<T>> {
|
||||||
|
val out = mutableListOf<T>()
|
||||||
|
|
||||||
|
var curr = str
|
||||||
|
fun step() =
|
||||||
|
map(Parsable(curr))?.also {
|
||||||
|
curr = it.first.str
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
val r = step() ?: break
|
||||||
|
out.add(r.second)
|
||||||
|
curr = (Parsable(curr).require(sep) ?: break).str
|
||||||
|
}
|
||||||
|
|
||||||
|
return Parsable(curr) to out
|
||||||
|
}
|
@@ -13,7 +13,7 @@ class AnsiiMode(internal val values: MutableList<Int>) {
|
|||||||
values.hashCode()
|
values.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val escape = (27).toChar()
|
internal val escape = (27).toChar()
|
||||||
|
|
||||||
internal fun ansiiStr(str: String, vararg modes: AnsiiMode) =
|
internal fun ansiiStr(str: String, vararg modes: AnsiiMode) =
|
||||||
if (modes.isEmpty())
|
if (modes.isEmpty())
|
||||||
|
46
src/main/kotlin/blitz/term/ProgressBar.kt
Normal file
46
src/main/kotlin/blitz/term/ProgressBar.kt
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package blitz.term
|
||||||
|
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single-line progress bar
|
||||||
|
*/
|
||||||
|
class ProgressBar(
|
||||||
|
var max: Int,
|
||||||
|
val style: Style,
|
||||||
|
private val preDrawLine: () -> Unit = {},
|
||||||
|
private val postDrawLine: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
fun end() {
|
||||||
|
Terminal.Cursor.gotoBeginOfLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun step(pos: Int) {
|
||||||
|
Terminal.Cursor.gotoBeginOfLine()
|
||||||
|
preDrawLine()
|
||||||
|
print(style.pre)
|
||||||
|
val left = (pos.toDouble() / max * style.len).toInt()
|
||||||
|
val right = style.len - left
|
||||||
|
print(style.segment.toString().repeat(max(0, left - 1)))
|
||||||
|
print(style.segmentCursor)
|
||||||
|
print(style.segmentEmpty.toString().repeat(right))
|
||||||
|
print(style.post)
|
||||||
|
postDrawLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Style(
|
||||||
|
val len: Int,
|
||||||
|
val pre: String,
|
||||||
|
val segment: Char,
|
||||||
|
val segmentCursor: Char,
|
||||||
|
val segmentEmpty: Char,
|
||||||
|
val post: String
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val BASIC = Style(36, "[ ", '=', '=', ' ', " ]")
|
||||||
|
val LIGHT = Style(36, "< ", '-', '-', ' ', " >")
|
||||||
|
val STARS = Style(36, "( ", '*', '*', ' ', " )")
|
||||||
|
val ARROW = Style(36, "[ ", '=', '>', ' ', " ]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -17,6 +17,9 @@ object Terminal {
|
|||||||
val DIM = AnsiiMode(2)
|
val DIM = AnsiiMode(2)
|
||||||
val UNDERLINE = AnsiiMode(4)
|
val UNDERLINE = AnsiiMode(4)
|
||||||
val BLINK = AnsiiMode(5)
|
val BLINK = AnsiiMode(5)
|
||||||
|
val OVERLINE = AnsiiMode(53)
|
||||||
|
val FRAMED = AnsiiMode(51)
|
||||||
|
val ENCIRCLED = AnsiiMode(52)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Color(
|
class Color(
|
||||||
@@ -84,4 +87,46 @@ object Terminal {
|
|||||||
fun warn(str: String, vararg modes: AnsiiMode) {
|
fun warn(str: String, vararg modes: AnsiiMode) {
|
||||||
errln(str, *modes)
|
errln(str, *modes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object Cursor {
|
||||||
|
fun savePos() {
|
||||||
|
print(escape + "7")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restorePos() {
|
||||||
|
print(escape + "8")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun goto(line: Int, col: Int) {
|
||||||
|
print("$escape[${line};${col}H")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun gotoZero() {
|
||||||
|
print("$escape[H")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun gotoBeginOfPrevLine() {
|
||||||
|
print("$escape[F")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun gotoBeginOfLine() {
|
||||||
|
print((0x0D).toChar())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save() {
|
||||||
|
print("$escape[?47h")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restore() {
|
||||||
|
print("$escape[?47l")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
print("$escape[2J")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearLine() {
|
||||||
|
print("$escape[2K")
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user