From 1d53d7ebe9ef46b97245cadbe582fc21fa7ed49a Mon Sep 17 00:00:00 2001 From: HatoYuze Date: Sun, 26 Jan 2025 22:45:37 +0900 Subject: [PATCH 1/7] fix: Wrong serialization of strings containing commas (#68) --- .../commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt | 2 +- .../kotlin/net.mamoe.yamlkt/decoder/FlowListTest.kt | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt index 8301ed2..d852edb 100644 --- a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt +++ b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt @@ -688,7 +688,7 @@ internal fun String.getQuotationAvailability(): Int { c == ':' -> lastIsColon = true c == ' ' && lastIsColon -> canBeUnquoted = false c in """ - []{}"'$^*|>-?/~ + []{}"'$^*|>-?/~, """.trimIndent() -> { // less mistakes canBeUnquoted = false } diff --git a/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/decoder/FlowListTest.kt b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/decoder/FlowListTest.kt index 8922eb3..5c4d363 100644 --- a/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/decoder/FlowListTest.kt +++ b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/decoder/FlowListTest.kt @@ -133,4 +133,11 @@ internal class FlowListTest { ) ) } + + @Test + fun testContainingCommasFlow() { + // https://github.com/Him188/yamlkt/issues/68 + assertEquals("[ foo, 'bar, baz' ]", allFlow.encodeToString(listOf("foo", "bar, baz"))) + assertEquals(listOf("foo", "bar, baz"), allFlow.decodeFromString("[ foo, 'bar, baz' ]")) + } } \ No newline at end of file From 474d7526f6ace399004456ec4194f2d07c58e90b Mon Sep 17 00:00:00 2001 From: HatoYuze Date: Mon, 27 Jan 2025 06:08:38 +0900 Subject: [PATCH 2/7] feat: The configuring of char serialization feat: Add char serialization using Unicode code points fix: Correct serialization for chars with special characters --- .../YamlConfigurationInternal.kt | 45 +++++++++++++++++++ .../net.mamoe.yamlkt/internal/Converters.kt | 4 ++ .../net.mamoe.yamlkt/internal/Escape.kt | 25 +++++++++-- .../net.mamoe.yamlkt/internal/YamlDecoder.kt | 7 ++- .../net.mamoe.yamlkt/internal/YamlEncoder.kt | 4 +- .../encoder/BasicEncoderTest.kt | 15 +++++++ .../encoder/TestEncoderEscape.kt | 23 ++++++++++ 7 files changed, 116 insertions(+), 7 deletions(-) diff --git a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt index 665f8c2..2ab48c3 100644 --- a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt +++ b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt @@ -53,6 +53,12 @@ public class YamlBuilder internal constructor( @JvmField public var stringSerialization: StringSerialization = conf.stringSerialization + /** + * Configure how to serialize chars + * */ + @JvmField + public var charSerialization: CharSerialization = conf.charSerialization + /** * The value set for `null` serialization. * Default: serialize `null` as "null" @@ -80,6 +86,43 @@ public class YamlBuilder internal constructor( @JvmField public var listSerialization: ListSerialization = conf.listSerialization + /** + * The suggested format for [Char] serialization. + * + * [Char] isn't always serialized in this format, depending on the content. + * + * Some escape sequences of special characters will be processed as escaped characters _(such as '\n')_ + * */ + public enum class CharSerialization { + /** + * Quote all [Char]s with `'` + * + * If a value can't be serialized using single quotation, it will use [CHAR_DOUBLE_QUOTATION] + */ + CHAR_SINGLE_QUOTATION, + + /** + * Quote all [Char]s with `"` + */ + CHAR_DOUBLE_QUOTATION, + + /** + * Convert all [Char]s to their corresponding Unicode code points [Int] + * + * For example, the character 'A' will be converted to 65 + * + * It will work like [Byte] + */ + CHAR_UNICODE_CODE, + + /** + * Directly use the character content of [Char] + * + * When escaping is necessary, it defaults to using [CHAR_SINGLE_QUOTATION] + */ + NORMAL, + } + /** * The suggested format for [String] serialization. @@ -213,6 +256,7 @@ public class YamlBuilder internal constructor( nonStrictNumber, encodeDefaultValues, stringSerialization, + charSerialization, nullSerialization, mapSerialization, classSerialization, @@ -229,6 +273,7 @@ internal class YamlConfigurationInternal internal constructor( // encoding @JvmField val encodeDefaultValues: Boolean = true, @JvmField val stringSerialization: YamlBuilder.StringSerialization = YamlBuilder.StringSerialization.NONE, + @JvmField val charSerialization: YamlBuilder.CharSerialization = YamlBuilder.CharSerialization.NORMAL, @JvmField val nullSerialization: YamlBuilder.NullSerialization = YamlBuilder.NullSerialization.NULL, @JvmField val mapSerialization: MapSerialization = MapSerialization.BLOCK_MAP, @JvmField val classSerialization: MapSerialization = MapSerialization.BLOCK_MAP, diff --git a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Converters.kt b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Converters.kt index 33d19b2..411ac0f 100644 --- a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Converters.kt +++ b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Converters.kt @@ -100,6 +100,10 @@ internal object BinaryConverter { } } +internal fun Long.limitToChar(): Char { + if (this in Char.MIN_VALUE.code.toLong()..Char.MAX_VALUE.code.toLong()) return toInt().toChar() + error("value is too large for byte: $this") +} internal fun Long.limitToByte(): Byte { if (this in Byte.MIN_VALUE.toLong()..Byte.MAX_VALUE.toLong()) return this.toByte() diff --git a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt index d852edb..cd03343 100644 --- a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt +++ b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt @@ -4,6 +4,7 @@ package net.mamoe.yamlkt.internal import net.mamoe.yamlkt.YamlBuilder +import net.mamoe.yamlkt.YamlBuilder.CharSerialization.* import net.mamoe.yamlkt.YamlBuilder.StringSerialization.* import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -602,6 +603,26 @@ internal inline fun TokenStream.peekNext(block: (ch: Char) -> R?): R? { internal fun Char.isHexDigit(): Boolean = this in '0'..'9' || this in 'a'..'f' || this in 'A'..'F' +private const val ESCAPED_CHARACTERS: String = "[]{}\"'\\$^*|>-?/~,:#" + +internal fun Char.encodeEscapedString( + charSerialization: YamlBuilder.CharSerialization +): String { + if (charSerialization == CHAR_UNICODE_CODE) { + return this.code.toString() + } + var requiresDoubleQuoted = charSerialization == CHAR_DOUBLE_QUOTATION || this == '\'' + val requiresSingleQuoted = charSerialization == CHAR_SINGLE_QUOTATION || this in ESCAPED_CHARACTERS || this == ' ' + + val escapedChars = REPLACEMENT_CHARS.getOrNull(this.code)?.also { requiresDoubleQuoted = true } ?: this.toString() + + return when{ + requiresDoubleQuoted -> "\"$escapedChars\"" + requiresSingleQuoted -> "'$escapedChars'" + else -> escapedChars + } +} + internal fun String.toEscapedString( buf: StringBufHolder, stringSerialization: YamlBuilder.StringSerialization @@ -687,9 +708,7 @@ internal fun String.getQuotationAvailability(): Int { c == '#' -> canBeUnquoted = false c == ':' -> lastIsColon = true c == ' ' && lastIsColon -> canBeUnquoted = false - c in """ - []{}"'$^*|>-?/~, - """.trimIndent() -> { // less mistakes + c in ESCAPED_CHARACTERS -> { // less mistakes canBeUnquoted = false } } diff --git a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlDecoder.kt b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlDecoder.kt index 4bf3ab9..4bb499a 100644 --- a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlDecoder.kt +++ b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlDecoder.kt @@ -1014,8 +1014,11 @@ internal class YamlDecoder( private fun String?.decodeCharElementImpl(descriptor: SerialDescriptor?, index: Int): Char = this.debuggingLogDecoder(descriptor, index)?.let { - check(it.length == 1) { "too many chars for a char: $it" } - it.first() + when { + it.length == 1 -> it.first() + it.any { !it.isDigit() } -> error("too many chars for a char: $it") + else -> withIntegerValue("char", descriptor, index).limitToChar() + } } ?: checkNonStrictNullability(descriptor, index) ?: 0.toChar() diff --git a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlEncoder.kt b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlEncoder.kt index f10e81b..e52fde7 100644 --- a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlEncoder.kt +++ b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlEncoder.kt @@ -460,7 +460,7 @@ internal class YamlEncoder( override fun encodeBoolean(value: Boolean) = writer.write(if (value) "true" else "false") override fun encodeByte(value: Byte) = writer.write(value.toInt().toString()) - override fun encodeChar(value: Char) = writer.write(value) + override fun encodeChar(value: Char) = writer.write(value.encodeEscapedString(configuration.charSerialization)) override fun encodeDouble(value: Double) = writer.write(value.toString()) override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = writer.write(enumDescriptor.getElementName(index)) @@ -652,7 +652,7 @@ internal class YamlEncoder( encodeValue(if (value) "true" else "false") final override fun encodeByte(value: Byte) = encodeValue(value.toString()) - final override fun encodeChar(value: Char) = encodeValue(value) + final override fun encodeChar(value: Char) = encodeValue(value.encodeEscapedString(configuration.charSerialization)) final override fun encodeDouble(value: Double) = encodeValue(value.toString()) final override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = encodeValue(enumDescriptor.getElementName(index)) diff --git a/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/BasicEncoderTest.kt b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/BasicEncoderTest.kt index 4a63fc4..8927c48 100644 --- a/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/BasicEncoderTest.kt +++ b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/BasicEncoderTest.kt @@ -1,8 +1,11 @@ package net.mamoe.yamlkt.encoder import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString import net.mamoe.yamlkt.Yaml.Default import kotlin.test.Test +import kotlin.test.assertEquals internal class BasicEncoderTest { @@ -68,6 +71,18 @@ internal class BasicEncoderTest { ) } + @Test + fun testSpecialChar() { + val origin = listOf(':','#','\n','\r','a','c') + assertEquals( + origin, + allFlow.decodeFromString>( + allFlow.encodeToString>(origin) + ) + ) + } + + /* Data(v1=value1, number=123456, map={bob=2}, list=[value1, value2], anotherData=Data(v1=, number=111, map={bob=2}, list=[value1, value2], anotherData=null)) (v1=, number=0, map={bob=2}, list=[value1, value2], anotherData=Data(v1=, number=0, map={bob=2}, list=[value1, value2], anotherData=null))> diff --git a/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt index 44a3414..4fac563 100644 --- a/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt +++ b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt @@ -1,5 +1,6 @@ package net.mamoe.yamlkt.encoder +import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import net.mamoe.yamlkt.Yaml import net.mamoe.yamlkt.YamlBuilder @@ -19,6 +20,18 @@ private val best = Yaml { stringSerialization = YamlBuilder.StringSerialization.BEST_PERFORMANCE } +private val normal = Yaml { + charSerialization = YamlBuilder.CharSerialization.NORMAL +} +private val singleChar = Yaml { + charSerialization = YamlBuilder.CharSerialization.CHAR_SINGLE_QUOTATION +} +private val doubleChar = Yaml { + charSerialization = YamlBuilder.CharSerialization.CHAR_DOUBLE_QUOTATION +} +private val unicode = Yaml { + charSerialization = YamlBuilder.CharSerialization.CHAR_UNICODE_CODE +} internal class TestEncoderEscape { @Test @@ -55,6 +68,16 @@ internal class TestEncoderEscape { assertEquals("\' \'", best.encodeToString(" ")) } + @Test + fun testCharEscape() { + assertEquals("\" \"", doubleChar.encodeToString(' ')) + assertEquals("\' \'", singleChar.encodeToString(' ')) + assertEquals("\' \'", normal.encodeToString(' ')) + assertEquals("32", unicode.encodeToString(' ')) + + assertEquals(' ', unicode.decodeFromString("32")) + } + @Test fun testUnicodeEscape() { assertEquals( From c2317ba85adaf3b684037fca53357821c831d12f Mon Sep 17 00:00:00 2001 From: HatoYuze Date: Mon, 27 Jan 2025 07:43:20 +0900 Subject: [PATCH 3/7] fix: switch JS backend from BOTH to IR --- gradle.properties | 2 +- yamlkt/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 1c8094b..4970e2f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # style guide kotlin.code.style=official kotlin.incremental.multiplatform=true -kotlin.js.compiler=both +kotlin.js.compiler=ir kotlin.native.ignoreDisabledTargets=true systemProp.org.gradle.internal.publish.checksums.insecure=true org.gradle.vfs.watch=true diff --git a/yamlkt/build.gradle.kts b/yamlkt/build.gradle.kts index 13b6ad5..c246f79 100644 --- a/yamlkt/build.gradle.kts +++ b/yamlkt/build.gradle.kts @@ -19,7 +19,7 @@ kotlin { kotlinOptions.jvmTarget = "1.8" } } - js(BOTH) { + js(IR) { compilations.all { kotlinOptions { moduleKind = "umd" From c98c5876e4cb38a6804860df8444fff1534874ec Mon Sep 17 00:00:00 2001 From: HatoYuze Date: Mon, 27 Jan 2025 09:26:32 +0900 Subject: [PATCH 4/7] feat: Upgrade Kotlin to 2.0.0 Also: Updated kotlinx.serialization to 1.7.0 for compatibility --- .gitignore | 2 + benchmark/build.gradle.kts | 25 +-- build.gradle.kts | 2 +- buildSrc/src/main/kotlin/Versions.kt | 4 +- gradle.properties | 5 +- karma/config.js | 5 + yamlkt/build.gradle.kts | 174 ++++-------------- .../net.mamoe.yamlkt/internal/Escape.kt | 2 - 8 files changed, 62 insertions(+), 157 deletions(-) create mode 100644 karma/config.js diff --git a/.gitignore b/.gitignore index 899b29b..00adf7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ .idea/ .gradle/ +.kotlin/ build/ .idea_modules/ hs_err_pid* *.hprof local.properties + diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts index f026c65..5feae0f 100644 --- a/benchmark/build.gradle.kts +++ b/benchmark/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -5,36 +6,36 @@ plugins { kotlin("jvm") kotlin("kapt") kotlin("plugin.serialization") - id("me.champeau.gradle.jmh") + id("me.champeau.jmh") } -apply(plugin = "me.champeau.gradle.jmh") +apply(plugin = "me.champeau.jmh") dependencies { api(kotlin("stdlib-jdk8")) - api("org.openjdk.jmh:jmh-core:1.23") - api("org.openjdk.jmh:jmh-generator-annprocess:1.21") + api("org.openjdk.jmh:jmh-core:1.37") + api("org.openjdk.jmh:jmh-generator-annprocess:1.37") api(project(":yamlkt")) api(kotlinx("serialization-core", Versions.serialization)) api(kotlinx("serialization-json", Versions.serialization)) - kapt("org.openjdk.jmh:jmh-generator-annprocess:1.21") + kapt("org.openjdk.jmh:jmh-generator-annprocess:1.37") api("com.charleskorn.kaml:kaml:0.17.0") api("org.yaml:snakeyaml:1.26") - api("com.google.code.gson:gson:2.8.6") - api("com.alibaba:fastjson:1.2.75") + api("com.google.code.gson:gson:2.11.0") + api("com.alibaba:fastjson:1.2.83") // Next major: 2.0.51 } group = "" jmh { - include = listOf("DeserializingTest") + includes.set(listOf("DeserializingTest")) } val compileKotlin: KotlinCompile by tasks -compileKotlin.kotlinOptions { - jvmTarget = "1.8" +compileKotlin.compilerOptions { + jvmTarget.set(JvmTarget.JVM_1_8) } val compileTestKotlin: KotlinCompile by tasks -compileTestKotlin.kotlinOptions { - jvmTarget = "1.8" +compileTestKotlin.compilerOptions { + jvmTarget.set(JvmTarget.JVM_1_8) } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 69b27fd..f92b63e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { kotlin("multiplatform") version Versions.kotlin apply false kotlin("plugin.serialization") version Versions.kotlin apply false - id("me.champeau.gradle.jmh") version "0.5.3" apply false + id("me.champeau.jmh") version "0.7.2" apply false } allprojects { diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index c45828a..2ca5935 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,8 +1,8 @@ object Versions { const val version = "0.13.0" - const val kotlin = "1.8.0" - const val serialization = "1.5.0" + const val kotlin = "2.0.0" + const val serialization = "1.7.0" const val mavenCentralPublish = "1.0.0-dev-3" } diff --git a/gradle.properties b/gradle.properties index 4970e2f..3f8523f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,9 @@ # style guide kotlin.code.style=official kotlin.incremental.multiplatform=true -kotlin.js.compiler=ir kotlin.native.ignoreDisabledTargets=true systemProp.org.gradle.internal.publish.checksums.insecure=true org.gradle.vfs.watch=true -kotlin.mpp.enableCompatibilityMetadataVariant=true -kotlin.mpp.enableCInteropCommonization=true +org.gradle.jvmargs=-Xmx2g "-XX:MaxMetaspaceSize=2g" +kotlin.mpp.enableCInteropCommonization=true \ No newline at end of file diff --git a/karma/config.js b/karma/config.js new file mode 100644 index 0000000..a344d36 --- /dev/null +++ b/karma/config.js @@ -0,0 +1,5 @@ +config.client = config.client || {} +config.client.mocha = config.client.mocha || {} +config.client.mocha.timeout = '30s' +config.browserNoActivityTimeout = 30000 +config.browserDisconnectTimeout = 30000 \ No newline at end of file diff --git a/yamlkt/build.gradle.kts b/yamlkt/build.gradle.kts index c246f79..dcf5b14 100644 --- a/yamlkt/build.gradle.kts +++ b/yamlkt/build.gradle.kts @@ -1,8 +1,3 @@ -@file:Suppress("UNUSED_VARIABLE") - -import org.apache.tools.ant.taskdefs.condition.Os -import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet - plugins { id("me.him188.maven-central-publish") kotlin("multiplatform") @@ -13,129 +8,47 @@ plugins { kotlin { explicitApi() - targets { - jvm { - compilations.all { - kotlinOptions.jvmTarget = "1.8" - } - } - js(IR) { - compilations.all { - kotlinOptions { - moduleKind = "umd" - sourceMap = true - metaInfo = true + jvmToolchain(8) + jvm() + js { + browser { + testTask { + useKarma { + useChromeHeadless() + useConfigDirectory(rootDir.resolve("karma")) } } - browser() - nodejs() } - - - val ideaActive = System.getProperty("idea.active") == "true" && System.getProperty("publication.test") != "true" - - val nativeMainSets = mutableListOf() - val nativeTestSets = mutableListOf() - - if (ideaActive) { - when { - Os.isFamily(Os.FAMILY_MAC) -> if (Os.isArch("aarch64")) macosArm64("native") else macosX64("native") - Os.isFamily(Os.FAMILY_WINDOWS) -> mingwX64("native") - else -> linuxX64("native") - } - } else { - // https://kotlinlang.org/docs/native-target-support.html - // Updated for Kotlin 1.8.0, serialization 1.5.0 - //kotlinx-serialization-core-iosarm32/ - - - //kotlinx-serialization-core-iosarm64/ - - - //kotlinx-serialization-core-iossimulatorarm64/ - - - //kotlinx-serialization-core-iosx64/ - - - //kotlinx-serialization-core-js/ - - - //kotlinx-serialization-core-jvm/ - - - //kotlinx-serialization-core-linuxarm32hfp/ - - - //kotlinx-serialization-core-linuxarm64/ - - - //kotlinx-serialization-core-linuxx64/ - - - //kotlinx-serialization-core-macosarm64/ - - - //kotlinx-serialization-core-macosx64/ - - - //kotlinx-serialization-core-metadata/ - - - //kotlinx-serialization-core-mingwx64/ - - - //kotlinx-serialization-core-mingwx86/ - - - //kotlinx-serialization-core-tvosarm64/ - - - //kotlinx-serialization-core-tvossimulatorarm64/ - - - //kotlinx-serialization-core-tvosx64/ - - - //kotlinx-serialization-core-watchosarm32/ - - - //kotlinx-serialization-core-watchosarm64/ - - - //kotlinx-serialization-core-watchossimulatora.../ - - - //kotlinx-serialization-core-watchosx64/ - - - //kotlinx-serialization-core-watchosx86/ - // Commented ones are not supported by kotlinx-coroutines-core - val nativeTargets: List = arrayOf( - // Tier 1: - "linuxX64", - "macosX64", - "macosArm64", - "iosSimulatorArm64", - "iosX64", - - // Tier 2: - "linuxArm64", -// "watchosSimulatorArm64", - "watchosX64w", - "wwatchosArm32", - "watchosArm64", - "tvosSimulatorArm64", - "tvosX64", - "tvosArm64", - "iosArm64", - - // Tier 3: -// "androidNativeArm32", -// "androidNativeArm64", -// "androidNativeX86", -// "androidNativeX64", - "mingwX64", -// "watchosDeviceArm64", - - // Deprecated: - "iosArm32", - "watchosX86", -// "wasm32", - "mingwX86", - "linuxArm32Hfp", -// "linuxMips32", -// "linuxMipsel32", - ).flatMap { it.split(", ") } - presets.filter { it.name in nativeTargets } - .forEach { preset -> - val target = targetFromPreset(preset, preset.name) - nativeMainSets.add(target.compilations[org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.MAIN_COMPILATION_NAME].kotlinSourceSets.first()) - nativeTestSets.add(target.compilations[org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.TEST_COMPILATION_NAME].kotlinSourceSets.first()) - } - - sourceSets { - if (!ideaActive) { - configure(nativeMainSets) { - dependsOn(sourceSets.maybeCreate("nativeMain")) - } - - configure(nativeTestSets) { - dependsOn(sourceSets.maybeCreate("nativeTest")) - } - } - } - } - - /* - val hostOs = System.getProperty("os.name") - val isMingwX64 = hostOs.startsWith("Windows") - val nativeTarget = when { - hostOs == "Mac OS X" -> macosX64("native") - hostOs == "Linux" -> linuxX64("native") - isMingwX64 -> mingwX64("native") - else -> throw GradleException("Host OS is not supported in Kotlin/Native.") - }*/ } + // https://kotlinlang.org/docs/native-target-support.html + // Updated for Kotlin 2.0.0, serialization 1.7.0 + // Commented ones are not supported by kotlinx-coroutines-core + + // Tier 1: + macosX64() + macosArm64() + iosSimulatorArm64() + iosX64() + + // Tier 2: + linuxX64() + linuxArm64() + watchosSimulatorArm64() + watchosX64() + watchosArm32() + watchosArm64() + tvosSimulatorArm64() + tvosX64() + tvosArm64() + iosArm64() + + // Tier 3: + androidNativeArm32() + androidNativeArm64() + androidNativeX86() + androidNativeX64() + sourceSets { val serializationVersion: String = Versions.serialization @@ -166,8 +79,6 @@ kotlin { api(kotlin("reflect")) } } - - val jvmMain by getting val jvmTest by getting { dependencies { api(kotlin("test-junit")) @@ -175,21 +86,10 @@ kotlin { api("org.yaml:snakeyaml:1.26") } } - - val jsMain by getting - val jsTest by getting - - val nativeMain by getting { - dependsOn(commonMain) - } - - val nativeTest by getting { - dependsOn(commonTest) - } } } mavenCentralPublish { singleDevGithubProject("Him188", "yamlkt") licenseFromGitHubProject("Apache-2.0", "master") -} +} \ No newline at end of file diff --git a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt index cd03343..9d5ac02 100644 --- a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt +++ b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt @@ -9,7 +9,6 @@ import net.mamoe.yamlkt.YamlBuilder.StringSerialization.* import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic -import kotlin.native.concurrent.SharedImmutable // region EscapeCharMappings @@ -25,7 +24,6 @@ internal const val STRING_ESC = '\\' internal const val INVALID = 0.toChar() internal const val UNICODE_ESC = 'u' -@SharedImmutable internal val REPLACEMENT_CHARS: Array = arrayOfNulls(128).apply { for (i in 0..0xf) { this[i] = "\\u000$i" From 942b838487c37283154b9999721f7bb05fc97c06 Mon Sep 17 00:00:00 2001 From: HatoYuze Date: Fri, 31 Jan 2025 10:49:13 +0900 Subject: [PATCH 5/7] refactor: use `contextualDecodingException` when failing to decode a char --- .../kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt | 7 +++---- .../kotlin/net.mamoe.yamlkt/internal/YamlDecoder.kt | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt index 2ab48c3..29fc8a9 100644 --- a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt +++ b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt @@ -107,16 +107,15 @@ public class YamlBuilder internal constructor( CHAR_DOUBLE_QUOTATION, /** - * Convert all [Char]s to their corresponding Unicode code points [Int] + * Encode [Char]s as their [code][Char.code] in integer. + * _(It will work like [Byte])_ * * For example, the character 'A' will be converted to 65 - * - * It will work like [Byte] */ CHAR_UNICODE_CODE, /** - * Directly use the character content of [Char] + * Don't quote any [Char]. * * When escaping is necessary, it defaults to using [CHAR_SINGLE_QUOTATION] */ diff --git a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlDecoder.kt b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlDecoder.kt index 4bb499a..c1edb69 100644 --- a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlDecoder.kt +++ b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/YamlDecoder.kt @@ -1016,7 +1016,7 @@ internal class YamlDecoder( this.debuggingLogDecoder(descriptor, index)?.let { when { it.length == 1 -> it.first() - it.any { !it.isDigit() } -> error("too many chars for a char: $it") + it.any { !it.isDigit() } -> throw contextualDecodingException("too many chars for a char: $it") else -> withIntegerValue("char", descriptor, index).limitToChar() } } ?: checkNonStrictNullability(descriptor, index) From d0d6319c808dcfe43fa882d77fe61f1d20b9cd09 Mon Sep 17 00:00:00 2001 From: HatoYuze Date: Sat, 1 Feb 2025 00:55:00 +0900 Subject: [PATCH 6/7] refactor: renamed elements of CharSerialization --- .../net.mamoe.yamlkt/YamlConfigurationInternal.kt | 14 +++++++------- .../kotlin/net.mamoe.yamlkt/internal/Escape.kt | 10 +++++----- .../net.mamoe.yamlkt/encoder/TestEncoderEscape.kt | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt index 29fc8a9..a3e5d17 100644 --- a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt +++ b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/YamlConfigurationInternal.kt @@ -97,14 +97,14 @@ public class YamlBuilder internal constructor( /** * Quote all [Char]s with `'` * - * If a value can't be serialized using single quotation, it will use [CHAR_DOUBLE_QUOTATION] + * If a value can't be serialized using single quotation, it will use [DOUBLE_QUOTATION] */ - CHAR_SINGLE_QUOTATION, + SINGLE_QUOTATION, /** * Quote all [Char]s with `"` */ - CHAR_DOUBLE_QUOTATION, + DOUBLE_QUOTATION, /** * Encode [Char]s as their [code][Char.code] in integer. @@ -112,14 +112,14 @@ public class YamlBuilder internal constructor( * * For example, the character 'A' will be converted to 65 */ - CHAR_UNICODE_CODE, + UNICODE_CODE, /** * Don't quote any [Char]. * - * When escaping is necessary, it defaults to using [CHAR_SINGLE_QUOTATION] + * When escaping is necessary, it defaults to using [SINGLE_QUOTATION] */ - NORMAL, + PLAIN, } @@ -272,7 +272,7 @@ internal class YamlConfigurationInternal internal constructor( // encoding @JvmField val encodeDefaultValues: Boolean = true, @JvmField val stringSerialization: YamlBuilder.StringSerialization = YamlBuilder.StringSerialization.NONE, - @JvmField val charSerialization: YamlBuilder.CharSerialization = YamlBuilder.CharSerialization.NORMAL, + @JvmField val charSerialization: YamlBuilder.CharSerialization = YamlBuilder.CharSerialization.PLAIN, @JvmField val nullSerialization: YamlBuilder.NullSerialization = YamlBuilder.NullSerialization.NULL, @JvmField val mapSerialization: MapSerialization = MapSerialization.BLOCK_MAP, @JvmField val classSerialization: MapSerialization = MapSerialization.BLOCK_MAP, diff --git a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt index 9d5ac02..3d1d25f 100644 --- a/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt +++ b/yamlkt/src/commonMain/kotlin/net.mamoe.yamlkt/internal/Escape.kt @@ -4,7 +4,7 @@ package net.mamoe.yamlkt.internal import net.mamoe.yamlkt.YamlBuilder -import net.mamoe.yamlkt.YamlBuilder.CharSerialization.* +import net.mamoe.yamlkt.YamlBuilder.CharSerialization import net.mamoe.yamlkt.YamlBuilder.StringSerialization.* import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -604,13 +604,13 @@ internal fun Char.isHexDigit(): Boolean = this in '0'..'9' || this in 'a'..'f' | private const val ESCAPED_CHARACTERS: String = "[]{}\"'\\$^*|>-?/~,:#" internal fun Char.encodeEscapedString( - charSerialization: YamlBuilder.CharSerialization + charSerialization: CharSerialization ): String { - if (charSerialization == CHAR_UNICODE_CODE) { + if (charSerialization == CharSerialization.UNICODE_CODE) { return this.code.toString() } - var requiresDoubleQuoted = charSerialization == CHAR_DOUBLE_QUOTATION || this == '\'' - val requiresSingleQuoted = charSerialization == CHAR_SINGLE_QUOTATION || this in ESCAPED_CHARACTERS || this == ' ' + var requiresDoubleQuoted = charSerialization == CharSerialization.DOUBLE_QUOTATION || this == '\'' + val requiresSingleQuoted = charSerialization == CharSerialization.SINGLE_QUOTATION || this in ESCAPED_CHARACTERS || this == ' ' val escapedChars = REPLACEMENT_CHARS.getOrNull(this.code)?.also { requiresDoubleQuoted = true } ?: this.toString() diff --git a/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt index 4fac563..fa915af 100644 --- a/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt +++ b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt @@ -20,17 +20,17 @@ private val best = Yaml { stringSerialization = YamlBuilder.StringSerialization.BEST_PERFORMANCE } -private val normal = Yaml { - charSerialization = YamlBuilder.CharSerialization.NORMAL +private val PLAIN = Yaml { + charSerialization = YamlBuilder.CharSerialization.PLAIN } private val singleChar = Yaml { - charSerialization = YamlBuilder.CharSerialization.CHAR_SINGLE_QUOTATION + charSerialization = YamlBuilder.CharSerialization.SINGLE_QUOTATION } private val doubleChar = Yaml { - charSerialization = YamlBuilder.CharSerialization.CHAR_DOUBLE_QUOTATION + charSerialization = YamlBuilder.CharSerialization.DOUBLE_QUOTATION } private val unicode = Yaml { - charSerialization = YamlBuilder.CharSerialization.CHAR_UNICODE_CODE + charSerialization = YamlBuilder.CharSerialization.UNICODE_CODE } internal class TestEncoderEscape { @@ -72,7 +72,7 @@ internal class TestEncoderEscape { fun testCharEscape() { assertEquals("\" \"", doubleChar.encodeToString(' ')) assertEquals("\' \'", singleChar.encodeToString(' ')) - assertEquals("\' \'", normal.encodeToString(' ')) + assertEquals("\' \'", PLAIN.encodeToString(' ')) assertEquals("32", unicode.encodeToString(' ')) assertEquals(' ', unicode.decodeFromString("32")) From f30fdd5695aa9901c12bd141dcd4d35967dbad32 Mon Sep 17 00:00:00 2001 From: HatoYuze Date: Sat, 1 Feb 2025 06:33:03 +0900 Subject: [PATCH 7/7] test: add test for char serialization --- .../encoder/TestEncoderEscape.kt | 24 +----- .../escaping/CharEscapingTest.kt | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+), 23 deletions(-) create mode 100644 yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/escaping/CharEscapingTest.kt diff --git a/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt index fa915af..d6e85ea 100644 --- a/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt +++ b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/encoder/TestEncoderEscape.kt @@ -1,6 +1,5 @@ package net.mamoe.yamlkt.encoder -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import net.mamoe.yamlkt.Yaml import net.mamoe.yamlkt.YamlBuilder @@ -20,18 +19,7 @@ private val best = Yaml { stringSerialization = YamlBuilder.StringSerialization.BEST_PERFORMANCE } -private val PLAIN = Yaml { - charSerialization = YamlBuilder.CharSerialization.PLAIN -} -private val singleChar = Yaml { - charSerialization = YamlBuilder.CharSerialization.SINGLE_QUOTATION -} -private val doubleChar = Yaml { - charSerialization = YamlBuilder.CharSerialization.DOUBLE_QUOTATION -} -private val unicode = Yaml { - charSerialization = YamlBuilder.CharSerialization.UNICODE_CODE -} + internal class TestEncoderEscape { @Test @@ -68,16 +56,6 @@ internal class TestEncoderEscape { assertEquals("\' \'", best.encodeToString(" ")) } - @Test - fun testCharEscape() { - assertEquals("\" \"", doubleChar.encodeToString(' ')) - assertEquals("\' \'", singleChar.encodeToString(' ')) - assertEquals("\' \'", PLAIN.encodeToString(' ')) - assertEquals("32", unicode.encodeToString(' ')) - - assertEquals(' ', unicode.decodeFromString("32")) - } - @Test fun testUnicodeEscape() { assertEquals( diff --git a/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/escaping/CharEscapingTest.kt b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/escaping/CharEscapingTest.kt new file mode 100644 index 0000000..588d238 --- /dev/null +++ b/yamlkt/src/commonTest/kotlin/net.mamoe.yamlkt/escaping/CharEscapingTest.kt @@ -0,0 +1,73 @@ +package net.mamoe.yamlkt.escaping + +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import net.mamoe.yamlkt.Yaml +import net.mamoe.yamlkt.YamlBuilder +import net.mamoe.yamlkt.encoder.allFlow +import kotlin.test.Test +import kotlin.test.assertEquals + +private val plain = Yaml { + charSerialization = YamlBuilder.CharSerialization.PLAIN +} +private val singleChar = Yaml { + charSerialization = YamlBuilder.CharSerialization.SINGLE_QUOTATION +} +private val doubleChar = Yaml { + charSerialization = YamlBuilder.CharSerialization.DOUBLE_QUOTATION +} +private val unicode = Yaml { + charSerialization = YamlBuilder.CharSerialization.UNICODE_CODE +} + +internal class CharEscapingTest { + + @Serializable + data class Container( + val c: Char, + ) + + @Test + fun testTopElement() { + assertEquals("\" \"", doubleChar.encodeToString(' ')) + assertEquals("\' \'", singleChar.encodeToString(' ')) + assertEquals("\' \'", plain.encodeToString(' ')) + assertEquals("32", unicode.encodeToString(' ')) + + assertEquals(' ', unicode.decodeFromString("32")) + } + + @Test + fun testClass() { + assertEquals("c: \" \"",doubleChar.encodeToString(Container(' '))) + assertEquals("c: ' '",singleChar.encodeToString(Container(' '))) + assertEquals("c: ' '",plain.encodeToString(Container(' '))) + assertEquals("c: 32",unicode.encodeToString(Container(' '))) + + assertEquals(Container(' '),doubleChar.decodeFromString("c: \" \"")) + assertEquals(Container(' '),singleChar.decodeFromString("c: ' '")) + assertEquals(Container(' '),plain.decodeFromString("c: ' '")) + assertEquals(Container(' '),unicode.decodeFromString("c: 32")) + } + + @Test + fun testList() { + val list = charArrayOf(' ','\n',',','-','a','文',':') + assertEquals("[ ' ', \"\\n\", ',', '-', a, 文, ':' ]",allFlow.encodeToString(list)) + assertEquals(list.concatToString(), allFlow.decodeFromString("[ ' ', \"\\n\", ',', '-', a, 文, ':' ]").concatToString()) + } + + @Test + fun testMap() { + val map = mapOf( + 'a' to 'b' + ) + assertEquals(map, allFlow.decodeFromString(allFlow.encodeToString(map))) + assertEquals(map, unicode.decodeFromString(unicode.encodeToString(map))) + assertEquals(map, allFlow.decodeFromString("{ a: 98 }")) + } + + +} \ No newline at end of file