From 5ac1bc26e163f96eab11c815c5aee936cbc13355 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 3 Nov 2024 13:30:20 +0600 Subject: [PATCH] rewrite check onto fsm --- src/commonMain/kotlin/KrontabConfig.kt | 2 +- src/commonMain/kotlin/internal/Parser.kt | 72 +++++++++++++++++++++++- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/commonMain/kotlin/KrontabConfig.kt b/src/commonMain/kotlin/KrontabConfig.kt index 2a3c145..2afcb2f 100644 --- a/src/commonMain/kotlin/KrontabConfig.kt +++ b/src/commonMain/kotlin/KrontabConfig.kt @@ -97,7 +97,7 @@ value class KrontabConfig( var millisecondsParsed: Array? = null val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = template .split(" ") - .filter { it.matches(KrontabConfigPartRegex) } // filter garbage from string + .filter { checkIncomingPart(it) } // filter garbage from string .let { if (it.size < 5) { // reconstruction in case of insufficient arguments; 5 is amount of required arguments out of latest also code it + (it.size until 5).map { "*" } diff --git a/src/commonMain/kotlin/internal/Parser.kt b/src/commonMain/kotlin/internal/Parser.kt index 54ceb5c..caaad9b 100644 --- a/src/commonMain/kotlin/internal/Parser.kt +++ b/src/commonMain/kotlin/internal/Parser.kt @@ -37,9 +37,75 @@ private fun createSimpleScheduler(from: String, dataRange: IntRange, dataCon return results.map(dataConverter) } -internal val KrontabPartNumberRegexString = "((\\d+\\-\\d+)|([\\d\\*]+(/[\\d\\*]+)?))" -internal val KrontabPartsNumberRegexString = "$KrontabPartNumberRegexString(,$KrontabPartNumberRegexString)*" -internal val KrontabConfigPartRegex = Regex("(($KrontabPartsNumberRegexString+)|(\\d+o)|($KrontabPartsNumberRegexString+w)|($KrontabPartsNumberRegexString+ms))") +/** + * FSM for parsing of incoming data. If at the end of parsing it have non-null state and string is not empty, data passed check + * + * 1. + * * \\d -> 1 + * * \\* -> 2 + * * \\- -> 5 + * * , -> 1 + * * m -> 6 + * * o -> 7 + * * w -> 7 + * 2. + * * / -> 3 + * 3. + * * \\d -> 3 + * * \\* -> 4 + * 4. + * * , -> 1 + * 5. + * * \\d -> 5 + * * , -> 1 + * 6. + * * s -> 7 + * 7. Empty, end of parse + */ +private val checkIncomingPartTransitionsMap = listOf( + listOf( + Regex("\\d") to 0, + Regex("\\*") to 1, + Regex("-") to 4, + Regex(",") to 0, + Regex("m") to 5, + Regex("o") to 6, + Regex("w") to 6, + ), + listOf( + Regex("/") to 2, + ), + listOf( + Regex("\\d") to 2, + Regex("\\*") to 3, + ), + listOf( + Regex(",") to 0, + ), + listOf( + Regex("\\d") to 4, + Regex(",") to 0, + ), + listOf( + Regex("s") to 6, // end of ms + ), + listOf(), // empty state, end of parsing +) +internal fun checkIncomingPart(part: String): Boolean { + var i = 0 + var state = checkIncomingPartTransitionsMap[0] + while (i < part.length) { + val char = part[i] + val nextState = state.firstNotNullOfOrNull { + it.second.takeIf { _ -> it.first.matches("$char") } + } + if (nextState == null) return false + state = checkIncomingPartTransitionsMap[nextState] + i++ + } + + return part.isNotEmpty() +} internal fun parseWeekDay(from: String?) = from ?.let { if (it.endsWith("w")) createSimpleScheduler(it.removeSuffix("w"), dayOfWeekRange, intToByteConverter) ?.toTypedArray() else null } internal fun parseOffset(from: String?) = from ?.let { if (it.endsWith("o")) it.removeSuffix("o").toIntOrNull() else null }