Skip to content
Snippets Groups Projects
Verified Commit ab835591 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Implement code to cleanly parse command expansions

parent fe83786a
No related branches found
No related tags found
No related merge requests found
/*
* 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
}
}
/*
* 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()
}
}
/*
* 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
}
)
}
/*
* 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..\"")
)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment