-
Notifications
You must be signed in to change notification settings - Fork 0
/
FeedModel.kt
83 lines (68 loc) · 3.46 KB
/
FeedModel.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package org.parserkt
import org.parserkt.util.*
// File: FeedModel
interface Feed<out T>: Preety {
val peek: T; fun consume(): T
class End: NoSuchElementException("no more")
}
typealias AllFeed = Feed<*>
abstract class PreetyFeed<T>: PreetyAny(), Feed<T>
//// == consume & takeWhile ==
fun <IN> Feed<IN>.consumeOrNull() = try { consume() } catch (_: Feed.End) { null }
fun <IN> Feed<IN>.consumeIf(predicate: Predicate<IN>): IN?
= peek.takeIf(predicate)?.let { consumeOrNull() }
fun <IN> Feed<IN>.takeWhile(predicate: Predicate<IN>): Sequence<IN>
= sequence { while (predicate(peek)) yield(consume()) }
fun <IN> Feed<IN>.takeWhileNotEnd(predicate: Predicate<IN>): Sequence<IN>
= sequence { while (true) yield(consumeIf(predicate) ?: break) }
// NOTES ABOUT Feed:
fun <IN> Feed<IN>.asSequence(): Sequence<IN>
= sequence { while (true) yield(consumeOrNull() ?: break) }
fun <IN> Feed<IN>.asIterable() = asSequence().asIterable()
fun <IN> Feed<IN>.toList() = asIterable().toList()
// - Feed cannot be constructed using empty input
fun Feed<Char>.readText() = asIterable().joinToString("")
// - Feed.peek will yield last item *again* when EOS reached
fun AllFeed.isStickyEnd() = consumeOrNull() == null
// - Patterns like `Until(elementIn(' '), asString(), anyChar)` will fail when EOS entercounted
// easiest workaround: append EOF or terminate char to end of *actual input*
fun <R> AllFeed.catchError(op: Producer<R>): R? = try { op() } catch (e: Exception) { this.error(e.message ?: e.toString()); null }
//// == SliceFeed & StreamFeed ==
// SliceFeed { position, viewport }
// StreamFeed<T, BUF, STREAM> { bufferIterator, convert, nextOne }
// - IteratorFeed
// - ReaderFeed
open class SliceFeed<T>(private val slice: Slice<T>): PreetyFeed<T>() {
init { require(slice.isNotEmpty) {"empty input"} }
protected var position = 0
override val peek get() = try { slice[position] }
catch (_: IndexOutOfBoundsException) { slice[slice.lastIndex] }
override fun consume() = try { slice[position++] }
catch (_: IndexOutOfBoundsException) { --position; throw Feed.End() }
override fun toPreetyDoc() = "Slice".preety() + listOf(peek.rawPreety(), viewport(slice)).joinText("...").surroundText(parens)
protected open fun viewport(slice: Slice<T>): PP
= (position.inbound()..(position+10).inbound()).map(slice::get)
.let { items -> items.preety().joinText(if (items.all { it is Char }) "" else ", ") }
private fun Idx.inbound() = coerceIn(slice.indices)
}
abstract class StreamFeed<T, BUF, STREAM>(private val stream: STREAM): PreetyFeed<T>() {
protected abstract fun bufferIterator(stream: STREAM): Iterator<BUF>
protected abstract fun convert(buffer: BUF): T
private val iterator = bufferIterator(stream)
protected var nextOne: BUF = try { iterator.next() }
catch (_: NoSuchElementException) { throw IllegalArgumentException("empty input") }
private var tailConsumed = false
override val peek get() = convert(nextOne)
override fun consume() = peek.also { moveNext() }
private fun moveNext() {
if (iterator.hasNext()) nextOne = iterator.next()
else if (!tailConsumed) tailConsumed = true
else throw Feed.End()
}
override fun toPreetyDoc() = "Stream".preety() + listOf(peek.rawPreety(), stream.preety()).joinText("...").surroundText(parens)
}
//// == Stream Feeds ==
class IteratorFeed<T>(iterator: Iterator<T>): StreamFeed<T, T, Iterator<T>>(iterator) {
override fun bufferIterator(stream: Iterator<T>) = stream
override fun convert(buffer: T) = buffer
}