Skip to content

Commit 337ff93

Browse files
committed
Make and chains with only one non-skipped component T return T, not tuple
Add operator equivalents to `and` & `skip`.
1 parent 1dee8ac commit 337ff93

File tree

9 files changed

+189
-64
lines changed

9 files changed

+189
-64
lines changed

README.md

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,25 @@
55
A nice parser combinator library for Kotlin
66

77
```kotlin
8-
val booleanGrammar = object : Grammar<BooleanExpression>() {
9-
val id by token("\\w+")
10-
val not by token("!")
11-
val and by token("&")
12-
val or by token("|")
13-
val ws by token("\\s+", ignore = true)
14-
val lpar by token("\\(")
15-
val rpar by token("\\)")
16-
17-
val term =
18-
(id use { Variable(text) }) or
19-
(not and parser(this::term) map { (_, n) -> Not(n) }) or
20-
(lpar and parser(this::rootParser) and rpar map { (_, e, _) -> e })
21-
22-
val andChain = leftAssociative(term, and) { l, _, r -> And(l, r) }
23-
override val rootParser = leftAssociative(andChain, or) { l, _, r -> Or(l, r) }
24-
}
25-
26-
val ast = booleanGrammar.parseToEnd("a & !b | b & (!a | c)")
8+
val booleanGrammar = object : Grammar<BooleanExpression>() {
9+
val id by token("\\w+")
10+
val not by token("!")
11+
val and by token("&")
12+
val or by token("|")
13+
val ws by token("\\s+", ignore = true)
14+
val lpar by token("\\(")
15+
val rpar by token("\\)")
16+
17+
val term =
18+
(id use { Variable(text) }) or
19+
(-not * parser(this::term) map { (Not(it) }) or
20+
(-lpar * parser(this::rootParser) * -rpar)
21+
22+
val andChain = leftAssociative(term, and) { l, _, r -> And(l, r) }
23+
val rootParser = leftAssociative(andChain, or) { l, _, r -> Or(l, r) }
24+
}
25+
26+
val ast = booleanGrammar.parseToEnd("a & !b | b & (!a | c)")
2727
```
2828

