~repos /only-bible-app

#kotlin#android#ios

git clone https://pyrossh.dev/repos/only-bible-app.git

The only bible app you will ever need. No ads. No in-app purchases. No distractions.


a454b21e pyrossh

1 year ago
improve parser
composeApp/src/commonMain/kotlin/dev/pyrossh/only_bible_app/AppHost.kt CHANGED
@@ -3,7 +3,6 @@ package dev.pyrossh.only_bible_app
3
3
  import androidx.compose.animation.EnterTransition
4
4
  import androidx.compose.animation.ExitTransition
5
5
  import androidx.compose.animation.core.tween
6
- import androidx.compose.material3.Text
7
6
  import androidx.compose.runtime.Composable
8
7
  import androidx.compose.runtime.CompositionLocalProvider
9
8
  import androidx.navigation.compose.NavHost
@@ -20,7 +19,6 @@ fun AppHost(
20
19
  model: AppViewModel
21
20
  ) {
22
21
  val navController = rememberNavController()
23
- Text("Failed to load data")
24
22
  if (!model.isLoading) {
25
23
  CompositionLocalProvider(LocalNavController provides navController) {
26
24
  NavHost(
composeApp/src/commonMain/kotlin/dev/pyrossh/only_bible_app/utils/SimpleParser.kt CHANGED
@@ -1,9 +1,25 @@
1
1
  package dev.pyrossh.only_bible_app.utils
2
2
 
3
+ data class Pos(val start: Int, val end: Int)
4
+
5
+ interface Node {
6
+ var pos: Pos
7
+ }
8
+
9
+ data class TextNode(override var pos: Pos, val value: String) : Node
10
+ data class TagNode(
11
+ override var pos: Pos,
12
+ val name: String,
13
+ val attributes: Map<String, String> = emptyMap(),
14
+ var child: TextNode? = null
15
+ ) : Node {
16
+ fun isSelfClosing() = name == "br"
17
+ }
18
+
3
19
  class SimpleParser(val input: CharSequence) {
4
20
  var cursor: Int = 0
5
21
 
6
- private fun step(): Char {
22
+ private fun consume(): Char {
7
23
  return input[cursor++]
8
24
  }
9
25
 
@@ -15,24 +31,64 @@ class SimpleParser(val input: CharSequence) {
15
31
  cursor++
16
32
  }
17
33
 
34
+ private fun skip(n: Int) {
35
+ cursor += n
36
+ }
37
+
18
38
  private fun hasNext(): Boolean {
19
39
  return cursor < input.length
20
40
  }
21
41
 
22
- fun read(predicate: (Char) -> Boolean): String {
42
+ fun parse(): List<Node> {
23
- val result = StringBuilder()
43
+ val nodes = mutableListOf<Node>()
24
- while (predicate(peek())) {
44
+ while (hasNext()) {
45
+ if (peek() != '<') {
46
+ nodes.add(parseTextNode())
47
+ } else {
25
- result.append(step())
48
+ nodes.add(parseTag())
49
+ }
26
50
  }
27
- return result.toString()
51
+ return nodes
28
52
  }
29
53
 
30
- fun read(pattern: CharSequence): String {
54
+ private fun parseTextNode(): TextNode {
55
+ val start = cursor
31
56
  val result = StringBuilder()
32
- var patternCursor = 0
33
- while (peek() == pattern[patternCursor++]) {
57
+ while (hasNext() && peek() != '<') {
34
- result.append(skip())
58
+ result.append(consume())
35
59
  }
60
+ return TextNode(Pos(start, cursor), result.toString())
61
+ }
62
+
63
+ private fun parseTag(): TagNode {
64
+ val start = cursor
65
+ if (input.substring(start, start + 4) == "<br>") {
66
+ skip(4)
67
+ return TagNode(Pos(start, cursor), "br")
68
+ }
69
+ for (t in listOf("red", "yellow", "em")) {
70
+ if (input.substring(start, start + 2 + t.length) == "<$t>") {
71
+ val redNode = TagNode(Pos(start, 0), t)
72
+ skip(t.length + 2)
73
+ val textNode = parseTextNode()
74
+ if (input.substring(textNode.pos.end, textNode.pos.end + t.length + 3) == "</$t>") {
75
+ skip(t.length + 3)
76
+ redNode.pos = Pos(start, textNode.pos.end + t.length + 3)
77
+ redNode.child = textNode
36
- return result.toString()
78
+ return redNode
79
+ } else {
80
+ throw RuntimeException(
81
+ "failed find closing tag for <red> at ${textNode.pos.end} ${
82
+ input.substring(
83
+ textNode.pos.end,
84
+ textNode.pos.end + t.length + 3
85
+ )
86
+ }"
87
+ )
88
+ }
89
+ }
90
+ }
91
+
92
+ throw RuntimeException("failed to parseTag at $start ${input.substring(start, start + 10)}")
37
93
  }
38
94
  }