Skip to content

Commit e4c4eea

Browse files
committed
Parser rework
1 parent b360a1c commit e4c4eea

File tree

15 files changed

+265
-117
lines changed

15 files changed

+265
-117
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ dokkaVersion=1.4.10
66
kotlinVersion=1.4.10
77
ktlintVersion=9.4.1
88

9-
libraryVersion=0.2.0
9+
libraryVersion=0.3.0
1010
libraryDescription=Kotlin Multiplatform implementation of the CYK algorithm.
1111
publishedGroupId=eu.yeger
1212
artifact=cyk-algorithm

src/commonMain/kotlin/eu/yeger/cyk/CYK.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@ package eu.yeger.cyk
22

33
import eu.yeger.cyk.model.*
44
import eu.yeger.cyk.model.withSymbolAt
5+
import eu.yeger.cyk.parser.word
56

67
public fun cyk(
7-
inputString: String,
8-
block: () -> Result<Grammar>,
8+
wordString: String,
9+
grammar: () -> Result<Grammar>
910
): Result<CYKModel> {
10-
return block().map { grammar -> cyk(Word(inputString), grammar) }
11+
return word(wordString).with(grammar(), ::cyk)
12+
}
13+
14+
public fun cyk(
15+
word: Result<Word>,
16+
grammar: Result<Grammar>
17+
): Result<CYKModel> {
18+
return word.with(grammar, ::cyk)
1119
}
1220