2929
### Using with Gradle
@@ -145,6 +145,9 @@ There are several kinds of combinators included in `better-parse`:
145145
val bbWithoutA = skip(a) and b and skip(a) and b and skip(a) // Parser<Tuple2<B, B>>
146146
```
147147
148+
> If all the components in an `and` chain are skipped except for one `Parser<T>`, the resulting parser
149+
is `Parser<T>`, not `Parser<Tuple1<T>>`.
150+
148151
To process the resulting `Tuple`, use the aforementioned `map` and `use`. These parsers are equivalent:
149152
150153
* ```val fCall = id and skip(lpar) and id and skip(rpar) map { (fName, arg) -> FunctionCall(fName, arg) }```
@@ -155,6 +158,17 @@ There are several kinds of combinators included in `better-parse`:
155158
156159
> There are `Tuple` classes up to `Tuple16` and the corresponding `and` overloads.
157160
161+
##### Operators
162+
163+
There are operator overloads for more compact `and` chains definition:
164+
165+
* `a * b` is equivalent to `a and b`.
166+
167+
* `-a` is equivalent to `skip(a)`.
168+
169+
With these operators, the parser `a and skip(b) and skip(c) and d` can also be defined as
170+
`a * -b * -c * d`.
171+
158172
* `or`
159173
160174
The alternative combinator tries to parse the sequence with the parsers it combines one by one until one succeeds. If all the parsers fail,
@@ -225,7 +239,7 @@ To use a parser that has not been constructed yet, reference it with `parser { s
225239
val term =
226240
constParser or
227241
variableParser or
228-
(skip(lpar) and parser(this::term) and skip(lpar) use { t1 })
242+
(-lpar and parser(this::term) and -rpar)
229243
```
230244
231245
# Examples

build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
group 'com.github.h0tk3y.betterParse'
2-
version '0.1'
2+
version '0.2'
33

44
wrapper {
55
gradleVersion = '3.3'
@@ -18,6 +18,7 @@ buildscript {
1818
}
1919

2020
apply plugin: 'kotlin'
21+
apply plugin: 'maven'
2122

2223
repositories {
2324
jcenter()

buildSrc/src/main/kotlin/AndCodegen.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ fun andCodegen(maxN: Int, outputFile: String) {
2222
Tuple${i + 1}($casts)
2323
})
2424
""".trimIndent() + "\n")
25+
26+
appendln("""
27+
@JvmName("and$i${"Operator"}") inline operator fun <$reifiedNext>
28+
AndCombinator<Tuple$i$generics>.times(p${i + 1}: Parser<T${i + 1}>) = this and p${i + 1}
29+
""".trimIndent() + "\n\n")
2530
}
2631
}
2732

demo/src/main/kotlin/com/example/ArithmeticsEvaluator.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ class ArithmeticsEvaluator : Grammar<Int>() {
1919

2020
val number = num use { text.toInt() }
2121
val term: Parser<Int> = number or
22-
(skip(minus) and parser(this::term) use { -t1 }) or
23-
(skip(lpar) and parser(this::rootParser) and skip(rpar) use { t1 })
22+
(skip(minus) and parser(this::term) map { -it }) or
23+
(skip(lpar) and parser(this::rootParser) and skip(rpar))
2424

2525
val powChain = leftAssociative(term, pow) { a, _, b -> Math.pow(a.toDouble(), b.toDouble()).toInt() }
2626

demo/src/main/kotlin/com/example/BooleanExpression.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,10 @@ val booleanGrammar = object : Grammar<BooleanExpression>() {
3030

3131
val term: Parser<BooleanExpression> =
3232
(tru asJust TRUE) or
33-
(fal asJust FALSE) or
34-
(id map { Variable(it.text) }) or
35-
(not and parser(this::term) map { (_, t) -> Not(t) }) or
36-
(lpar and parser(this::implChain) and rpar map { (_, i, _) -> i })
37-
33+
(fal asJust FALSE) or
34+
(id map { Variable(it.text) }) or
35+
(-not * parser(this::term) map { Not(it) }) or
36+
(-lpar * parser(this::implChain) * -rpar)
3837

3938
val andChain = leftAssociative(term, and) { a, _, b -> And(a, b) }
4039
val orChain = leftAssociative(andChain, or) { a, _, b -> Or(a, b) }

src/main/kotlin/com/github/h0tk3y/betterParse/combinators/AndCombinator.kt

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,44 @@ import com.github.h0tk3y.betterParse.parser.ErrorResult
55
import com.github.h0tk3y.betterParse.parser.ParseResult
66
import com.github.h0tk3y.betterParse.parser.Parsed
77
import com.github.h0tk3y.betterParse.parser.Parser
8-
import com.github.h0tk3y.betterParse.utils.Tuple
9-
import com.github.h0tk3y.betterParse.utils.Tuple1
108
import com.github.h0tk3y.betterParse.utils.Tuple2
119

1210
/** Parses the sequence with the receiver [Parser] and then with the [other] parser. If both suceed, returns a [Tuple2]
1311
* with the values from the [Parsed] results. Otherwise, returns the [ErrorResult] of the failed parser. */
1412
infix inline fun <reified A, reified B> Parser<A>.and(other: Parser<B>) =
1513
AndCombinator(listOf(this, other)) { (a1, a2) -> Tuple2(a1 as A, a2 as B) }
1614

15+
/** The same as `this `[and]` other`*/
16+
operator inline fun <reified A, reified B> Parser<A>.times(other: Parser<B>) = this and other
17+
1718
/** Parses the sequence with the receiver [Parser] and then with the [other] parser. If both suceed, returns a [Tuple2]
1819
* with the values from the [Parsed] results. Otherwise, returns the [ErrorResult] of the failed parser. */
19-
infix inline fun <reified A, reified B> AndCombinator<Tuple1<A>>.and(other: Parser<B>) =
20-
AndCombinator(consumers + other) { (a1, a2) -> Tuple2(a1 as A, a2 as B) }
20+
@JvmName("and0")
21+
infix inline fun <reified A, reified B> AndCombinator<A>.and(other: Parser<B>) =
22+
AndCombinator(consumers + listOf(other)) { (a1, a2) -> Tuple2(a1 as A, a2 as B) }
23+
24+
/** The same as `this `[and]` other`*/
25+
operator inline fun <reified A, reified B> AndCombinator<A>.times(other: Parser<B>) = this and other
2126

22-
class AndCombinator<out R : Tuple> internal @PublishedApi constructor(
27+
class AndCombinator<out R> internal @PublishedApi constructor(
2328
val consumers: List<Any>,
2429
val transform: (List<*>) -> R
2530
) : Parser<R> {
2631

27-
fun process(tokens: Sequence<TokenMatch>): Pair<List<ParseResult<*>>, Sequence<TokenMatch>> {
32+
private fun process(tokens: Sequence<TokenMatch>): Pair<List<ParseResult<*>>, Sequence<TokenMatch>> {
2833
var lastTokens = tokens
29-
var errorResult: ErrorResult? = null
3034
return consumers.map { consumer ->
3135
val parser = when (consumer) {
3236
is Parser<*> -> consumer
33-
is SkipParser<*> -> consumer.innerParser
37+
is SkipParser -> consumer.innerParser
3438
else -> throw IllegalArgumentException()
3539
}
36-
val result = errorResult ?: parser.tryParse(lastTokens)
40+
val result = parser.tryParse(lastTokens)
3741
when (result) {
3842
is ErrorResult -> return@process listOf(result) to lastTokens
3943
is Parsed<*> -> lastTokens = result.remainder
4044
}
41-
if (consumer is SkipParser<*>) null else result
45+
if (consumer is SkipParser) null else result
4246
}.filterNotNull() to lastTokens
4347
}
4448

@@ -47,25 +51,4 @@ class AndCombinator<out R : Tuple> internal @PublishedApi constructor(
4751
return results.firstOrNull { it is ErrorResult } as? ErrorResult
4852
?: results.filterIsInstance<Parsed<*>>().let { Parsed(transform(it.map { it.value }), remainder) }
4953
}
50-
}
51-
52-
/** Wraps a [Parser] to distinguish it from other parsers when it is used in [and] functions. */
53-
class SkipParser<T>(val innerParser: Parser<T>)
54-
55-
/** Wraps a [Parser] to distinguish it from other parsers when it is used in [and] functions. */
56-
fun <T> skip(parser: Parser<T>) = SkipParser(parser)
57-
58-
/** Parses the sequence with the receiver [Parser] and the wrapped [other] parser, but returns the [Parsed] result
59-
* from the receiver parser. */
60-
infix fun <T : Tuple, B> AndCombinator<T>.and(other: SkipParser<B>) =
61-
AndCombinator(consumers + other, transform)
62-
63-
/** Parses the sequence with the receiver [Parser] and the wrapped [other] parser, but returns the [Parsed] result
64-
* with a value from the receiver parser in a [Tuple1]. */
65-
infix inline fun <reified T> Parser<T>.and(other: SkipParser<*>) =
66-
AndCombinator(listOf(this, other), { (a) -> Tuple1(a as T) })
67-
68-
/** Parses the wrapped receiver [Parser] and the [other] parser and returns the [Parsed] result
69-
* with a value from the [other] parser in a [Tuple1]. */
70-
infix inline fun <reified T> SkipParser<*>.and(other: Parser<T>) =
71-
AndCombinator(listOf(this, other)) { (b) -> Tuple1(b as T) }
54+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.github.h0tk3y.betterParse.combinators
2+
3+
import com.github.h0tk3y.betterParse.parser.Parsed
4+
import com.github.h0tk3y.betterParse.parser.Parser
5+
import com.github.h0tk3y.betterParse.utils.Tuple
6+
import com.github.h0tk3y.betterParse.utils.Tuple1
7+
8+
/** Wraps a [Parser] to distinguish it from other parsers when it is used in [and] functions. */
9+
class SkipParser(val innerParser: Parser<*>)
10+
11+
/** Wraps a [Parser] to distinguish it from other parsers when it is used in [and] functions. */
12+
fun <T> skip(parser: Parser<T>) = SkipParser(parser)
13+
14+
/** The same as `[skip] of this parser. ` */
15+
operator fun Parser<*>.unaryMinus() = skip(this)
16+
17+
/** Parses the sequence with the receiver [Parser] and the wrapped [other] parser, but returns the [Parsed] result
18+
* from the receiver parser. */
19+
infix fun <T : Tuple> AndCombinator<T>.and(other: SkipParser) =
20+
AndCombinator(consumers + other, transform)
21+
22+
operator fun <T : Tuple> AndCombinator<T>.times(other: SkipParser) = this and other
23+
24+
/** Parses the sequence with the receiver [Parser] and the wrapped [other] parser, but returns the [Parsed] result
25+
* with a value from the receiver parser in a [Tuple1]. */
26+
infix inline fun <reified T> Parser<T>.and(other: SkipParser) =
27+
AndCombinator(listOf(this, other), { (a) -> a as T } )
28+
29+
/** The same as `this `[and]` other`*/
30+
operator inline fun <reified T> Parser<T>.times(other: SkipParser) = this and other
31+
32+
/** Parses the wrapped receiver [Parser] and the [other] parser and returns the [Parsed] result
33+
* with a value from the [other] parser in a [Tuple1]. */
34+
infix inline fun <reified T> SkipParser.and(other: Parser<T>) =
35+
AndCombinator(listOf(this, other)) { (b) -> b as T }
36+
37+
/** The same as `this `[and]` other`*/
38+
operator inline fun <reified T> SkipParser.times(other: Parser<T>) = this and other
39+
40+
infix fun SkipParser.and(other: SkipParser): SkipParser {
41+
val parsersLeft = if (innerParser is AndCombinator) innerParser.consumers else listOf(innerParser)
42+
return SkipParser(AndCombinator(parsersLeft + other.innerParser, { Unit }))
43+
}
44+
45+
/** The same as `this `[and]` other`*/
46+
operator fun SkipParser.times(other: SkipParser) = this and other

0 commit comments

Comments
 (0)