Skip to content

Commit 1adeff1

Browse files
committed
one file to rule them all
1 parent 9e33d89 commit 1adeff1

File tree

4 files changed

+142
-127
lines changed

4 files changed

+142
-127
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33

44
# Ignore Gradle build output directory
55
build
6+
7+
.idea

lib/src/main/kotlin/konnik/json/Combinators.kt

Lines changed: 0 additions & 71 deletions
This file was deleted.

lib/src/main/kotlin/konnik/json/JsonParser.kt

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package konnik.json
22

3+
/* ----------------------------------------------------------------------------
4+
*
5+
* PUBLIC INTERFACE
6+
*
7+
* ----------------------------------------------------------------------------
8+
*/
9+
310
/**
411
* Algebraic Data Type representing a parsed JSON-value.
512
*/
@@ -27,7 +34,138 @@ fun parseJson(input: String): JsonValue? =
2734
}
2835

2936

30-
/*
37+
/* ----------------------------------------------------------------------------
38+
*
39+
* IMPLEMENTATION
40+
*
41+
* ----------------------------------------------------------------------------
42+
*/
43+
44+
/**
45+
* Data type representing a parser that parses a some input string and produces
46+
* a value of type A.
47+
*
48+
* If the parser succeeds the parsed value is returned together with the remainder of the input
49+
* that has not been consumed.
50+
*
51+
* If the parser fails null is returned.
52+
*
53+
*/
54+
private typealias Parser<A> = (String) -> Pair<A, String>?
55+
56+
57+
/* ----------------------------------------------------------------------------
58+
*
59+
* COMBINATORS
60+
*
61+
* ----------------------------------------------------------------------------
62+
*/
63+
64+
/**
65+
* Transforms the value of a successful parse to another value.
66+
*
67+
* Fun fact: this means that Parser is a Functor and corresponds to fmap in Haskell.
68+
*/
69+
private fun <A : Any, B : Any> Parser<A>.map(transform: (A) -> B): Parser<B> = { input ->
70+
this(input)?.let { transform(it.first) to it.second }
71+
}
72+
73+
/**
74+
* A parser that always succeeds with the provided value without consuming any input.
75+
*
76+
* Fun fact: this corresponds to pure (from type class Applicative) in Haskell.
77+
*/
78+
79+
private fun <T : Any> succeed(value: T): Parser<T> = { input -> value to input }
80+
81+
/**
82+
* Chain two parsers together where the second parser depends on the produced value
83+
* if the first.
84+
*
85+
* This function is often called flatMap, but I think andThen is a more intuitive name. It's
86+
* also the name used in the Elm, the best programming language ever created so let's go with that.
87+
*
88+
* Fun fact: This is bind / >>= in Haskell and makes our Parser a Monad.
89+
*/
90+
private fun <A : Any, B : Any> Parser<A>.andThen(aToParserB: (A) -> Parser<B>): Parser<B> = { input ->
91+
this(input)?.let { a -> aToParserB(a.first)(a.second) }
92+
}
93+
94+
/**
95+
* Chain two parsers together but keep only the value from de second parser.
96+
*
97+
* Fun fact: This is Applicative *> (or Monad >>) in Haskell
98+
*/
99+
private fun <A : Any, B : Any> Parser<A>.keep(parserB: Parser<B>): Parser<B> =
100+
this.andThen { parserB }
101+
102+
103+
/**
104+
* Chain two parsers together but keep only the value from de first parser.
105+
*
106+
* Fun fact: This is Applicative <* in Haskell
107+
*/
108+
private fun <A : Any, B : Any> Parser<A>.skip(parserB: Parser<B>): Parser<A> =
109+
this.andThen { a -> parserB.map { a } }
110+
111+
/**
112+
* Combine multiple parsers into a parser that returns the value of the first
113+
* parser that succeeds.
114+
*/
115+
private fun <A : Any> oneOf(vararg parsers: Parser<A>): Parser<A> = { input ->
116+
parsers.firstNotNullOfOrNull { it(input) }
117+
}
118+
119+
/**
120+
* Make the construction of a parser lazy. This is sometimes useful for
121+
* preventing stack overflows when defining parsers recursively.
122+
*/
123+
private fun <A : Any> lazy(parser: () -> Parser<A>): Parser<A> = { input ->
124+
parser()(input)
125+
}
126+
127+
/**
128+
* Combine to parsers into one that concatenates the string results of the
129+
* individual parsers into one string.
130+
*/
131+
private operator fun Parser<String>.plus(parserB: Parser<String>): Parser<String> =
132+
this.andThen { valueA -> parserB.map { valueB -> valueA + valueB } }
133+
134+
135+
/* ----------------------------------------------------------------------------
136+
*
137+
* PRIMITIVE PARSERS
138+
*
139+
* ----------------------------------------------------------------------------
140+
*/
141+
142+
/**
143+
* Exact match of a string literal.
144+
*/
145+
private fun s(str: String): Parser<String> = { input ->
146+
when {
147+
input.startsWith(str) -> str to input.drop(str.length)
148+
else -> null
149+
}
150+
}
151+
152+
/**
153+
* Match a character using the provided predicate.
154+
*/
155+
private fun match(test: (Char) -> Boolean): Parser<Char> = { input ->
156+
input.firstOrNull()?.let {
157+
when {
158+
test(it) -> input.first() to input.drop(1)
159+
else -> null
160+
}
161+
}
162+
}
163+
164+
165+
166+
/* ------------------------------------------------------------------------------------------
167+
* GRAMMAR
168+
*
31169
* Implementation of the grammar from https://www.json.org/json-en.html follows.
32170
*
33171
* The parsers defined below are given names that follows the grammar but due to Kotlin
@@ -38,6 +176,7 @@ fun parseJson(input: String): JsonValue? =
38176
*
39177
* To be able to define some recursive parsers a "lazy" variant of some parsers was needed,
40178
* in the form of a function that lazily returns the corresponding parser.
179+
* ------------------------------------------------------------------------------------------
41180
*/
42181

43182

lib/src/main/kotlin/konnik/json/Parser.kt

Lines changed: 0 additions & 55 deletions
This file was deleted.

0 commit comments

Comments
 (0)