1321
public fun cyk(

src/commonMain/kotlin/eu/yeger/cyk/Result.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ public sealed class Result<out T : Any> {
55
public data class Failure<T : Any>(val error: String) : Result<T>()
66
}
77

8-
public fun <T : Any> succeed(data: T): Result.Success<T> {
9-
return Result.Success(data)
8+
public fun <T : Any> T.asSuccess(): Result.Success<T> {
9+
return Result.Success(this)
1010
}
1111

1212
public fun <T : Any> fail(error: String): Result.Failure<T> {
@@ -82,3 +82,21 @@ public fun <T : Any> Result<T>.getErrorOrElse(block: (T) -> String): String {
8282
is Result.Failure -> error
8383
}
8484
}
85+
86+
public fun <T : Any, U : Any, V : Any> Result<T>.with(other: Result<U>, block: (T, U) -> V): Result<V> {
87+
return when {
88+
this is Result.Success && other is Result.Success -> Result.Success(block(this.data, other.data))
89+
this is Result.Failure && other is Result.Failure -> Result.Failure("${this.error}\n${other.error}")
90+
this is Result.Failure -> Result.Failure(this.error)
91+
other is Result.Failure -> Result.Failure(other.error)
92+
else -> Result.Failure("Impossible state reached.")
93+
}
94+
}
95+
96+
public fun <T : Any, U : Any> List<T>.mapAsResult(
97+
transform: T.() -> Result<U>,
98+
): Result<List<U>> {
99+
return fold(Result.Success(emptyList<U>()) as Result<List<U>>) { acc, x ->
100+
acc.with(transform(x), List<U>::plus)
101+
}
102+
}

src/commonMain/kotlin/eu/yeger/cyk/RunningCYK.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
package eu.yeger.cyk
22

33
import eu.yeger.cyk.model.*
4+
import eu.yeger.cyk.parser.word
45

56
public fun runningCYK(
6-
inputString: String,
7-
block: () -> Result<Grammar>,
7+
wordString: String,
8+
grammar: () -> Result<Grammar>
89
): Result<List<CYKState>> {
9-
return block().map { grammar -> runningCYK(Word(inputString), grammar) }
10+
return word(wordString).with(grammar(), ::runningCYK)
11+
}
12+
13+
public fun runningCYK(
14+
word: Result<Word>,
15+
grammar: Result<Grammar>
16+
): Result<List<CYKState>> {
17+
return word.with(grammar, ::runningCYK)
1018
}
1119

1220
public fun runningCYK(

src/commonMain/kotlin/eu/yeger/cyk/Utilities.kt

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

src/commonMain/kotlin/eu/yeger/cyk/model/CYKModel.kt

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package eu.yeger.cyk.model
22

3-
import eu.yeger.cyk.epsilon
4-
53
public data class CYKModel
64
internal constructor(
75
val word: Word,
@@ -30,15 +28,12 @@ internal constructor(
3028
}
3129
val maxTerminalSymbolLength = word.maxOf { terminalSymbol ->
3230
terminalSymbol.toString().length
33-
}.coerceAtLeast(epsilon.length)
31+
}
3432
val maxLength = maxOf(maxRuleSetLength, maxTerminalSymbolLength)
3533

3634
val columnPadding = " | "
3735
val wordRowString = word.joinToString(columnPadding, prefix = "| ", postfix = " |") { nonTerminalSymbol ->
38-
when {
39-
nonTerminalSymbol.symbol.isEmpty() -> epsilon
40-
else -> nonTerminalSymbol.toString()
41-
}.padEnd(maxLength, ' ')
36+
nonTerminalSymbol.toString().padEnd(maxLength, ' ')
4237
}.plus("\n")
4338
val wordRowSeparator = "".padEnd(size * maxLength + (size - 1) * columnPadding.length + 4, '-').plus("\n")
4439

src/commonMain/kotlin/eu/yeger/cyk/model/ProductionRule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public data class NonTerminatingRule(
1313
}
1414
}
1515

16-
public class TerminatingRule(
16+
public data class TerminatingRule(
1717
public override val left: NonTerminalSymbol,
1818
public val right: TerminalSymbol,
1919
) : ProductionRule() {

src/commonMain/kotlin/eu/yeger/cyk/model/Symbol.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package eu.yeger.cyk.model
22

3-
import eu.yeger.cyk.epsilon
4-
53
public sealed class Symbol {
64
public abstract val symbol: String
75
}
86

97
public data class TerminalSymbol(public override val symbol: String) : Symbol() {
10-
override fun toString(): String = symbol.ifBlank { epsilon }
8+
override fun toString(): String = symbol.ifBlank { "ε" }
119
}
1210

1311
public sealed class NonTerminalSymbol : Symbol()
Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
package eu.yeger.cyk.model
22

3-
public class Word
4-
private constructor(terminalSymbols: List<TerminalSymbol>) : List<TerminalSymbol> by terminalSymbols {
5-
public constructor(word: String) : this(
6-
word.split(" ")
7-
.filter { it.isNotBlank() }
8-
.map { TerminalSymbol(it) }
9-
.ifEmpty { listOf(TerminalSymbol("")) }
10-
)
11-
}
3+
public class Word(
4+
terminalSymbols: List<TerminalSymbol>
5+
) : List<TerminalSymbol> by terminalSymbols
Lines changed: 7 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,28 @@
11
package eu.yeger.cyk.parser
22

3-
import eu.yeger.cyk.*
4-
import eu.yeger.cyk.model.*
5-
6-
private const val terminalSymbolRegexString = "[a-z]+"
7-
private const val nonTerminalSymbolRegexString = "[A-Z]+[a-z]*"
8-
9-
public val terminalSymbolRegex: Regex = terminalSymbolRegexString.toRegex()
10-
public val nonTerminalSymbolRegex: Regex = nonTerminalSymbolRegexString.toRegex()
11-
public val productionRuleRegex: Regex = "$nonTerminalSymbolRegexString -> ($nonTerminalSymbolRegexString $nonTerminalSymbolRegexString|$terminalSymbolRegexString)".toRegex()
3+
import eu.yeger.cyk.Result
4+
import eu.yeger.cyk.map
5+
import eu.yeger.cyk.model.Grammar
6+
import eu.yeger.cyk.model.StartSymbol
127

138
public fun grammar(
149
startSymbol: String,
1510
includeEmptyProductionRule: Boolean = false,
1611
block: () -> String,
1712
): Result<Grammar> {
18-
return parseAsGrammar(
13+
return grammar(
1914
startSymbol = startSymbol,
2015
includeEmptyProductionRule = includeEmptyProductionRule,
2116
productionRules = block(),
2217
)
2318
}
2419

25-
public fun parseAsGrammar(
20+
public fun grammar(
2621
startSymbol: String,
2722
includeEmptyProductionRule: Boolean = false,
2823
productionRules: String,
2924
): Result<Grammar> {
30-
return parse(
25+
return parseProductionRules(
3126
startSymbol = startSymbol,
3227
includeEmptyProductionRule = includeEmptyProductionRule,
3328
productionRules = productionRules
@@ -38,76 +33,3 @@ public fun parseAsGrammar(
3833
)
3934
}
4035
}
41-
42-
public fun parse(
43-
startSymbol: String,
44-
includeEmptyProductionRule: Boolean = false,
45-
productionRules: String,
46-
): Result<ProductionRuleSet> {
47-
return validateStartSymbol(startSymbol)
48-
.andThen { validatedStartSymbol ->
49-
productionRules.trimIndent()
50-
.lines()
51-
.filter { it.isNotBlank() }
52-
.parseLines(validatedStartSymbol)
53-
}
54-
.map { productionRuleSet ->
55-
when (includeEmptyProductionRule) {
56-
true -> productionRuleSet.copy(terminatingRules = productionRuleSet.terminatingRules + TerminatingRule(StartSymbol(startSymbol), TerminalSymbol("")))
57-
else -> productionRuleSet
58-
}
59-
}
60-
}
61-
62-
private fun validateStartSymbol(startSymbol: String): Result<StartSymbol> {
63-
return when {
64-
startSymbol matches nonTerminalSymbolRegex -> succeed(StartSymbol(startSymbol))
65-
startSymbol.isBlank() -> fail("Start symbol cannot be blank.")
66-
else -> fail("Invalid start symbol: $startSymbol")
67-
}
68-
}
69-
70-
private fun List<String>.parseLines(startSymbol: StartSymbol): Result<ProductionRuleSet> {
71-
return fold(succeed(emptyList())) { productionRules: Result<List<ProductionRule>>, line: String ->
72-
productionRules
73-
.and(line.parseLine(startSymbol))
74-
.andThen { productionRule: ProductionRule -> succeed(productionRules.getOr(emptyList()) + productionRule) }
75-
}.map { productionRules -> productionRuleSet(productionRules) }
76-
}
77-
78-
private fun String.parseLine(startSymbol: StartSymbol): Result<ProductionRule> {
79-
return trim()
80-
.splitIntoComponents()
81-
.andThen { components -> components.asProductionRule(startSymbol) }
82-
}
83-
84-
private fun String.splitIntoComponents(): Result<List<String>> {
85-
return when {
86-
this matches productionRuleRegex -> succeed(split("->", " ").filter { it.isNotBlank() })
87-
else -> fail("Invalid production rule: $this")
88-
}
89-
}
90-
91-
private fun List<String>.asProductionRule(startSymbol: StartSymbol): Result<ProductionRule> {
92-
val inputSymbol = when (val inputString = get(0)) {
93-
startSymbol.symbol -> startSymbol
94-
else -> RegularNonTerminalSymbol(inputString)
95-
}
96-
return when (size) {
97-
3 -> asNonTerminatingProductionRule(inputSymbol)
98-
2 -> asTerminatingProductionRule(inputSymbol)
99-
else -> fail("Invalid component amount ($size)! Must be 2 for terminal or 3 for non terminal production rules.")
100-
}
101-
}
102-
103-
private fun List<String>.asNonTerminatingProductionRule(inputSymbol: NonTerminalSymbol): Result<NonTerminatingRule> {
104-
return succeed(NonTerminatingRule(inputSymbol, RegularNonTerminalSymbol(get(1)) to RegularNonTerminalSymbol(get(2))))
105-
}
106-
107-
private fun List<String>.asTerminatingProductionRule(inputSymbol: NonTerminalSymbol): Result<TerminatingRule> {
108-
val outputSymbol = when (val terminalSymbol = get(1)) {
109-
epsilon -> TerminalSymbol(terminalSymbol)
110-
else -> TerminalSymbol(terminalSymbol)
111-
}
112-
return succeed(TerminatingRule(inputSymbol, outputSymbol))
113-
}

0 commit comments

Comments
 (0)