A pattern matching library for Minecraft's Component text tree, shipped as a Fabric mod.
Unlike regex, TextMatcher operates at the node level — it flattens a Component into a sequence of styled FlatNodes and matches against that sequence. This means you can match on both text content and style properties (color, bold, font, etc.) at the same time.
Chat messages and GUI text in Wynncraft are composed of many Component nodes with different styles. Regex can only see the plain text — it can't distinguish styles and struggles with node boundaries. TextMatcher matches at the node level, which is a natural fit.
val pattern = textPattern {
one(text("[GUILD]")) { color(0x2d8627) }
one(text(" "))
one(any(), "player") { color(0x55ff55) }
one(text(": "))
oneOrMore(any(), "message")
}
val result = pattern.match(chatComponent)
if (result.success) {
val player = result["player"]
val message = result["message"]
}| Function | Description |
|---|---|
text("exact") |
Exact match |
regex("\\d+") |
Regex match |
contains("sub") |
Contains substring |
startsWith / endsWith |
Prefix / suffix |
any() |
Matches anything |
plainText() |
Plain text nodes only |
translatable("key") |
Translation key match |
Combinable with or, and, not:
one(text("A") or text("B"))
one(!plainText())one(...) // exactly 1
optional(...) // 0 or 1
oneOrMore(...) // 1+
zeroOrMore(...) // 0+
exactly(3, ...) // exactly n
between(2..5, ...) // rangeGreedy by default. Pass greedy = false for lazy matching.
one(any()) {
color(0xff5555)
bold()
italic(false)
font("minecraft", "default")
}Also supports not { ... } and anyOf { match { ... }; match { ... } }.
either {
branch {
one(text("option A"))
}
branch {
one(text("option B"))
}
}
// lookahead (does not consume nodes)
lookahead { one(text("peek")) }
negativeLookahead { one(text("not this")) }Add a capture parameter to any quantifier:
oneOrMore(any(), "content") { bold() }Access via result["content"], result.groupText("content"), or result.groupNodes("content").
pattern.match(component) // full match — entire input must match
pattern.find(component) // first occurrence
pattern.findAll(component) // all non-overlapping matches (Sequence)Extension functions are also available:
component.matches(pattern)
component.find(pattern)
component.findAll(pattern)Measured with JMH on JDK 21 (kotlinx-benchmark, 3 warmups, 5 iterations × 1s).
| Benchmark | Throughput | Description |
|---|---|---|
simpleExactMatch |
156.1 M op/s | 10-node exact match |
pathologicalWorstCase |
44.9 M op/s | Worst-case backtrack (hits limit) |
findAllRepeating |
36.3 M op/s | findAll over 100 key-value pairs |
greedyBacktrack |
33.6 M op/s | Greedy .* over 50 nodes + backtrack |
patternConstruction |
33.2 M op/s | Pattern DSL compilation |
endToEndFind |
6.5 M op/s | Full pipeline: flatten → compact → find |
endToEndMatch |
6.3 M op/s | Full pipeline: flatten → compact → match |
endToEndWithCapture |
5.6 M op/s | Full pipeline with capture group access |
eitherBranchMatch |
5.5 M op/s | 4-branch either with capture |
compactPreFlattened |
5.2 M op/s | Compact pre-flattened nodes |
regexContentMatch |
4.6 M op/s | Content matching with regex |
flattenShallow |
4.5 M op/s | Flatten 20-sibling component |
eitherBranchEndToEnd |
4.3 M op/s | End-to-end either branch + capture |
flattenAndCompact |
2.2 M op/s | Flatten + compact combined |
flattenDeep |
1.8 M op/s | Flatten 50-deep nested component |
flattenWide |
111.5 K op/s | Flatten tree (5^4 = 625 nodes) |
Run benchmarks with:
./gradlew benchmarkGradle (Kotlin DSL):
repositories {
maven {
name = "WynnSource"
url = uri("https://git.fyw.fyi/api/packages/wynnsource/maven")
}
}
dependencies {
modImplementation("fyw.fyi.textmatcher:textmatcher:<version>")
include("fyw.fyi.textmatcher:textmatcher:<version>")
}./gradlew build- Minecraft 1.21.11
- Fabric Loader >= 0.18.4
- Fabric Language Kotlin
- Java 21+
LGPL-3.0-or-later