Unverified Commit 9939833d authored by Oleksandr Karpovich's avatar Oleksandr Karpovich Committed by GitHub

add Pattern and Matcher actual implementation for js and native (#335)

* add Pattern and Matcher actual implementation for js and native

Update FontFeature.kt and FontVariation.kt parse functions to use regex numeric groups instead of named groups because k/n doesn't support named groups

* PR review improvements
Co-authored-by: 's avatarOleksandr Karpovich <oleksandr.karpovich@jetbrains.com>
parent 6d773d5e
......@@ -9,12 +9,13 @@ expect fun defaultLanguageTag(): String
expect abstract class OutputStream
expect class Pattern {
fun split(input: CharSequence): Array<String?>?
fun split(input: CharSequence): Array<String>
fun matcher(input: CharSequence): Matcher
}
expect class Matcher {
fun group(name: String): String?
// Named groups are not supported in k/n. That's why we can use only numeric groups
fun group(ix: Int): String?
fun matches(): Boolean
}
......
......@@ -78,20 +78,28 @@ class FontFeature(val _tag: Int, val value: Int, val start: UInt, val end: UInt)
val EMPTY = arrayOfNulls<FontFeature>(0)
val _splitPattern = compilePattern("\\s+")
val _featurePattern =
compilePattern("(?<sign>[-+])?(?<tag>[a-z0-9]{4})(?:\\[(?<start>\\d+)?:(?<end>\\d+)?\\])?(?:=(?<value>\\d+))?")
compilePattern("([-+])?([a-z0-9]{4})(?:\\[(\\d+)?:(\\d+)?\\])?(?:=(\\d+))?")
// We can't use named groups (not supported in k/n), so we use numeric groups.
// These constants are group indexes of _featurePattern:
private const val signIx = 1
private const val tagIx = 2
private const val startIx = 3
private const val endIx = 4
private const val valueIx = 5
fun parseOne(s: String): FontFeature {
val m = _featurePattern.matcher(s)
require(m.matches()) { "Can’t parse FontFeature: $s" }
val value = if (m.group("value") != null) m.group("value")!!
.toInt() else if (m.group("sign") == null) 1 else if ("-" == m.group("sign")) 0 else 1
val start = if (m.group("start") == null) 0u else m.group("start")!!.toUInt()
val end = if (m.group("end") == null) UInt.MAX_VALUE else m.group("end")!!.toUInt()
return FontFeature(m.group("tag")!!, value, start, end)
val value = if (m.group(valueIx) != null) m.group(valueIx)!!
.toInt() else if (m.group(signIx) == null) 1 else if ("-" == m.group(signIx)) 0 else 1
val start = if (m.group(startIx) == null) 0u else m.group(startIx)!!.toUInt()
val end = if (m.group(endIx) == null) GLOBAL_END else m.group(endIx)!!.toUInt()
return FontFeature(m.group(tagIx)!!, value, start, end)
}
fun parse(str: String): Array<FontFeature?> {
return _splitPattern.split(str)?.map { s -> parseOne(s!!) }?.toTypedArray() ?: emptyArray()
return _splitPattern.split(str).map { s -> parseOne(s) }.toTypedArray()
}
internal fun InteropScope.toInterop(fontFeatures: Array<FontFeature>?): InteropPointer {
......
......@@ -31,18 +31,23 @@ class FontVariation(val _tag: Int, val value: Float) {
internal val _splitPattern = compilePattern("\\s+")
internal val _variationPattern = compilePattern("(?<tag>[a-z0-9]{4})=(?<value>\\d+)")
internal val _variationPattern = compilePattern("([a-z0-9]{4})=(\\d+)")
// We can't use named groups (not supported in k/n), so we use numeric groups.
// These constants are group indexes of _variationPattern:
private const val tagIx = 1
private const val valueIx = 2
fun parseOne(s: String): FontVariation {
val m = _variationPattern.matcher(s)
require(m.matches()) { "Can’t parse FontVariation: $s" }
val value = m.group("value")!!.toFloat()
return FontVariation(m.group("tag")!!, value)
val value = m.group(valueIx)!!.toFloat()
val tag = m.group(tagIx)!!
return FontVariation(tag, value)
}
fun parse(str: String): Array<FontVariation> {
return _splitPattern.split(str)!!.map { s -> parseOne(s!!) }
.toTypedArray()
return _splitPattern.split(str).map { s -> parseOne(s) }.toTypedArray()
}
}
}
package org.jetbrains.skia
import org.jetbrains.skiko.tests.runTest
import kotlin.test.*
class PatternMatcherTests {
@Test
fun canSplit() = runTest {
val _splitPattern = compilePattern("\\s+")
val result = _splitPattern.split("a b cd 1 23 -7.0 4 5")!!
assertContentEquals(
expected = arrayOf("a", "b", "cd", "1", "23", "-7.0", "4", "5"),
actual = result
)
}
@Test
fun matchesTrue() = runTest {
val pattern = compilePattern("[0-9]{3}-[a-z]{3}")
val result = pattern.matcher("123-abc")
assertTrue(result.matches())
}
@Test
fun matchesFalse() = runTest {
val pattern = compilePattern("[0-9]{3}-[a-z]{3}")
val result = pattern.matcher("1c23-abc")
assertFalse(result.matches())
}
@Test
fun findGroup() = runTest {
val pattern = compilePattern("([-+])?([a-z0-9]{4})(?:\\[(\\d+)?:(\\d+)?\\])?(?:=(\\d+))?")
val matcher1 = pattern.matcher("+abcd[1234:567891234]=10101010101")
assertTrue(matcher1.matches())
assertEquals("+", matcher1.group(1))
assertEquals("abcd", matcher1.group(2))
assertEquals("1234", matcher1.group(3))
assertEquals("567891234", matcher1.group(4))
assertEquals("10101010101", matcher1.group(5))
val matcher2 = pattern.matcher("abcd[:567891234]=10101010101")
assertTrue(matcher2.matches())
assertEquals(null, matcher2.group(1))
assertEquals("abcd", matcher2.group(2))
assertEquals(null, matcher2.group(3))
assertEquals("567891234", matcher2.group(4))
assertEquals("10101010101", matcher2.group(5))
}
@Test
fun fontFeatureCanBeParsed() = runTest {
val f1 = FontFeature.parseOne("abcd[5:15]=42")
assertEquals("abcd", f1.tag)
assertEquals(5u, f1.start)
assertEquals(15u, f1.end)
assertEquals(42, f1.value)
val f2 = FontFeature.parseOne("abcd[:15]=42")
assertEquals("abcd", f2.tag)
assertEquals(FontFeature.GLOBAL_START, f2.start)
assertEquals(15u, f2.end)
assertEquals(42, f2.value)
val f3 = FontFeature.parseOne("abcd=42")
assertEquals("abcd", f3.tag)
assertEquals(FontFeature.GLOBAL_START, f3.start)
assertEquals(FontFeature.GLOBAL_END, f3.end)
assertEquals(42, f3.value)
val f4 = FontFeature.parseOne("abcd[:]=42")
assertEquals("abcd", f4.tag)
assertEquals(FontFeature.GLOBAL_START, f4.start)
assertEquals(FontFeature.GLOBAL_END, f4.end)
assertEquals(42, f4.value)
}
@Test
fun fontVariationCanBeParsed() = runTest {
val f1 = FontVariation.parseOne("abcd=111")
assertEquals("abcd", f1.tag)
assertEquals(111f, f1.value)
val f2 = FontVariation.parseOne("a1c2=0")
assertEquals("a1c2", f2.tag)
assertEquals(0f, f2.value)
assertFailsWith<IllegalArgumentException> {
FontVariation.parseOne("acdd=")
}
assertFailsWith<IllegalArgumentException> {
FontVariation.parseOne("=123")
}
}
}
......@@ -8,18 +8,27 @@ actual fun <R> commonSynchronized(lock: Any, block: () -> R) {
actual fun String.intCodePoints(): IntArray = TODO()
actual class Pattern {
actual fun split(input: CharSequence): Array<String?>? = TODO()
actual fun matcher(input: CharSequence): Matcher = TODO()
actual class Pattern constructor(regex: String) {
private val _regex = Regex(regex)
actual fun split(input: CharSequence): Array<String> = _regex.split(input).toTypedArray()
actual fun matcher(input: CharSequence): Matcher = Matcher(_regex, input)
}
actual class Matcher {
actual fun group(name: String): String? = TODO()
actual fun matches(): Boolean = TODO()
actual class Matcher constructor(private val regex: Regex, private val input: CharSequence) {
private val matches: Boolean by lazy {
regex.matches(input)
}
private val groups: MatchGroupCollection? by lazy { regex.matchEntire(input)?.groups }
actual fun group(ix: Int): String? = groups?.get(ix)?.value
actual fun matches(): Boolean = matches
}
actual fun defaultLanguageTag(): String = TODO()
actual fun compilePattern(regex: String): Pattern = TODO()
actual fun compilePattern(regex: String): Pattern = Pattern(regex)
actual typealias ExternalSymbolName = kotlin.js.JsName
......@@ -8,18 +8,27 @@ actual fun <R> commonSynchronized(lock: Any, block: () -> R) {
actual fun String.intCodePoints(): IntArray = TODO()
actual class Pattern {
actual fun split(input: CharSequence): Array<String?>? = TODO()
actual fun matcher(input: CharSequence): Matcher = TODO()
actual class Pattern constructor(regex: String) {
private val _regex = Regex(regex)
actual fun split(input: CharSequence): Array<String> = _regex.split(input).toTypedArray()
actual fun matcher(input: CharSequence): Matcher = Matcher(_regex, input)
}
actual class Matcher {
actual fun group(name: String): String? = TODO()
actual fun matches(): Boolean = TODO()
actual class Matcher constructor(private val regex: Regex, private val input: CharSequence) {
private val matches: Boolean by lazy {
regex.matches(input)
}
private val groups: MatchGroupCollection? by lazy { regex.matchEntire(input)?.groups }
actual fun group(ix: Int): String? = groups?.get(ix)?.value
actual fun matches(): Boolean = matches
}
actual fun defaultLanguageTag(): String = TODO()
actual fun compilePattern(regex: String): Pattern = TODO()
actual fun compilePattern(regex: String): Pattern = Pattern(regex)
actual typealias ExternalSymbolName = kotlin.native.SymbolName
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment