Skip to content

Commit 36c5953

Browse files
committed
Updated parser, CYKState model and gitignore
1 parent 71e6e76 commit 36c5953

File tree

7 files changed

+104
-53
lines changed

7 files changed

+104
-53
lines changed

.gitignore

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@
3333
# When using Gradle or Maven with auto-import, you should exclude module files,
3434
# since they will be recreated, and may cause churn. Uncomment if using
3535
# auto-import.
36-
# .idea/artifacts
37-
# .idea/compiler.xml
38-
# .idea/jarRepositories.xml
39-
# .idea/modules.xml
40-
# .idea/*.iml
41-
# .idea/modules
42-
# *.iml
43-
# *.ipr
36+
.idea/artifacts
37+
.idea/compiler.xml
38+
.idea/jarRepositories.xml
39+
.idea/modules.xml
40+
.idea/*.iml
41+
.idea/modules
42+
*.iml
43+
*.ipr
4444

4545
# CMake
4646
cmake-build-*/

gradle.properties

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ kotlin.mpp.stability.nowarn=true
55
dokkaVersion=1.4.10
66
kotlinVersion=1.4.10
77
ktlintVersion=9.4.1
8-
kotlinResultVersion=1.1.9
98

10-
libraryVersion=0.1.0
11-
libraryDescription=CYK
9+
libraryVersion=0.2.0
10+
libraryDescription=Kotlin Multiplatform implementation of the CYK algorithm.
1211
publishedGroupId=eu.yeger
1312
artifact=cyk-algorithm
1413
bintrayRepo=maven

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ public fun <T : Any, U : Any> Result<T>.map(transformation: (T) -> U): Result<U>
2020
}
2121
}
2222

23+
public fun <T : Any> Result<T>.mapError(transformation: (String) -> String): Result<T> {
24+
return when (this) {
25+
is Result.Success -> this
26+
is Result.Failure -> fail(transformation(error))
27+
}
28+
}
29+
2330
public fun <T : Any, U : Any> Result<T>.andThen(transformation: (T) -> Result<U>): Result<U> {
2431
return when (this) {
2532
is Result.Success -> transformation(data)
@@ -34,6 +41,13 @@ public fun <T : Any, U : Any> Result<T>.and(other: Result<U>): Result<U> {
3441
}
3542
}
3643

44+
public fun <T : Any> Result<T>.getOrNull(): T? {
45+
return when (this) {
46+
is Result.Success -> data
47+
is Result.Failure -> null
48+
}
49+
}
50+
3751
public fun <T : Any> Result<T>.getOr(fallback: T): T {
3852
return when (this) {
3953
is Result.Success -> data
@@ -47,3 +61,24 @@ public fun <T : Any> Result<T>.getOrElse(block: (String) -> T): T {
4761
is Result.Failure -> block(error)
4862
}
4963
}
64+
65+
public fun <T : Any> Result<T>.getErrorOrNull(): String? {
66+
return when (this) {
67+
is Result.Success -> null
68+
is Result.Failure -> error
69+
}
70+
}
71+
72+
public fun <T : Any> Result<T>.getErrorOr(fallback: String): String {
73+
return when (this) {
74+
is Result.Success -> fallback
75+
is Result.Failure -> error
76+
}
77+
}
78+
79+
public fun <T : Any> Result<T>.getErrorOrElse(block: (T) -> String): String {
80+
return when (this) {
81+
is Result.Success -> block(data)
82+
is Result.Failure -> error
83+
}
84+
}

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,19 @@ public fun runningCYK(
1313
word: Word,
1414
grammar: Grammar,
1515
): List<CYKState> {
16-
val cykStartState = CYKStart(CYKModel(word, grammar))
16+
val cykStartState = CYKState.Start(CYKModel(word, grammar))
1717
return (0..word.size).fold(listOf(cykStartState)) { previousStates: List<CYKState>, l: Int ->
1818
when (l) {
1919
0 -> previousStates.runningPropagateTerminalProductionRules()
2020
else -> previousStates.runningPropagateNonTerminalProductionRules(l + 1)
2121
}
22+
}.let { previousStates: List<CYKState> ->
23+
previousStates + previousStates.last().terminated()
2224
}
2325
}
2426

2527
private fun List<CYKState>.runningPropagateTerminalProductionRules(): List<CYKState> {
26-
return last().cykModel.word.foldIndexed(this) { terminalSymbolIndex: Int, previousStates: List<CYKState>, terminalSymbol: TerminalSymbol ->
28+
return last().model.word.foldIndexed(this) { terminalSymbolIndex: Int, previousStates: List<CYKState>, terminalSymbol: TerminalSymbol ->
2729
previousStates.runningFindProductionRulesForTerminalSymbol(terminalSymbol, terminalSymbolIndex)
2830
}
2931
}
@@ -32,7 +34,7 @@ private fun List<CYKState>.runningFindProductionRulesForTerminalSymbol(
3234
terminalSymbol: TerminalSymbol,
3335
terminalSymbolIndex: Int,
3436
): List<CYKState> {
35-
return last().cykModel.grammar.productionRuleSet.terminatingRules.fold(this) { previousStates: List<CYKState>, terminatingRule: TerminatingRule ->
37+
return last().model.grammar.productionRuleSet.terminatingRules.fold(this) { previousStates: List<CYKState>, terminatingRule: TerminatingRule ->
3638
val lastState = previousStates.last()
3739
previousStates + when {
3840
terminatingRule produces terminalSymbol -> lastState.stepWithRuleAt(
@@ -54,7 +56,7 @@ private fun List<CYKState>.runningFindProductionRulesForTerminalSymbol(
5456
private fun List<CYKState>.runningPropagateNonTerminalProductionRules(
5557
l: Int,
5658
): List<CYKState> {
57-
return (1..(last().cykModel.word.size - l + 1)).fold(this) { rowSteps: List<CYKState>, s: Int ->
59+
return (1..(last().model.word.size - l + 1)).fold(this) { rowSteps: List<CYKState>, s: Int ->
5860
(1 until l).fold(rowSteps) { columnSteps: List<CYKState>, p: Int ->
5961
columnSteps.runningFindProductionRulesForNonTerminalSymbols(l = l, s = s, p = p)
6062
}
@@ -66,10 +68,10 @@ private fun List<CYKState>.runningFindProductionRulesForNonTerminalSymbols(
6668
s: Int,
6769
p: Int,
6870
): List<CYKState> {
69-
return last().cykModel.grammar.productionRuleSet.nonTerminatingRules.fold(this) { previousStates: List<CYKState>, nonTerminatingRule: NonTerminatingRule ->
71+
return last().model.grammar.productionRuleSet.nonTerminatingRules.fold(this) { previousStates: List<CYKState>, nonTerminatingRule: NonTerminatingRule ->
7072
val lastState = previousStates.last()
7173
previousStates + when {
72-
lastState.cykModel.allowsNonTerminalRuleAt(
74+
lastState.model.allowsNonTerminalRuleAt(
7375
nonTerminatingRule,
7476
l = l,
7577
s = s,

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

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,36 @@ package eu.yeger.cyk.model
33
public data class Coordinates(val row: Int, val column: Int)
44

55
public sealed class CYKState {
6-
public abstract val cykModel: CYKModel
7-
}
6+
public abstract val model: CYKModel
7+
8+
public data class Start(
9+
public override val model: CYKModel,
10+
) : CYKState()
811

9-
public data class CYKStart(
10-
public override val cykModel: CYKModel,
11-
) : CYKState()
12+
public data class Step(
13+
public override val model: CYKModel,
14+
public val productionRule: ProductionRule,
15+
public val productionRuleWasApplied: Boolean,
16+
public val sourceCoordinates: Coordinates,
17+
public val targetCoordinates: List<Coordinates>,
18+
) : CYKState()
1219

13-
public data class CYKStep(
14-
public override val cykModel: CYKModel,
15-
public val productionRule: ProductionRule,
16-
public val ruleWasApplied: Boolean,
17-
public val sourceCoordinates: Coordinates,
18-
public val targetCoordinates: List<Coordinates>
19-
) : CYKState()
20+
public data class Done(
21+
public override val model: CYKModel,
22+
public val wordIsMemberOfLanguage: Boolean,
23+
) : CYKState()
24+
}
2025

2126
internal fun CYKState.stepWithRuleAt(
2227
productionRule: ProductionRule,
2328
rowIndex: Int,
2429
columnIndex: Int,
2530
targetCoordinates: List<Coordinates>,
26-
): CYKStep {
27-
return CYKStep(
28-
cykModel = cykModel.withSymbolAt(productionRule.left, rowIndex = rowIndex, columnIndex = columnIndex),
31+
): CYKState.Step {
32+
return CYKState.Step(
33+
model = model.withSymbolAt(productionRule.left, rowIndex = rowIndex, columnIndex = columnIndex),
2934
productionRule = productionRule,
30-
ruleWasApplied = true,
35+
productionRuleWasApplied = true,
3136
sourceCoordinates = Coordinates(rowIndex, columnIndex),
3237
targetCoordinates = targetCoordinates,
3338
)
@@ -38,12 +43,19 @@ internal fun CYKState.stepWithoutRuleAt(
3843
rowIndex: Int,
3944
columnIndex: Int,
4045
targetCoordinates: List<Coordinates>,
41-
): CYKStep {
42-
return CYKStep(
43-
cykModel = cykModel,
46+
): CYKState.Step {
47+
return CYKState.Step(
48+
model = model,
4449
productionRule = productionRule,
45-
ruleWasApplied = false,
50+
productionRuleWasApplied = false,
4651
sourceCoordinates = Coordinates(rowIndex, columnIndex),
4752
targetCoordinates = targetCoordinates,
4853
)
4954
}
55+
56+
internal fun CYKState.terminated(): CYKState.Done {
57+
return CYKState.Done(
58+
model = model,
59+
wordIsMemberOfLanguage = model.result,
60+
)
61+
}

src/commonMain/kotlin/eu/yeger/cyk/parser/RuleParser.kt renamed to src/commonMain/kotlin/eu/yeger/cyk/parser/GrammarParser.kt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package eu.yeger.cyk.parser
33
import eu.yeger.cyk.*
44
import eu.yeger.cyk.model.*
55

6-
private val startSymbolRegex = Regex("[A-Z]+[a-z]*")
6+
private const val terminalSymbolRegexString = "[a-z]+"
7+
private const val nonTerminalSymbolRegexString = "[A-Z]+[a-z]*"
78

8-
private val productionRuleRegex = Regex("[A-Z]+[a-z]* -> ([A-Z]+[a-z]* [A-Z]+[a-z]*|[a-z]+)")
9+
public val terminalSymbolRegex: Regex = terminalSymbolRegexString.toRegex()
10+
public val nonTerminalSymbolRegex: Regex = nonTerminalSymbolRegexString.toRegex()
11+
public val productionRuleRegex: Regex = "$nonTerminalSymbolRegexString -> ($nonTerminalSymbolRegexString $nonTerminalSymbolRegexString|$terminalSymbolRegexString)".toRegex()
912

1013
public fun grammar(
1114
startSymbol: String,
@@ -15,23 +18,22 @@ public fun grammar(
1518
return parseAsGrammar(
1619
startSymbol = startSymbol,
1720
includeEmptyProductionRule = includeEmptyProductionRule,
18-
rulesString = block(),
21+
productionRules = block(),
1922
)
2023
}
2124

2225
public fun parseAsGrammar(
2326
startSymbol: String,
2427
includeEmptyProductionRule: Boolean = false,
25-
rulesString: String,
28+
productionRules: String,
2629
): Result<Grammar> {
2730
return parse(
2831
startSymbol = startSymbol,
2932
includeEmptyProductionRule = includeEmptyProductionRule,
30-
rulesString = rulesString
33+
productionRules = productionRules
3134
).map { productionRuleSet ->
3235
Grammar(
3336
startSymbol = StartSymbol(startSymbol),
34-
3537
productionRuleSet = productionRuleSet,
3638
)
3739
}
@@ -40,11 +42,11 @@ public fun parseAsGrammar(
4042
public fun parse(
4143
startSymbol: String,
4244
includeEmptyProductionRule: Boolean = false,
43-
rulesString: String,
45+
productionRules: String,
4446
): Result<ProductionRuleSet> {
4547
return validateStartSymbol(startSymbol)
4648
.andThen { validatedStartSymbol ->
47-
rulesString.trimIndent()
49+
productionRules.trimIndent()
4850
.lines()
4951
.filter { it.isNotBlank() }
5052
.parseLines(validatedStartSymbol)
@@ -59,7 +61,8 @@ public fun parse(
5961

6062
private fun validateStartSymbol(startSymbol: String): Result<StartSymbol> {
6163
return when {
62-
startSymbol matches startSymbolRegex -> succeed(StartSymbol(startSymbol))
64+
startSymbol matches nonTerminalSymbolRegex -> succeed(StartSymbol(startSymbol))
65+
startSymbol.isBlank() -> fail("Start symbol cannot be blank.")
6366
else -> fail("Invalid start symbol: $startSymbol")
6467
}
6568
}
@@ -93,7 +96,7 @@ private fun List<String>.asProductionRule(startSymbol: StartSymbol): Result<Prod
9396
return when (size) {
9497
3 -> asNonTerminatingProductionRule(inputSymbol)
9598
2 -> asTerminatingProductionRule(inputSymbol)
96-
else -> fail("Invalid component amount ($size)! Must be 2 or 3")
99+
else -> fail("Invalid component amount ($size)! Must be 2 for terminal or 3 for non terminal production rules.")
97100
}
98101
}
99102

src/commonTest/kotlin/eu/yeger/cyk/RunningCYKTests.kt

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

3-
import eu.yeger.cyk.model.result
3+
import eu.yeger.cyk.model.CYKState
44
import eu.yeger.cyk.parser.grammar
55
import kotlin.test.Test
66
import kotlin.test.assertFalse
@@ -28,7 +28,7 @@ class RunningCYKTests {
2828
""".trimIndent()
2929
}
3030
}.getOrElse { error(it) }
31-
assertTrue(states.last().cykModel.result)
31+
assertTrue((states.last() as CYKState.Done).wordIsMemberOfLanguage)
3232
}
3333

3434
@Test
@@ -51,30 +51,30 @@ class RunningCYKTests {
5151
""".trimIndent()
5252
}
5353
}.getOrElse { error(it) }
54-
assertFalse(states.last().cykModel.result)
54+
assertFalse((states.last() as CYKState.Done).wordIsMemberOfLanguage)
5555
}
5656

5757
@Test
5858
fun verifyThatTheRunningCYKAlgorithmDetectsEmptyWords() {
5959
val states = runningCYK("") {
6060
grammar("S", includeEmptyProductionRule = true) { "" }
6161
}.getOrElse { error(it) }
62-
assertTrue(states.last().cykModel.result)
62+
assertTrue((states.last() as CYKState.Done).wordIsMemberOfLanguage)
6363
}
6464

6565
@Test
6666
fun verifyThatTheRunningCYKAlgorithmDoesntDetectsEmptyWordsIncorrectlyExplicitly() {
6767
val states = runningCYK("") {
6868
grammar("S", includeEmptyProductionRule = false) { "" }
6969
}.getOrElse { error(it) }
70-
assertFalse(states.last().cykModel.result)
70+
assertFalse((states.last() as CYKState.Done).wordIsMemberOfLanguage)
7171
}
7272

7373
@Test
7474
fun verifyThatTheRunningCYKAlgorithmDoesntDetectsEmptyWordsIncorrectlyImplicitly() {
7575
val states = runningCYK("") {
7676
grammar("S") { "" }
7777
}.getOrElse { error(it) }
78-
assertFalse(states.last().cykModel.result)
78+
assertFalse((states.last() as CYKState.Done).wordIsMemberOfLanguage)
7979
}
8080
}

0 commit comments

Comments
 (0)