diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/ParsingContext.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/ParsingContext.kt new file mode 100644 index 0000000000000000000000000000000000000000..9531f839ded60be5c494b379c39abe357703c1a4 --- /dev/null +++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/ParsingContext.kt @@ -0,0 +1,66 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.protocol.util + +import de.justjanne.libquassel.annotations.Generated +import de.justjanne.libquassel.protocol.util.expansion.Expansion +import java.util.function.Supplier + +internal abstract class ParsingContext<T>( + internal val text: String +) { + protected abstract val matchers: List<Supplier<T?>> + + internal var position = 0 + + fun parse(): List<T> { + val result = mutableListOf<T>() + while (position < text.length) { + for (matcher in matchers) { + val match = matcher.get() + if (match != null) { + result.add(match) + continue + } + } + } + return result + } + + @Generated + protected inline fun match( + vararg patterns: String, + crossinline function: () -> Expansion + ) = Supplier { + for (pattern in patterns) { + if (text.startsWith(pattern, startIndex = position)) { + position += pattern.length + return@Supplier function() + } + } + return@Supplier null + } + + @Generated + protected inline fun match( + vararg patterns: Regex, + crossinline function: (List<String>) -> Expansion + ) = Supplier { + for (pattern in patterns) { + val match = pattern.find(text, startIndex = position) + if (match != null && match.range.first == position) { + position = match.range.last + 1 + return@Supplier function(match.groupValues) + } + } + return@Supplier null + } +} diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/Expansion.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/Expansion.kt new file mode 100644 index 0000000000000000000000000000000000000000..93da3d9035c2afa957823ee5f8a09dbb65974474 --- /dev/null +++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/Expansion.kt @@ -0,0 +1,36 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.protocol.util.expansion + +sealed class Expansion { + data class Text(val value: String) : Expansion() + data class ParameterRange(val from: Int, val to: Int?) : Expansion() + data class Parameter(val index: Int, val field: ParameterField?) : Expansion() + data class Constant(val field: ConstantField) : Expansion() + + enum class ParameterField { + HOSTNAME, + VERIFIED_IDENT, + IDENT, + ACCOUNT + } + + enum class ConstantField { + CHANNEL, + NICK, + NETWORK + } + + companion object { + fun parse(text: String): List<Expansion> = + ExpansionParsingContext(text).parse() + } +} diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionParsingContext.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionParsingContext.kt new file mode 100644 index 0000000000000000000000000000000000000000..566e2df1362548c2bd5f08d4432456b13d73bc87 --- /dev/null +++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionParsingContext.kt @@ -0,0 +1,67 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.protocol.util.expansion + +import de.justjanne.libquassel.protocol.util.ParsingContext +import java.util.function.Supplier + +internal class ExpansionParsingContext( + text: String +) : ParsingContext<Expansion>(text) { + override val matchers: List<Supplier<Expansion?>> = listOf( + match("\$channelname", "\$channel") { + Expansion.Constant(Expansion.ConstantField.CHANNEL) + }, + match("\$currentnick", "\$nick") { + Expansion.Constant(Expansion.ConstantField.NICK) + }, + match("\$network") { + Expansion.Constant(Expansion.ConstantField.NETWORK) + }, + match("\$0") { + Expansion.Parameter(0, null) + }, + match("""\$(\d+)\.\.(\d+)""".toRegex()) { (_, from, to) -> + Expansion.ParameterRange(from.toInt(), to.toInt()) + }, + match("""\$(\d+)\.\.""".toRegex()) { (_, from) -> + Expansion.ParameterRange(from.toInt(), null) + }, + match("""\$(\d+):hostname""".toRegex()) { (_, value) -> + Expansion.Parameter(value.toInt(), Expansion.ParameterField.HOSTNAME) + }, + match("""\$(\d+):identd""".toRegex()) { (_, value) -> + Expansion.Parameter(value.toInt(), Expansion.ParameterField.VERIFIED_IDENT) + }, + match("""\$(\d+):ident""".toRegex()) { (_, value) -> + Expansion.Parameter(value.toInt(), Expansion.ParameterField.IDENT) + }, + match("""\$(\d+):account""".toRegex()) { (_, value) -> + Expansion.Parameter(value.toInt(), Expansion.ParameterField.ACCOUNT) + }, + match("""\$(\d+)""".toRegex()) { (_, value) -> + Expansion.Parameter(value.toInt(), null) + }, + Supplier { + val end = text.indexOf('$', startIndex = position).let { + if (it >= 0) it + else text.length + } + if (position < end) { + val start = position + position = end + val value = text.substring(start, end) + return@Supplier Expansion.Text(value) + } + return@Supplier null + } + ) +} diff --git a/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionTest.kt b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..02290613c6e94d40d9ec754934e4b81e4363504d --- /dev/null +++ b/libquassel-protocol/src/test/kotlin/de/justjanne/libquassel/protocol/util/expansion/ExpansionTest.kt @@ -0,0 +1,106 @@ +/* + * libquassel + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +package de.justjanne.libquassel.protocol.util.expansion + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class ExpansionTest { + @Test + fun testDefaults() { + assertEquals( + listOf( + Expansion.Text("/join "), + Expansion.Parameter(0, null) + ), + Expansion.parse("/join $0") + ) + + assertEquals( + listOf( + Expansion.Text("/whois "), + Expansion.Parameter(0, null), + Expansion.Text(" "), + Expansion.Parameter(0, null) + ), + Expansion.parse("/whois $0 $0") + ) + } + + @Test + fun testParameters() { + assertEquals( + listOf( + Expansion.Text("/say Welcome to the support channel for the IRC client Quassel, "), + Expansion.Parameter(1, null) + ), + Expansion.parse("/say Welcome to the support channel for the IRC client Quassel, \$1") + ) + assertEquals( + listOf( + Expansion.Parameter(1, null), + Expansion.Text(" "), + Expansion.Parameter(1, Expansion.ParameterField.ACCOUNT), + Expansion.Text(" "), + Expansion.Parameter(1, Expansion.ParameterField.HOSTNAME), + Expansion.Text(" "), + Expansion.Parameter(1, Expansion.ParameterField.VERIFIED_IDENT), + Expansion.Text(" "), + Expansion.Parameter(1, Expansion.ParameterField.IDENT), + ), + Expansion.parse("\$1 \$1:account \$1:hostname \$1:identd \$1:ident") + ) + } + + @Test + fun testConstants() { + assertEquals( + listOf( + Expansion.Text("/say I am "), + Expansion.Constant(Expansion.ConstantField.NICK), + Expansion.Text(", welcoming you to our channel "), + Expansion.Constant(Expansion.ConstantField.CHANNEL), + Expansion.Text(" on "), + Expansion.Constant(Expansion.ConstantField.NETWORK), + Expansion.Text(".") + ), + Expansion.parse("/say I am \$nick, welcoming you to our channel \$channelname on \$network.") + ) + assertEquals( + listOf( + Expansion.Text("/say That’s right, I’m /the/ "), + Expansion.Constant(Expansion.ConstantField.NICK), + Expansion.Text(" from "), + Expansion.Constant(Expansion.ConstantField.CHANNEL), + Expansion.Text(".") + ), + Expansion.parse("/say That’s right, I’m /the/ \$nick from \$channel.") + ) + } + + @Test + fun testRanges() { + assertEquals( + listOf( + Expansion.Text("1 \""), + Expansion.Parameter(1, null), + Expansion.Text("\" 2 \""), + Expansion.Parameter(2, null), + Expansion.Text("\" 3..4 \""), + Expansion.ParameterRange(3, 4), + Expansion.Text("\" 3.. \""), + Expansion.ParameterRange(3, null), + Expansion.Text("\""), + ), + Expansion.parse("1 \"\$1\" 2 \"\$2\" 3..4 \"\$3..4\" 3.. \"\$3..\"") + ) + } +}