Skip to content

Commit 606a5a8

Browse files
authored
Merge pull request #4 from kgbier/feature/uplift-parser-type
Feature/uplift parser type
2 parents dccf205 + 86b8201 commit 606a5a8

File tree

7 files changed

+131
-136
lines changed

7 files changed

+131
-136
lines changed

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/commonMain/kotlin/com/kgbier/graphql/parser/GraphQl.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.kgbier.graphql.parser
22

33
import com.kgbier.graphql.parser.Parsers.always
4+
import com.kgbier.graphql.parser.Parsers.char
45
import com.kgbier.graphql.parser.Parsers.character
56
import com.kgbier.graphql.parser.Parsers.deferred
67
import com.kgbier.graphql.parser.Parsers.literal
@@ -19,7 +20,7 @@ internal class GraphQl {
1920
*/
2021

2122
// sourceChar -> '[\u0009\u000A\u000D\u0020-\uFFFF]'
22-
val sourceChar = character
23+
val sourceChar = char
2324

2425
// name -> '[_A-Za-z][_0-9A-Za-z]'
2526
val name = Parsers.prefix { it.isLetterOrDigit() || it == '_' }
@@ -63,7 +64,7 @@ internal class GraphQl {
6364
// commentChar -> sourceChar != lineTerminator
6465
val commentChar = zip(
6566
notOneOf(listOf(lineTerminator)),
66-
character
67+
char
6768
).map { (_, c) -> c }
6869

6970
// comment -> " '#' { commentChar }? "

src/commonMain/kotlin/com/kgbier/graphql/parser/GraphQlParser.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ object GraphQLParser {
1111

1212
internal fun parseWithResult(str: String): GraphQlParseResult {
1313
val result = parser.document.parse(str.trim())
14-
return GraphQlParseResult(result.match, result.rest)
14+
return GraphQlParseResult(result.match, result.remainder)
1515
}
1616

1717
fun parse(str: String): Document? = parseWithResult(str).match

src/commonMain/kotlin/com/kgbier/graphql/parser/Parser.kt

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,46 @@ package com.kgbier.graphql.parser
33
import com.kgbier.graphql.parser.structure.*
44
import com.kgbier.graphql.parser.substring.Substring
55

6-
internal interface Parser<A> {
7-
fun run(str: Substring): A?
6+
internal fun interface Parser<Output> {
7+
operator fun invoke(str: Substring): Output?
88
}
99

10-
internal fun <A> Parser<A>.parse(str: Substring) = run(str)
11-
12-
internal data class ParseResult<A>(val match: A?, val rest: Substring)
10+
internal data class ParseResult<Output>(val match: Output?, val remainder: Substring)
1311

1412
internal fun <A> Parser<A>.parse(str: String): ParseResult<A> {
1513
val substring = Substring(str)
16-
val match = parse(substring)
14+
val match = invoke(substring)
1715
return ParseResult(match, substring)
1816
}
1917

20-
internal inline fun <A, B> Parser<A>.map(crossinline f: (A) -> B): Parser<B> = object : Parser<B> {
21-
override fun run(str: Substring): B? = this@map.parse(str)?.let(f)
22-
}
23-
24-
internal fun <A> Parser<A>.erase(): Parser<Unit> = object : Parser<Unit> {
25-
override fun run(str: Substring) = this@erase.parse(str)?.let { Unit }
18+
internal inline fun <A, B> Parser<A>.map(
19+
crossinline transform: (A) -> B,
20+
): Parser<B> = Parser { invoke(it)?.let(transform) }
21+
22+
internal fun <A> Parser<A>.erase(): Parser<Unit> =
23+
Parser { invoke(it)?.let { } }
24+
25+
internal inline fun <A, B> Parser<A>.flatMap(
26+
crossinline f: (A) -> Parser<B>,
27+
): Parser<B> = Parser {
28+
val originalState = it.state
29+
val matchA = invoke(it)
30+
val parserB = matchA?.let(f)
31+
val matchB = parserB?.invoke(it)
32+
if (matchB == null) {
33+
it.state = originalState
34+
null
35+
} else matchB
2636
}
2737

28-
internal inline fun <A, B> Parser<A>.flatMap(crossinline f: (A) -> Parser<B>): Parser<B> = object : Parser<B> {
29-
override fun run(str: Substring): B? {
30-
val originalState = str.state
31-
val matchA = this@flatMap.parse(str)
32-
val parserB = matchA?.let(f)
33-
val matchB = parserB?.parse(str)
34-
return if (matchB == null) {
35-
str.state = originalState
36-
null
37-
} else matchB
38-
}
39-
}
40-
41-
internal fun <A, B> zip(a: Parser<A>, b: Parser<B>): Parser<Tuple2<A, B>> = object : Parser<Tuple2<A, B>> {
42-
override fun run(str: Substring): Tuple2<A, B>? {
43-
val originalState = str.state
44-
val resultA = a.parse(str) ?: return null
45-
val resultB = b.parse(str)
46-
return if (resultB == null) {
47-
str.state = originalState
48-
null
49-
} else Tuple2(resultA, resultB)
50-
}
38+
internal fun <A, B> zip(a: Parser<A>, b: Parser<B>): Parser<Tuple2<A, B>> = Parser {
39+
val originalState = it.state
40+
val resultA = a(it) ?: return@Parser null
41+
val resultB = b(it)
42+
if (resultB == null) {
43+
it.state = originalState
44+
null
45+
} else Tuple2(resultA, resultB)
5146
}
5247

5348
internal fun <A, B, C> zip(
Lines changed: 76 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,112 @@
1+
/**
2+
* Redundant Unit returns are necessary when running in JS.
3+
* Functions which do not explicitly return a Unit value will return `undefined` in JS.
4+
*/
5+
@file:Suppress("RedundantUnitExpression")
6+
17
package com.kgbier.graphql.parser
28

39
import com.kgbier.graphql.parser.structure.Maybe
4-
import com.kgbier.graphql.parser.substring.Substring
510

611
internal object Parsers {
712

8-
val always = object : Parser<Unit> {
9-
override fun run(str: Substring) = Unit
10-
}
13+
val always: Parser<Unit> = Parser { Unit }
1114

12-
fun <A> always(a: A) = object : Parser<A> {
13-
override fun run(str: Substring) = a
14-
}
15+
fun <Output> always(constant: Output): Parser<Output> = Parser { constant }
1516

16-
fun <A> never() = object : Parser<A> {
17-
override fun run(str: Substring): A? = null
18-
}
17+
fun <Output> never(): Parser<Output> = Parser { null }
1918

20-
fun <A> deferred(f: () -> Parser<A>) = object : Parser<A> {
21-
override fun run(str: Substring): A? = f().parse(str)
22-
}
19+
fun <Output> deferred(provideParser: () -> Parser<Output>): Parser<Output> =
20+
Parser { provideParser()(it) }
2321

24-
fun <A> zeroOrMore(p: Parser<A>) = zeroOrMore<A, Unit>(p, null)
25-
26-
fun <A, B> zeroOrMore(p: Parser<A>, separatedBy: Parser<B>?) = object : Parser<List<A>> {
27-
override fun run(str: Substring): List<A> {
28-
var remainderState = str.state
29-
val matches = mutableListOf<A>()
30-
while (true) {
31-
val match = p.parse(str) ?: break
32-
remainderState = str.state
33-
matches.add(match)
34-
if (separatedBy != null) {
35-
separatedBy.parse(str) ?: return matches
36-
}
22+
fun <Output> maybe(parser: Parser<Output>): Parser<Maybe<Output>> =
23+
Parser { Maybe(parser(it)) }
24+
25+
fun <Output> zeroOrMore(parser: Parser<Output>) =
26+
zeroOrMore<Output, Unit>(parser, null)
27+
28+
fun <Output, SeparatedBy> zeroOrMore(
29+
parser: Parser<Output>,
30+
separatedBy: Parser<SeparatedBy>?,
31+
): Parser<List<Output>> = Parser {
32+
var remainderState = it.state
33+
val matches = mutableListOf<Output>()
34+
while (true) {
35+
val match = parser(it) ?: break
36+
remainderState = it.state
37+
matches.add(match)
38+
if (separatedBy != null) {
39+
separatedBy(it) ?: return@Parser matches
3740
}
38-
str.state = remainderState
39-
return matches
4041
}
42+
it.state = remainderState
43+
44+
matches
4145
}
4246

43-
fun <A> oneOrMore(p: Parser<A>) = oneOrMore<A, Unit>(p, null)
47+
fun <Output> oneOrMore(parser: Parser<Output>) =
48+
oneOrMore<Output, Unit>(parser, null)
4449

45-
fun <A, B> oneOrMore(p: Parser<A>, separatedBy: Parser<B>?) = zeroOrMore(p, separatedBy).flatMap {
50+
fun <Output, SeparatedBy> oneOrMore(
51+
parser: Parser<Output>,
52+
separatedBy: Parser<SeparatedBy>?,
53+
) = zeroOrMore(parser, separatedBy).flatMap {
4654
if (it.isEmpty()) never() else always(it)
4755
}
4856

49-
fun <A> oneOf(ps: List<Parser<out A>>) = object : Parser<A> {
50-
override fun run(str: Substring): A? {
51-
for (p in ps) {
52-
val match = p.run(str)
53-
if (match != null) return match
54-
}
55-
return null
57+
fun <Output> oneOf(parsers: List<Parser<out Output>>): Parser<Output> = Parser {
58+
for (p in parsers) {
59+
val match = p(it)
60+
if (match != null) return@Parser match
5661
}
57-
}
5862

59-
fun <A> notOneOf(ps: List<Parser<A>>) = object : Parser<Unit> {
60-
override fun run(str: Substring): Unit? {
61-
for (p in ps) {
62-
val match = p.run(str)
63-
if (match != null) return null
64-
}
65-
return Unit
66-
}
63+
null
6764
}
6865

69-
fun <A> maybe(p: Parser<A>) = object : Parser<Maybe<A>> {
70-
override fun run(str: Substring): Maybe<A> {
71-
val match = p.parse(str)
72-
return Maybe(match)
66+
fun <Output> notOneOf(parsers: List<Parser<Output>>): Parser<Unit> = Parser {
67+
for (p in parsers) {
68+
val match = p(it)
69+
if (match != null) return@Parser null
7370
}
71+
Unit
7472
}
7573

76-
val integer = object : Parser<Int> {
77-
override fun run(str: Substring): Int? {
78-
val result = str.takeWhile { it.isDigit() }
79-
return if (result.isNotEmpty()) {
80-
str.advance(result.length)
81-
result.toString().toInt()
82-
} else null
83-
}
74+
val int: Parser<Int> = Parser {
75+
val result = it.takeWhile { it.isDigit() }
76+
if (result.isNotEmpty()) {
77+
it.advance(result.length)
78+
result.toString().toIntOrNull()
79+
} else null
8480
}
8581

86-
val character = object : Parser<Char> {
87-
override fun run(str: Substring): Char? {
88-
return if (str.isNotEmpty()) {
89-
val result = str.first()
90-
str.advance()
91-
result
92-
} else null
93-
}
82+
val char: Parser<Char> = Parser {
83+
if (it.isNotEmpty()) {
84+
val result = it.first()
85+
it.advance()
86+
result
87+
} else null
9488
}
9589

96-
fun character(char: Char) = object : Parser<Char> {
97-
override fun run(str: Substring): Char? {
98-
return if (str.isNotEmpty() && str.first() == char) {
99-
str.advance()
100-
char
101-
} else null
102-
}
90+
fun character(char: Char): Parser<Char> = Parser {
91+
if (it.isNotEmpty() && it.first() == char) {
92+
it.advance()
93+
char
94+
} else null
10395
}
10496

105-
fun literal(literal: String) = object : Parser<Unit> {
106-
override fun run(str: Substring): Unit? {
107-
return if (str.startsWith(literal)) {
108-
str.advance(literal.length)
109-
} else null
110-
}
97+
fun literal(literal: String): Parser<Unit> = Parser {
98+
if (it.startsWith(literal)) {
99+
it.advance(literal.length)
100+
Unit
101+
} else null
111102
}
112103

113104
// TODO: consider `Substring` instead of `String`?
114-
fun prefix(predicate: (Char) -> Boolean) = object : Parser<String> {
115-
override fun run(str: Substring): String? {
116-
val result = str.takeWhile(predicate)
117-
return if (result.isNotEmpty()) {
118-
str.advance(result.length)
119-
result.toString()
120-
} else null
121-
}
105+
fun prefix(predicate: (Char) -> Boolean): Parser<String> = Parser {
106+
val result = it.takeWhile(predicate)
107+
if (result.isNotEmpty()) {
108+
it.advance(result.length)
109+
result.toString()
110+
} else null
122111
}
123112
}

src/commonTest/kotlin/com/kgbier/graphql/parser/GraphQlTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,16 @@ internal class GraphQlTest {
147147
assertNull(testSubject(" "))
148148
}
149149

150+
@Test
151+
fun stringCharacter() {
152+
fun testSubject(str: String): Char? = graphQlparser.stringCharacter.parse(str).match
153+
154+
assertEquals('H', testSubject("H"))
155+
assertNull(testSubject("\""))
156+
assertNull(testSubject("\n"))
157+
assertNull(testSubject("\r"))
158+
}
159+
150160
@Test
151161
fun stringValue() {
152162
fun testSubject(str: String): Value? = graphQlparser.stringValue.parse(str).match

src/commonTest/kotlin/com/kgbier/graphql/parser/ParsersTest.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,30 @@ internal class ParsersTest {
88

99
@Test
1010
fun integer() {
11-
val result = Parsers.integer.parse("123abc")
11+
val result = Parsers.int.parse("123abc")
1212
assertEquals(123, result.match)
13-
assertEquals("abc", result.rest.toString())
13+
assertEquals("abc", result.remainder.toString())
1414
}
1515

1616
@Test
1717
fun zeroOrMore() {
18-
var result = Parsers.zeroOrMore(Parsers.integer, separatedBy = Parsers.literal(" ")).parse("1 2 3 abc")
18+
var result = Parsers.zeroOrMore(Parsers.int, separatedBy = Parsers.literal(" ")).parse("1 2 3 abc")
1919
assertEquals(listOf(1, 2, 3), result.match)
20-
assertEquals(" abc", result.rest.toString())
20+
assertEquals(" abc", result.remainder.toString())
2121

22-
result = Parsers.zeroOrMore(Parsers.integer, separatedBy = Parsers.literal(" ")).parse("abc")
22+
result = Parsers.zeroOrMore(Parsers.int, separatedBy = Parsers.literal(" ")).parse("abc")
2323
assertEquals(emptyList(), result.match)
24-
assertEquals("abc", result.rest.toString())
24+
assertEquals("abc", result.remainder.toString())
2525
}
2626

2727
@Test
2828
fun oneOrMore() {
29-
var result = Parsers.oneOrMore(Parsers.integer, separatedBy = Parsers.literal(" ")).parse("1 2 3 abc")
29+
var result = Parsers.oneOrMore(Parsers.int, separatedBy = Parsers.literal(" ")).parse("1 2 3 abc")
3030
assertEquals(listOf(1, 2, 3), result.match)
31-
assertEquals(" abc", result.rest.toString())
31+
assertEquals(" abc", result.remainder.toString())
3232

33-
result = Parsers.oneOrMore(Parsers.integer, separatedBy = Parsers.literal(" ")).parse("abc")
33+
result = Parsers.oneOrMore(Parsers.int, separatedBy = Parsers.literal(" ")).parse("abc")
3434
assertNull(result.match)
35-
assertEquals("abc", result.rest.toString())
35+
assertEquals("abc", result.remainder.toString())
3636
}
3737
}

0 commit comments

Comments
 (0)