diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts index 20ba7ef..6ff1081 100644 --- a/benchmark/build.gradle.kts +++ b/benchmark/build.gradle.kts @@ -23,8 +23,9 @@ dependencies { jmh("com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.15.1") jmh("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.1") jmh("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.1") - // ... - + // official JSON + val serializationVersion: String by rootProject + jmh("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion") } jmh { diff --git a/benchmark/src/jmh/kotlin/test/BytecodeChecker.kt b/benchmark/src/jmh/kotlin/test/BytecodeChecker.kt new file mode 100644 index 0000000..49ab921 --- /dev/null +++ b/benchmark/src/jmh/kotlin/test/BytecodeChecker.kt @@ -0,0 +1,8 @@ +package test + +import kotlinx.serialization.Serializable + +@Serializable +data class M( + val n: Nothing? +) diff --git a/benchmark/src/jmh/kotlin/test/Objects.kt b/benchmark/src/jmh/kotlin/test/Objects.kt index a095703..d1feb27 100644 --- a/benchmark/src/jmh/kotlin/test/Objects.kt +++ b/benchmark/src/jmh/kotlin/test/Objects.kt @@ -1,14 +1,25 @@ +@file:OptIn(ExperimentalUnsignedTypes::class) + package test +import kotlinx.datetime.Instant import kotlinx.datetime.serializers.InstantIso8601Serializer +import kotlinx.datetime.toKotlinInstant import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import net.peanuuutz.tomlkt.NativeOffsetDateTime import net.peanuuutz.tomlkt.TomlDecoder +import net.peanuuutz.tomlkt.TomlEncoder import net.peanuuutz.tomlkt.TomlOffsetDateTimeSerializer import org.intellij.lang.annotations.Language +import java.time.ZoneOffset + +fun main() { + +} @Serializable data class Config( @@ -74,7 +85,28 @@ object OffsetDateTimeSerializer : KSerializer { get() = InstantIso8601Serializer.descriptor override fun serialize(encoder: Encoder, value: Any) { - TODO("Not yet implemented") + if (encoder is TomlEncoder) { + when (value) { + is NativeOffsetDateTime -> { + TomlOffsetDateTimeSerializer().serialize(encoder, value) + } + is Instant -> { + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + val converted = value.value.atOffset(ZoneOffset.UTC) + TomlOffsetDateTimeSerializer().serialize(encoder, converted) + } + } + } else { + when (value) { + is NativeOffsetDateTime -> { + val converted = value.toInstant().toKotlinInstant() + InstantIso8601Serializer.serialize(encoder, converted) + } + is Instant -> { + InstantIso8601Serializer.serialize(encoder, value) + } + } + } } override fun deserialize(decoder: Decoder): Any { diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e615f9e..3186a44 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -67,7 +67,6 @@ kotlin { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion") } } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Annotations.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Annotations.kt index fd1018f..0775152 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Annotations.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Annotations.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.common.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.common.kt index 0155448..ffafd9a 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.common.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.common.kt @@ -1,3 +1,19 @@ +/* + Copyright 2023 Peanuuutz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package net.peanuuutz.tomlkt // -------- NativeLocalDateTime -------- diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Toml.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Toml.kt index 269f0b9..8650d0a 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Toml.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/Toml.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlConfig.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlConfig.kt index 516a263..606cc6f 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlConfig.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlConfig.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlDateTime.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlDateTime.kt index 62fab76..6330ded 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlDateTime.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlDateTime.kt @@ -1,3 +1,19 @@ +/* + Copyright 2023 Peanuuutz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package net.peanuuutz.tomlkt import kotlinx.serialization.KSerializer diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlDecoder.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlDecoder.kt index 4824b1c..853ec7a 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlDecoder.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlDecoder.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlElement.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlElement.kt index 5927880..64102c8 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlElement.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlElement.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import net.peanuuutz.tomlkt.internal.parser.ArrayNode import net.peanuuutz.tomlkt.internal.parser.KeyNode import net.peanuuutz.tomlkt.internal.parser.TreeNode import net.peanuuutz.tomlkt.internal.parser.ValueNode +import net.peanuuutz.tomlkt.internal.throwNonPrimitiveKey import net.peanuuutz.tomlkt.internal.toStringModified // -------- TomlElement -------- @@ -42,7 +43,7 @@ import net.peanuuutz.tomlkt.internal.toStringModified * Represents anything in TOML, including and only including [TomlNull], * [TomlLiteral], [TomlArray], [TomlTable]. * - * **Warning: Only use [Toml] to serialize/deserialize any subclass.** + * **Warning: Only use [Toml] to serialize/deserialize this or any subclass.** */ @Serializable(with = TomlElementSerializer::class) public sealed class TomlElement { @@ -83,14 +84,27 @@ public object TomlNull : TomlElement() { // ---- To TomlNull ---- /** - * Converts [this] to [TomlNull]. + * Casts [this] to [TomlNull]. * * @throws IllegalStateException if `this` is not `TomlNull`. */ -public fun TomlElement.toTomlNull(): TomlNull { +public fun TomlElement.asTomlNull(): TomlNull { return this as? TomlNull ?: failConversion("TomlNull") } +@Deprecated( + message = "Use asTomlNull() instead to better reflect the intent.", + replaceWith = ReplaceWith( + expression = "asTomlNull()", + imports = [ + "net.peanuuutz.tomlkt.asTomlNull" + ] + ) +) +public fun TomlElement.toTomlNull(): TomlNull { + return asTomlNull() +} + // -------- TomlLiteral -------- /** @@ -134,23 +148,36 @@ public class TomlLiteral internal constructor( // ---- To TomlLiteral ---- /** - * Converts [this] to [TomlLiteral]. + * Casts [this] to [TomlLiteral]. * * @throws IllegalStateException if `this` is not `TomlLiteral`. */ -public fun TomlElement.toTomlLiteral(): TomlLiteral { +public fun TomlElement.asTomlLiteral(): TomlLiteral { return this as? TomlLiteral ?: failConversion("TomlLiteral") } +@Deprecated( + message = "Use asTomlLiteral() instead to better reflect the intent.", + replaceWith = ReplaceWith( + expression = "asTomlLiteral()", + imports = [ + "net.peanuuutz.tomlkt.asTomlLiteral" + ] + ) +) +public fun TomlElement.toTomlLiteral(): TomlLiteral { + return asTomlLiteral() +} + /** - * Creates a [TomlLiteral] from the given boolean [value]. + * Creates a [TomlLiteral] from given boolean [value]. */ public fun TomlLiteral(value: Boolean): TomlLiteral { return TomlLiteral(value.toString(), isString = false) } /** - * Creates a [TomlLiteral] from the given numeric [value]. + * Creates a [TomlLiteral] from given numeric [value]. * * @see toStringModified */ @@ -159,49 +186,77 @@ public fun TomlLiteral(value: Number): TomlLiteral { } /** - * Creates a [TomlLiteral] from the given char [value]. + * Creates a [TomlLiteral] from given `UByte` [value]. + */ +public fun TomlLiteral(value: UByte): TomlLiteral { + return TomlLiteral(value.toString(), isString = false) +} + +/** + * Creates a [TomlLiteral] from given `UShort` [value]. + */ +public fun TomlLiteral(value: UShort): TomlLiteral { + return TomlLiteral(value.toString(), isString = false) +} + +/** + * Creates a [TomlLiteral] from given `UInt` [value]. + */ +public fun TomlLiteral(value: UInt): TomlLiteral { + return TomlLiteral(value.toString(), isString = false) +} + +/** + * Creates a [TomlLiteral] from given `ULong` [value]. + */ +public fun TomlLiteral(value: ULong): TomlLiteral { + return TomlLiteral(value.toString(), isString = false) +} + +/** + * Creates a [TomlLiteral] from given char [value]. */ public fun TomlLiteral(value: Char): TomlLiteral { return TomlLiteral(value.toString(), isString = true) } /** - * Creates a [TomlLiteral] from the given string [value]. + * Creates a [TomlLiteral] from given string [value]. */ public fun TomlLiteral(value: String): TomlLiteral { return TomlLiteral(value, isString = true) } /** - * Creates a [TomlLiteral] from the given `TomlLocalDateTime` [value]. + * Creates a [TomlLiteral] from given `TomlLocalDateTime` [value]. */ public fun TomlLiteral(value: TomlLocalDateTime): TomlLiteral { return TomlLiteral(value.toString(), isString = false) } /** - * Creates a [TomlLiteral] from the given `TomlOffsetDateTime` [value]. + * Creates a [TomlLiteral] from given `TomlOffsetDateTime` [value]. */ public fun TomlLiteral(value: TomlOffsetDateTime): TomlLiteral { return TomlLiteral(value.toString(), isString = false) } /** - * Creates a [TomlLiteral] from the given `TomlLocalDate` [value]. + * Creates a [TomlLiteral] from given `TomlLocalDate` [value]. */ public fun TomlLiteral(value: TomlLocalDate): TomlLiteral { return TomlLiteral(value.toString(), isString = false) } /** - * Creates a [TomlLiteral] from the given `TomlLocalTime` [value]. + * Creates a [TomlLiteral] from given `TomlLocalTime` [value]. */ public fun TomlLiteral(value: TomlLocalTime): TomlLiteral { return TomlLiteral(value.toString(), isString = false) } /** - * Creates a [TomlLiteral] from the given enum [value]. Delegates to the factory + * Creates a [TomlLiteral] from given enum [value]. Delegates to the factory * function which consumes string. * * @param E the enum class which `value` belongs to. @@ -524,16 +579,29 @@ public class TomlArray internal constructor( // ---- To TomlArray ---- /** - * Converts [this] to [TomlArray]. + * Casts [this] to [TomlArray]. * * @throws IllegalStateException if `this` is not `TomlArray`. */ -public fun TomlElement.toTomlArray(): TomlArray { +public fun TomlElement.asTomlArray(): TomlArray { return this as? TomlArray ?: failConversion("TomlArray") } +@Deprecated( + message = "Use asTomlArray() instead to better reflect the intent.", + replaceWith = ReplaceWith( + expression = "asTomlArray()", + imports = [ + "net.peanuuutz.tomlkt.asTomlArray" + ] + ) +) +public fun TomlElement.toTomlArray(): TomlArray { + return asTomlArray() +} + /** - * Creates a [TomlArray] from the given `Iterable` [value]. + * Creates a [TomlArray] from given `Iterable` [value]. */ public fun TomlArray(value: Iterable<*>): TomlArray { val content = value.map(Any?::toTomlElement) @@ -585,16 +653,29 @@ public class TomlTable internal constructor( // ---- To TomlTable ---- /** - * Converts [this] to [TomlTable]. + * Casts [this] to [TomlTable]. * * @throws IllegalStateException if `this` is not `TomlTable`. */ -public fun TomlElement.toTomlTable(): TomlTable { +public fun TomlElement.asTomlTable(): TomlTable { return this as? TomlTable ?: failConversion("TomlTable") } +@Deprecated( + message = "Use asTomlTable() instead to better reflect the intent.", + replaceWith = ReplaceWith( + expression = "asTomlTable()", + imports = [ + "net.peanuuutz.tomlkt.asTomlTable" + ] + ) +) +public fun TomlElement.toTomlTable(): TomlTable { + return asTomlTable() +} + /** - * Creates a [TomlTable] from the given map [value]. + * Creates a [TomlTable] from given `Map` [value]. */ public fun TomlTable(value: Map<*, *>): TomlTable { val content = buildMap(value.size) { @@ -657,7 +738,7 @@ internal fun Any?.toTomlKey(): String { return when (this) { is Boolean, is Number, is Char -> toString() is String -> this - else -> throw NonPrimitiveKeyException() + else -> throwNonPrimitiveKey(this) } } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlEncoder.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlEncoder.kt index dd94b52..59e0b9a 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlEncoder.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlEncoder.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlSpecific.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlSpecific.kt index 7408152..462ee82 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlSpecific.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlSpecific.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt index 7b0e51d..fc39e39 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlStringWriter.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt index c344848..638eb52 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/TomlWriter.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/DateTimeSerializers.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/DateTimeSerializers.kt index c34c6c4..cbd8b1f 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/DateTimeSerializers.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/DateTimeSerializers.kt @@ -1,3 +1,19 @@ +/* + Copyright 2023 Peanuuutz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package net.peanuuutz.tomlkt.internal import kotlinx.serialization.KSerializer @@ -13,11 +29,11 @@ import net.peanuuutz.tomlkt.NativeOffsetDateTime import net.peanuuutz.tomlkt.TomlDecoder import net.peanuuutz.tomlkt.TomlEncoder import net.peanuuutz.tomlkt.TomlLiteral +import net.peanuuutz.tomlkt.asTomlLiteral import net.peanuuutz.tomlkt.toLocalDate import net.peanuuutz.tomlkt.toLocalDateTime import net.peanuuutz.tomlkt.toLocalTime import net.peanuuutz.tomlkt.toOffsetDateTime -import net.peanuuutz.tomlkt.toTomlLiteral internal object LocalDateTimeSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( @@ -35,7 +51,7 @@ internal object LocalDateTimeSerializer : KSerializer { override fun deserialize(decoder: Decoder): NativeLocalDateTime { return if (decoder is TomlDecoder) { - decoder.decodeTomlElement().toTomlLiteral().toLocalDateTime() + decoder.decodeTomlElement().asTomlLiteral().toLocalDateTime() } else { NativeLocalDateTime(decoder.decodeString()) } @@ -58,7 +74,7 @@ internal object OffsetDateTimeSerializer : KSerializer { override fun deserialize(decoder: Decoder): NativeOffsetDateTime { return if (decoder is TomlDecoder) { - decoder.decodeTomlElement().toTomlLiteral().toOffsetDateTime() + decoder.decodeTomlElement().asTomlLiteral().toOffsetDateTime() } else { NativeOffsetDateTime(decoder.decodeString()) } @@ -81,7 +97,7 @@ internal object LocalDateSerializer : KSerializer { override fun deserialize(decoder: Decoder): NativeLocalDate { return if (decoder is TomlDecoder) { - decoder.decodeTomlElement().toTomlLiteral().toLocalDate() + decoder.decodeTomlElement().asTomlLiteral().toLocalDate() } else { NativeLocalDate(decoder.decodeString()) } @@ -104,7 +120,7 @@ internal object LocalTimeSerializer : KSerializer { override fun deserialize(decoder: Decoder): NativeLocalTime { return if (decoder is TomlDecoder) { - decoder.decodeTomlElement().toTomlLiteral().toLocalTime() + decoder.decodeTomlElement().asTomlLiteral().toLocalTime() } else { NativeLocalTime(decoder.decodeString()) } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt index b7e64aa..d317d9e 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/StringUtils.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -58,6 +58,7 @@ internal val AsciiMapping: List = buildList(128) { set('\b'.code, "\\b") set('\t'.code, "\\t") set('\n'.code, "\\n") + set(12, "\\f") set('\r'.code, "\\r") set('"'.code, "\\\"") set('\\'.code, "\\\\") @@ -80,8 +81,8 @@ internal fun Char.escape(multiline: Boolean = false): String { code >= 128 -> toString() !multiline -> AsciiMapping[code] this == '\\' -> "\\\\" - this == '\n' -> "\n" this == '\t' -> "\t" + this == '\n' -> "\n" this == '\r' -> "\r" else -> AsciiMapping[code] } @@ -115,14 +116,6 @@ internal fun String.unescape(): String { builder.append('\n') i++ } - 't' -> { - builder.append('\t') - i++ - } - 'r' -> { - builder.append('\r') - i++ - } '"' -> { builder.append('\"') i++ @@ -131,22 +124,39 @@ internal fun String.unescape(): String { builder.append('\\') i++ } + 'u' -> { + // \u0000. + require(lastIndex >= i + 5) { "Unexpected end in $this" } + val char = substring(i + 2, i + 6).toInt(16).toChar() + builder.append(char) + i += 5 + } + 'U' -> { + // \U00000000. + require(lastIndex >= i + 9) { "Unexpected end in $this" } + val char = substring(i + 2, i + 10).toInt(16).toChar() + builder.append(char) + i += 9 + } + 't' -> { + builder.append('\t') + i++ + } + 'r' -> { + builder.append('\r') + i++ + } 'b' -> { builder.append('\b') i++ } - 'u' -> { - require(lastIndex >= i + 5) { "Unexpected end in $this" } - val index = AsciiMapping.indexOf(substring(i, i + 6)) - if (index == -1) { - builder.append("\\u") - i++ - } else { - builder.append(index.toChar()) - i += 5 - } + 'f' -> { + builder.append(12.toChar()) + i++ + } + else -> { + error("Unknown escape $next") } - else -> error("Unknown escape $next") } i++ } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementDecoder.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementDecoder.kt index 4afdfef..02ebc69 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementDecoder.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementDecoder.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,9 +17,11 @@ package net.peanuuutz.tomlkt.internal import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.descriptors.StructureKind.CLASS +import kotlinx.serialization.descriptors.StructureKind.LIST +import kotlinx.serialization.descriptors.StructureKind.MAP +import kotlinx.serialization.descriptors.StructureKind.OBJECT import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.encoding.CompositeDecoder.Companion.DECODE_DONE import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME @@ -33,6 +35,10 @@ import net.peanuuutz.tomlkt.TomlLiteral import net.peanuuutz.tomlkt.TomlNull import net.peanuuutz.tomlkt.TomlSpecific import net.peanuuutz.tomlkt.TomlTable +import net.peanuuutz.tomlkt.asTomlArray +import net.peanuuutz.tomlkt.asTomlLiteral +import net.peanuuutz.tomlkt.asTomlNull +import net.peanuuutz.tomlkt.asTomlTable import net.peanuuutz.tomlkt.toBoolean import net.peanuuutz.tomlkt.toByte import net.peanuuutz.tomlkt.toChar @@ -41,55 +47,51 @@ import net.peanuuutz.tomlkt.toFloat import net.peanuuutz.tomlkt.toInt import net.peanuuutz.tomlkt.toLong import net.peanuuutz.tomlkt.toShort -import net.peanuuutz.tomlkt.toTomlArray -import net.peanuuutz.tomlkt.toTomlLiteral -import net.peanuuutz.tomlkt.toTomlNull -import net.peanuuutz.tomlkt.toTomlTable @OptIn(TomlSpecific::class) internal class TomlElementDecoder( private val config: TomlConfig, override val serializersModule: SerializersModule, private val element: TomlElement -) : Decoder, TomlDecoder { +) : TomlDecoder { override fun decodeBoolean(): Boolean { - return element.toTomlLiteral().toBoolean() + return element.asTomlLiteral().toBoolean() } override fun decodeByte(): Byte { - return element.toTomlLiteral().toByte() + return element.asTomlLiteral().toByte() } override fun decodeShort(): Short { - return element.toTomlLiteral().toShort() + return element.asTomlLiteral().toShort() } override fun decodeInt(): Int { - return element.toTomlLiteral().toInt() + return element.asTomlLiteral().toInt() } override fun decodeLong(): Long { - return element.toTomlLiteral().toLong() + return element.asTomlLiteral().toLong() } override fun decodeFloat(): Float { - return element.toTomlLiteral().toFloat() + return element.asTomlLiteral().toFloat() } override fun decodeDouble(): Double { - return element.toTomlLiteral().toDouble() + return element.asTomlLiteral().toDouble() } override fun decodeChar(): Char { - return element.toTomlLiteral().toChar() + return element.asTomlLiteral().toChar() } override fun decodeString(): String { - return element.toTomlLiteral().content + return element.asTomlLiteral().content } override fun decodeNull(): Nothing? { - return element.toTomlNull().content + return element.asTomlNull().content } override fun decodeNotNullMark(): Boolean { @@ -101,67 +103,99 @@ internal class TomlElementDecoder( } override fun decodeEnum(enumDescriptor: SerialDescriptor): Int { - return enumDescriptor.getElementIndex(element.toTomlLiteral().content) + return enumDescriptor.getElementIndex(element.asTomlLiteral().content) } override fun decodeInline(descriptor: SerialDescriptor): Decoder { - return this + return if (descriptor.isUnsignedInteger) { + InlineDecoder( + elementProvider = this::element, + delegate = this + ) + } else { + this + } } override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { - return when (descriptor.kind) { - StructureKind.CLASS -> ClassDecoder(element.toTomlTable()) - StructureKind.OBJECT -> ClassDecoder(element.toTomlTable()) - StructureKind.LIST -> ArrayDecoder(element.toTomlArray()) - StructureKind.MAP -> MapDecoder(element.toTomlTable()) - else -> throw UnsupportedSerialKindException(descriptor.kind) + return when (val kind = descriptor.kind) { + CLASS -> ClassDecoder(element.asTomlTable()) + OBJECT -> ClassDecoder(element.asTomlTable()) + LIST -> ArrayDecoder(element.asTomlArray()) + MAP -> MapDecoder(element.asTomlTable()) + else -> throwUnsupportedSerialKind(kind) } } - internal abstract inner class AbstractDecoder : Decoder, CompositeDecoder, TomlDecoder { + private class InlineDecoder( + private val elementProvider: () -> TomlElement, + private val delegate: TomlDecoder + ) : TomlDecoder by delegate { + override fun decodeByte(): Byte { + return elementProvider().asTomlLiteral().content.toUByte().toByte() + } + + override fun decodeShort(): Short { + return elementProvider().asTomlLiteral().content.toUShort().toShort() + } + + override fun decodeInt(): Int { + return elementProvider().asTomlLiteral().content.toUInt().toInt() + } + + override fun decodeLong(): Long { + return elementProvider().asTomlLiteral().content.toULong().toLong() + } + + override fun decodeInline(descriptor: SerialDescriptor): Decoder { + return if (descriptor.isUnsignedInteger) this else delegate + } + } + + private abstract inner class AbstractDecoder : TomlDecoder, CompositeDecoder { protected abstract val currentElement: TomlElement final override val serializersModule: SerializersModule get() = this@TomlElementDecoder.serializersModule final override fun decodeBoolean(): Boolean { - return currentElement.toTomlLiteral().toBoolean() + return currentElement.asTomlLiteral().toBoolean() } final override fun decodeByte(): Byte { - return currentElement.toTomlLiteral().toByte() + return currentElement.asTomlLiteral().toByte() } final override fun decodeShort(): Short { - return currentElement.toTomlLiteral().toShort() + return currentElement.asTomlLiteral().toShort() } final override fun decodeInt(): Int { - return currentElement.toTomlLiteral().toInt() + return currentElement.asTomlLiteral().toInt() } final override fun decodeLong(): Long { - return currentElement.toTomlLiteral().toLong() + return currentElement.asTomlLiteral().toLong() } final override fun decodeFloat(): Float { - return currentElement.toTomlLiteral().toFloat() + return currentElement.asTomlLiteral().toFloat() } final override fun decodeDouble(): Double { - return currentElement.toTomlLiteral().toDouble() + return currentElement.asTomlLiteral().toDouble() } final override fun decodeChar(): Char { - return currentElement.toTomlLiteral().toChar() + return currentElement.asTomlLiteral().toChar() } final override fun decodeString(): String { - return currentElement.toTomlLiteral().content + return currentElement.asTomlLiteral().content } final override fun decodeNull(): Nothing? { - return currentElement.toTomlNull().content + return currentElement.asTomlNull().content } final override fun decodeNotNullMark(): Boolean { @@ -172,79 +206,93 @@ internal class TomlElementDecoder( return currentElement } - final override fun decodeEnum(enumDescriptor: SerialDescriptor): Int { - return enumDescriptor.getElementIndex(currentElement.toTomlLiteral().content) + final override fun decodeInline(descriptor: SerialDescriptor): Decoder { + return if (descriptor.isUnsignedInteger) { + InlineDecoder( + elementProvider = this::currentElement, + delegate = this + ) + } else { + this + } } - final override fun decodeInline(descriptor: SerialDescriptor): Decoder { - return this + final override fun decodeEnum(enumDescriptor: SerialDescriptor): Int { + return enumDescriptor.getElementIndex(currentElement.asTomlLiteral().content) } override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { - return when (descriptor.kind) { - StructureKind.CLASS -> ClassDecoder(currentElement.toTomlTable()) - StructureKind.OBJECT -> ClassDecoder(currentElement.toTomlTable()) - StructureKind.LIST -> ArrayDecoder(currentElement.toTomlArray()) - StructureKind.MAP -> MapDecoder(currentElement.toTomlTable()) - else -> throw UnsupportedSerialKindException(descriptor.kind) + return when (val kind = descriptor.kind) { + CLASS -> ClassDecoder(currentElement.asTomlTable()) + OBJECT -> ClassDecoder(currentElement.asTomlTable()) + LIST -> ArrayDecoder(currentElement.asTomlArray()) + MAP -> MapDecoder(currentElement.asTomlTable()) + else -> throwUnsupportedSerialKind(kind) } } final override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean { - return decodeSerializableElement(descriptor, index, Boolean.serializer()) + return decodeElement(descriptor, index) { + decodeBoolean() + } } final override fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte { - return decodeSerializableElement(descriptor, index, Byte.serializer()) + return decodeElement(descriptor, index) { + decodeByte() + } } final override fun decodeShortElement(descriptor: SerialDescriptor, index: Int): Short { - return decodeSerializableElement(descriptor, index, Short.serializer()) + return decodeElement(descriptor, index) { + decodeShort() + } } final override fun decodeIntElement(descriptor: SerialDescriptor, index: Int): Int { - return decodeSerializableElement(descriptor, index, Int.serializer()) + return decodeElement(descriptor, index) { + decodeInt() + } } final override fun decodeLongElement(descriptor: SerialDescriptor, index: Int): Long { - return decodeSerializableElement(descriptor, index, Long.serializer()) + return decodeElement(descriptor, index) { + decodeLong() + } } final override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int): Float { - return decodeSerializableElement(descriptor, index, Float.serializer()) + return decodeElement(descriptor, index) { + decodeFloat() + } } final override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int): Double { - return decodeSerializableElement(descriptor, index, Double.serializer()) + return decodeElement(descriptor, index) { + decodeDouble() + } } final override fun decodeCharElement(descriptor: SerialDescriptor, index: Int): Char { - return decodeSerializableElement(descriptor, index, Char.serializer()) + return decodeElement(descriptor, index) { + decodeChar() + } } final override fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String { - return decodeSerializableElement(descriptor, index, String.serializer()) + return decodeElement(descriptor, index) { + decodeString() + } } final override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder { - return this - } - - final override fun endStructure(descriptor: SerialDescriptor) {} - } - - internal inner class ArrayDecoder( - private val array: TomlArray - ) : AbstractDecoder() { - private var elementIndex: Int = 0 - - override lateinit var currentElement: TomlElement - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - return if (elementIndex != array.size) { - elementIndex + return if (descriptor.getElementDescriptor(index).isUnsignedInteger) { + InlineElementDecoder( + parentDescriptor = descriptor, + elementIndex = index + ) } else { - DECODE_DONE + this } } @@ -254,15 +302,13 @@ internal class TomlElementDecoder( deserializer: DeserializationStrategy, previousValue: T? ): T? { - val element = array[elementIndex] - if (element == TomlNull) { - elementIndex++ - return null - } - currentElement = element - val value = deserializer.deserialize(this) - elementIndex++ - return value + return decodeElement(descriptor, index) { + if (currentElement == TomlNull) { + null + } else { + deserializer.deserialize(this) + } + } } override fun decodeSerializableElement( @@ -271,29 +317,224 @@ internal class TomlElementDecoder( deserializer: DeserializationStrategy, previousValue: T? ): T { - val element = array[elementIndex] - currentElement = element - val value = deserializer.deserialize(this) - elementIndex++ - return value + return decodeElement(descriptor, index) { + deserializer.deserialize(this) + } } - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - return array.size + protected inline fun decodeElement( + descriptor: SerialDescriptor, + index: Int, + block: () -> T + ): T { + beginElement(descriptor, index) + val value = block() + endElement(descriptor, index) + return value } - override fun decodeSequentially(): Boolean { - return true + protected abstract fun beginElement( + descriptor: SerialDescriptor, + index: Int + ) + + protected abstract fun endElement( + descriptor: SerialDescriptor, + index: Int + ) + + final override fun endStructure(descriptor: SerialDescriptor) {} + + private inner class InlineElementDecoder( + private val parentDescriptor: SerialDescriptor, + private val elementIndex: Int + ) : TomlDecoder { + private var decodedNotNullMark: Boolean = false + + override val serializersModule: SerializersModule + get() = this@AbstractDecoder.serializersModule + + override fun decodeBoolean(): Boolean { + return if (!decodedNotNullMark) { + decodeBooleanElement(parentDescriptor, elementIndex) + } else { + val value = this@AbstractDecoder.decodeBoolean() + decodedNotNullMark = false + value + } + } + + override fun decodeByte(): Byte { + return if (!decodedNotNullMark) { + decodeElement(parentDescriptor, elementIndex) { + currentElement.asTomlLiteral().content.toUByte().toByte() + } + } else { + val value = currentElement.asTomlLiteral().content.toUByte().toByte() + decodedNotNullMark = false + value + } + } + + override fun decodeShort(): Short { + return if (!decodedNotNullMark) { + decodeElement(parentDescriptor, elementIndex) { + currentElement.asTomlLiteral().content.toUShort().toShort() + } + } else { + val value = currentElement.asTomlLiteral().content.toUShort().toShort() + decodedNotNullMark = false + value + } + } + + override fun decodeInt(): Int { + return if (!decodedNotNullMark) { + decodeElement(parentDescriptor, elementIndex) { + currentElement.asTomlLiteral().content.toUInt().toInt() + } + } else { + val value = currentElement.asTomlLiteral().content.toUInt().toInt() + decodedNotNullMark = false + value + } + } + + override fun decodeLong(): Long { + return if (!decodedNotNullMark) { + decodeElement(parentDescriptor, elementIndex) { + currentElement.asTomlLiteral().content.toULong().toLong() + } + } else { + val value = currentElement.asTomlLiteral().content.toULong().toLong() + decodedNotNullMark = false + value + } + } + + override fun decodeFloat(): Float { + return if (!decodedNotNullMark) { + decodeFloatElement(parentDescriptor, elementIndex) + } else { + val value = this@AbstractDecoder.decodeFloat() + decodedNotNullMark = false + value + } + } + + override fun decodeDouble(): Double { + return if (!decodedNotNullMark) { + decodeDoubleElement(parentDescriptor, elementIndex) + } else { + val value = this@AbstractDecoder.decodeDouble() + decodedNotNullMark = false + value + } + } + + override fun decodeChar(): Char { + return if (!decodedNotNullMark) { + decodeCharElement(parentDescriptor, elementIndex) + } else { + val value = this@AbstractDecoder.decodeChar() + decodedNotNullMark = false + value + } + } + + override fun decodeString(): String { + return if (!decodedNotNullMark) { + decodeStringElement(parentDescriptor, elementIndex) + } else { + val value = this@AbstractDecoder.decodeString() + decodedNotNullMark = false + value + } + } + + override fun decodeNull(): Nothing? { + return if (!decodedNotNullMark) { + decodeElement(parentDescriptor, elementIndex) { + this@AbstractDecoder.decodeNull() + } + } else { + val value = this@AbstractDecoder.decodeNull() + decodedNotNullMark = false + value + } + } + + override fun decodeNotNullMark(): Boolean { + return if (!decodedNotNullMark) { + val isNotNull = decodeElement(parentDescriptor, elementIndex) { + this@AbstractDecoder.decodeNotNullMark() + } + decodedNotNullMark = true + isNotNull + } else { + this@AbstractDecoder.decodeNotNullMark() + } + } + + override fun decodeTomlElement(): TomlElement { + return if (!decodedNotNullMark) { + decodeElement(parentDescriptor, elementIndex) { + this@AbstractDecoder.decodeTomlElement() + } + } else { + val value = this@AbstractDecoder.decodeTomlElement() + decodedNotNullMark = false + value + } + } + + override fun decodeEnum(enumDescriptor: SerialDescriptor): Int { + return if (!decodedNotNullMark) { + decodeElement(parentDescriptor, elementIndex) { + this@AbstractDecoder.decodeEnum(enumDescriptor) + } + } else { + val value = this@AbstractDecoder.decodeEnum(enumDescriptor) + decodedNotNullMark = false + value + } + } + + override fun decodeInline(descriptor: SerialDescriptor): Decoder { + return if (!decodedNotNullMark) { + decodeElement(parentDescriptor, elementIndex) { + if (descriptor.isUnsignedInteger) this else this@AbstractDecoder + } + } else { + val decoder = if (descriptor.isUnsignedInteger) this else this@AbstractDecoder + decodedNotNullMark = false + decoder + } + } + + override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { + return if (!decodedNotNullMark) { + decodeElement(parentDescriptor, elementIndex) { + this@AbstractDecoder.beginStructure(descriptor) + } + } else { + val decoder = this@AbstractDecoder.beginStructure(descriptor) + decodedNotNullMark = false + decoder + } + } } } - internal inner class ClassDecoder( + private inner class ClassDecoder( table: TomlTable ) : AbstractDecoder() { private val iterator: Iterator> = table.iterator() private var consumedElementCount: Int = 0 + private var currentElementIndex: Int = 0 + override lateinit var currentElement: TomlElement override fun decodeElementIndex(descriptor: SerialDescriptor): Int { @@ -304,48 +545,29 @@ internal class TomlElementDecoder( currentElement = value val index = descriptor.getElementIndex(key) if (index == UNKNOWN_NAME && config.ignoreUnknownKeys.not()) { - throw UnknownKeyException(key) + throwUnknownKey(key) } + currentElementIndex = index index } else { DECODE_DONE } } iterator.hasNext() && config.ignoreUnknownKeys.not() -> { - throw UnknownKeyException(iterator.next().key) + throwUnknownKey(iterator.next().key) } else -> DECODE_DONE } } - override fun decodeNullableSerializableElement( - descriptor: SerialDescriptor, - index: Int, - deserializer: DeserializationStrategy, - previousValue: T? - ): T? { - if (currentElement == TomlNull) { - consumedElementCount++ - return null - } - val value = deserializer.deserialize(this) - consumedElementCount++ - return value - } + override fun beginElement(descriptor: SerialDescriptor, index: Int) {} - override fun decodeSerializableElement( - descriptor: SerialDescriptor, - index: Int, - deserializer: DeserializationStrategy, - previousValue: T? - ): T { - val value = deserializer.deserialize(this) + override fun endElement(descriptor: SerialDescriptor, index: Int) { consumedElementCount++ - return value } } - internal inner class MapDecoder( + private inner class MapDecoder( private val table: TomlTable ) : AbstractDecoder() { private val iterator: Iterator = iterator { @@ -355,46 +577,24 @@ internal class TomlElementDecoder( } } - private var elementIndex: Int = 0 + private var currentElementIndex: Int = 0 override lateinit var currentElement: TomlElement override fun decodeElementIndex(descriptor: SerialDescriptor): Int { return if (iterator.hasNext()) { - elementIndex + currentElementIndex } else { DECODE_DONE } } - override fun decodeNullableSerializableElement( - descriptor: SerialDescriptor, - index: Int, - deserializer: DeserializationStrategy, - previousValue: T? - ): T? { - val element = iterator.next() - if (element == TomlNull) { - elementIndex++ - return null - } - currentElement = element - val value = deserializer.deserialize(this) - elementIndex++ - return value + override fun beginElement(descriptor: SerialDescriptor, index: Int) { + currentElement = iterator.next() } - override fun decodeSerializableElement( - descriptor: SerialDescriptor, - index: Int, - deserializer: DeserializationStrategy, - previousValue: T? - ): T { - val element = iterator.next() - currentElement = element - val value = deserializer.deserialize(this) - elementIndex++ - return value + override fun endElement(descriptor: SerialDescriptor, index: Int) { + currentElementIndex++ } override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { @@ -405,4 +605,36 @@ internal class TomlElementDecoder( return true } } + + private inner class ArrayDecoder( + private val array: TomlArray + ) : AbstractDecoder() { + private var currentElementIndex: Int = 0 + + override lateinit var currentElement: TomlElement + + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + return if (currentElementIndex != array.size) { + currentElementIndex + } else { + DECODE_DONE + } + } + + override fun beginElement(descriptor: SerialDescriptor, index: Int) { + currentElement = array[currentElementIndex] + } + + override fun endElement(descriptor: SerialDescriptor, index: Int) { + currentElementIndex++ + } + + override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { + return array.size + } + + override fun decodeSequentially(): Boolean { + return true + } + } } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementEncoder.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementEncoder.kt index 16a782a..57407fe 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementEncoder.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementEncoder.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,9 +17,11 @@ package net.peanuuutz.tomlkt.internal import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.descriptors.StructureKind.CLASS +import kotlinx.serialization.descriptors.StructureKind.LIST +import kotlinx.serialization.descriptors.StructureKind.MAP +import kotlinx.serialization.descriptors.StructureKind.OBJECT import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule @@ -37,7 +39,7 @@ import net.peanuuutz.tomlkt.toTomlKey internal class TomlElementEncoder( private val config: TomlConfig, override val serializersModule: SerializersModule -) : Encoder, TomlEncoder { +) : TomlEncoder { lateinit var element: TomlElement override fun encodeBoolean(value: Boolean) { @@ -84,43 +86,75 @@ internal class TomlElementEncoder( element = value } - override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { - encodeString(enumDescriptor.getElementName(index)) + override fun encodeInline(descriptor: SerialDescriptor): Encoder { + return if (descriptor.isUnsignedInteger) { + InlineEncoder( + elementConsumer = this::element::set, + delegate = this + ) + } else { + this + } } - override fun encodeInline(descriptor: SerialDescriptor): Encoder { - return this + override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { + encodeString(enumDescriptor.getElementName(index)) } override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { return beginStructure(descriptor, this::element::set) } - private inline fun beginStructure( + private fun beginStructure( descriptor: SerialDescriptor, elementConsumer: (TomlElement) -> Unit ) : CompositeEncoder { - return when (descriptor.kind) { - StructureKind.CLASS, StructureKind.OBJECT -> { + return when (val kind = descriptor.kind) { + CLASS, OBJECT -> { val builder = mutableMapOf() elementConsumer(TomlTable(builder)) ClassEncoder(builder) } - StructureKind.LIST -> { + LIST -> { val builder = mutableListOf() elementConsumer(TomlArray(builder)) ArrayEncoder(builder) } - StructureKind.MAP -> { + MAP -> { val builder = mutableMapOf() elementConsumer(TomlTable(builder)) MapEncoder(builder) } - else -> throw UnsupportedSerialKindException(descriptor.kind) + else -> throwUnsupportedSerialKind(kind) + } + } + + private class InlineEncoder( + private val elementConsumer: (TomlElement) -> Unit, + private val delegate: TomlEncoder + ) : TomlEncoder by delegate { + override fun encodeByte(value: Byte) { + elementConsumer(TomlLiteral(value.toUByte())) + } + + override fun encodeShort(value: Short) { + elementConsumer(TomlLiteral(value.toUShort())) + } + + override fun encodeInt(value: Int) { + elementConsumer(TomlLiteral(value.toUInt())) + } + + override fun encodeLong(value: Long) { + elementConsumer(TomlLiteral(value.toULong())) + } + + override fun encodeInline(descriptor: SerialDescriptor): Encoder { + return if (descriptor.isUnsignedInteger) this else delegate } } - internal abstract inner class AbstractEncoder : Encoder, CompositeEncoder, TomlEncoder { + private abstract inner class AbstractEncoder : TomlEncoder, CompositeEncoder { lateinit var currentElement: TomlElement final override val serializersModule: SerializersModule @@ -170,12 +204,19 @@ internal class TomlElementEncoder( currentElement = value } - final override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { - encodeString(enumDescriptor.getElementName(index)) + final override fun encodeInline(descriptor: SerialDescriptor): Encoder { + return if (descriptor.isUnsignedInteger) { + InlineEncoder( + elementConsumer = this::currentElement::set, + delegate = this + ) + } else { + this + } } - final override fun encodeInline(descriptor: SerialDescriptor): Encoder { - return this + final override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { + encodeString(enumDescriptor.getElementName(index)) } final override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { @@ -183,43 +224,68 @@ internal class TomlElementEncoder( } final override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) { - encodeSerializableElement(descriptor, index, Boolean.serializer(), value) + encodeElement(descriptor, index) { + encodeBoolean(value) + } } final override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte) { - encodeSerializableElement(descriptor, index, Byte.serializer(), value) + encodeElement(descriptor, index) { + encodeByte(value) + } } final override fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short) { - encodeSerializableElement(descriptor, index, Short.serializer(), value) + encodeElement(descriptor, index) { + encodeShort(value) + } } final override fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int) { - encodeSerializableElement(descriptor, index, Int.serializer(), value) + encodeElement(descriptor, index) { + encodeInt(value) + } } final override fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long) { - encodeSerializableElement(descriptor, index, Long.serializer(), value) + encodeElement(descriptor, index) { + encodeLong(value) + } } final override fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float) { - encodeSerializableElement(descriptor, index, Float.serializer(), value) + encodeElement(descriptor, index) { + encodeFloat(value) + } } final override fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double) { - encodeSerializableElement(descriptor, index, Double.serializer(), value) + encodeElement(descriptor, index) { + encodeDouble(value) + } } final override fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char) { - encodeSerializableElement(descriptor, index, Char.serializer(), value) + encodeElement(descriptor, index) { + encodeChar(value) + } } final override fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String) { - encodeSerializableElement(descriptor, index, String.serializer(), value) + encodeElement(descriptor, index) { + encodeString(value) + } } final override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder { - TODO("Not yet implemented") + return if (descriptor.getElementDescriptor(index).isUnsignedInteger) { + InlineElementEncoder( + parentDescriptor = descriptor, + elementIndex = index + ) + } else { + this + } } final override fun encodeNullableSerializableElement( @@ -229,50 +295,144 @@ internal class TomlElementEncoder( value: T? ) { if (value == null) { - currentElement = TomlNull + encodeSerializableElement(descriptor, index, TomlNull.serializer(), TomlNull) } else { encodeSerializableElement(descriptor, index, serializer, value) } } - final override fun endStructure(descriptor: SerialDescriptor) {} - } - - internal inner class ArrayEncoder( - private val builder: MutableList - ) : AbstractEncoder() { override fun encodeSerializableElement( descriptor: SerialDescriptor, index: Int, serializer: SerializationStrategy, value: T ) { - serializer.serialize(this, value) - builder.add(currentElement) + encodeElement(descriptor, index) { + serializer.serialize(this, value) + } } - } - internal inner class ClassEncoder( - private val builder: MutableMap - ) : AbstractEncoder() { - override fun encodeSerializableElement( + protected inline fun encodeElement( descriptor: SerialDescriptor, index: Int, - serializer: SerializationStrategy, - value: T + block: () -> Unit ) { - val key = descriptor.getElementName(index) - serializer.serialize(this, value) - builder[key] = currentElement + beginElement(descriptor, index) + block() + endElement(descriptor, index) + } + + protected abstract fun beginElement( + descriptor: SerialDescriptor, + index: Int + ) + + protected abstract fun endElement( + descriptor: SerialDescriptor, + index: Int + ) + + final override fun endStructure(descriptor: SerialDescriptor) {} + + private inner class InlineElementEncoder( + private val parentDescriptor: SerialDescriptor, + private val elementIndex: Int + ) : TomlEncoder { + override val serializersModule: SerializersModule + get() = this@AbstractEncoder.serializersModule + + override fun encodeBoolean(value: Boolean) { + encodeBooleanElement(parentDescriptor, elementIndex, value) + } + + override fun encodeByte(value: Byte) { + encodeElement(parentDescriptor, elementIndex) { + currentElement = TomlLiteral(value.toUByte()) + } + } + + override fun encodeShort(value: Short) { + encodeElement(parentDescriptor, elementIndex) { + currentElement = TomlLiteral(value.toUShort()) + } + } + + override fun encodeInt(value: Int) { + encodeElement(parentDescriptor, elementIndex) { + currentElement = TomlLiteral(value.toUInt()) + } + } + + override fun encodeLong(value: Long) { + encodeElement(parentDescriptor, elementIndex) { + currentElement = TomlLiteral(value.toULong()) + } + } + + override fun encodeFloat(value: Float) { + encodeFloatElement(parentDescriptor, elementIndex, value) + } + + override fun encodeDouble(value: Double) { + encodeDoubleElement(parentDescriptor, elementIndex, value) + } + + override fun encodeChar(value: Char) { + encodeCharElement(parentDescriptor, elementIndex, value) + } + + override fun encodeString(value: String) { + encodeStringElement(parentDescriptor, elementIndex, value) + } + + override fun encodeNull() { + encodeElement(parentDescriptor, elementIndex) { + currentElement = TomlNull + } + } + + override fun encodeTomlElement(value: TomlElement) { + encodeElement(parentDescriptor, elementIndex) { + currentElement = value + } + } + + override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { + encodeElement(parentDescriptor, elementIndex) { + this@AbstractEncoder.encodeEnum(enumDescriptor, index) + } + } + + override fun encodeInline(descriptor: SerialDescriptor): Encoder { + return if (descriptor.isUnsignedInteger) this else this@AbstractEncoder + } + + override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { + return this@AbstractEncoder.beginStructure(descriptor) + } + } + } + + private inner class ClassEncoder( + private val builder: MutableMap + ) : AbstractEncoder() { + private lateinit var currentKey: String + + override fun beginElement(descriptor: SerialDescriptor, index: Int) { + currentKey = descriptor.getElementName(index) + } + + override fun endElement(descriptor: SerialDescriptor, index: Int) { + builder[currentKey] = currentElement } } - internal inner class MapEncoder( + private inner class MapEncoder( private val builder: MutableMap ) : AbstractEncoder() { private var isKey: Boolean = true - private lateinit var key: String + private lateinit var currentKey: String override fun encodeSerializableElement( descriptor: SerialDescriptor, @@ -281,12 +441,28 @@ internal class TomlElementEncoder( value: T ) { if (isKey) { - key = value.toTomlKey() + currentKey = value.toTomlKey() } else { serializer.serialize(this, value) - builder[key] = currentElement + builder[currentKey] = currentElement } isKey = !isKey } + + // This shouldn't be called. + override fun beginElement(descriptor: SerialDescriptor, index: Int) {} + + // This shouldn't be called. + override fun endElement(descriptor: SerialDescriptor, index: Int) {} + } + + private inner class ArrayEncoder( + private val builder: MutableList + ) : AbstractEncoder() { + override fun beginElement(descriptor: SerialDescriptor, index: Int) {} + + override fun endElement(descriptor: SerialDescriptor, index: Int) { + builder.add(currentElement) + } } } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementSerializers.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementSerializers.kt index 2d4656b..1702882 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementSerializers.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlElementSerializers.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -35,12 +35,12 @@ import net.peanuuutz.tomlkt.TomlElement import net.peanuuutz.tomlkt.TomlLiteral import net.peanuuutz.tomlkt.TomlNull import net.peanuuutz.tomlkt.TomlTable +import net.peanuuutz.tomlkt.asTomlArray import net.peanuuutz.tomlkt.asTomlDecoder import net.peanuuutz.tomlkt.asTomlEncoder -import net.peanuuutz.tomlkt.toTomlArray -import net.peanuuutz.tomlkt.toTomlLiteral -import net.peanuuutz.tomlkt.toTomlNull -import net.peanuuutz.tomlkt.toTomlTable +import net.peanuuutz.tomlkt.asTomlLiteral +import net.peanuuutz.tomlkt.asTomlNull +import net.peanuuutz.tomlkt.asTomlTable internal object TomlElementSerializer : KSerializer { override val descriptor: SerialDescriptor = buildSerialDescriptor( @@ -68,7 +68,7 @@ internal object TomlNullSerializer : KSerializer { } override fun deserialize(decoder: Decoder): TomlNull { - return decoder.asTomlDecoder().decodeTomlElement().toTomlNull() + return decoder.asTomlDecoder().decodeTomlElement().asTomlNull() } } @@ -83,7 +83,7 @@ internal object TomlLiteralSerializer : KSerializer { } override fun deserialize(decoder: Decoder): TomlLiteral { - return decoder.asTomlDecoder().decodeTomlElement().toTomlLiteral() + return decoder.asTomlDecoder().decodeTomlElement().asTomlLiteral() } } @@ -101,7 +101,7 @@ internal object TomlArraySerializer : KSerializer { } override fun deserialize(decoder: Decoder): TomlArray { - return decoder.asTomlDecoder().decodeTomlElement().toTomlArray() + return decoder.asTomlDecoder().decodeTomlElement().asTomlArray() } } @@ -120,6 +120,6 @@ internal object TomlTableSerializer : KSerializer { } override fun deserialize(decoder: Decoder): TomlTable { - return decoder.asTomlDecoder().decodeTomlElement().toTomlTable() + return decoder.asTomlDecoder().decodeTomlElement().asTomlTable() } } diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlFileEncoder.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlFileEncoder.kt index a6d9635..6b0017b 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlFileEncoder.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlFileEncoder.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package net.peanuuutz.tomlkt.internal import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialKind.CONTEXTUAL import kotlinx.serialization.descriptors.StructureKind.CLASS @@ -49,7 +48,7 @@ internal class TomlFileEncoder( private val config: TomlConfig, override val serializersModule: SerializersModule, private val writer: TomlWriter -) : Encoder, TomlEncoder { +) : TomlEncoder { override fun encodeBoolean(value: Boolean) { writer.writeBoolean(value) } @@ -92,15 +91,30 @@ internal class TomlFileEncoder( override fun encodeTomlElement(value: TomlElement) { when (value) { - TomlNull -> writer.writeNull() - is TomlLiteral -> writer.writeString(value.toString()) - is TomlArray -> TomlArray.serializer().serialize(this, value) - is TomlTable -> TomlTable.serializer().serialize(this, value) + TomlNull -> { + writer.writeNull() + } + is TomlLiteral -> { + writer.writeString(value.toString()) + } + is TomlArray -> { + TomlArray.serializer().serialize(this, value) + } + is TomlTable -> { + TomlTable.serializer().serialize(this, value) + } } } override fun encodeInline(descriptor: SerialDescriptor): Encoder { - return this + return if (descriptor.isUnsignedInteger) { + InlineEncoder( + writer = writer, + delegate = this + ) + } else { + this + } } override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { @@ -121,14 +135,16 @@ internal class TomlFileEncoder( FlowClassEncoder() } else { ClassEncoder( - structuredIndex = calculateStructuredIndex(descriptor), - structured = true, + structuredTableLikeIndex = calculateStructuredTableLikeIndex(descriptor), + isStructured = true, path = "" ) } } OBJECT -> FlowClassEncoder() - else -> throw UnsupportedSerialKindException(kind) + LIST -> throwUnsupportedSerialKind("Please use beginCollection() instead") + MAP -> throwUnsupportedSerialKind("Please use beginCollection() instead") + else -> throwUnsupportedSerialKind(kind) } } @@ -143,97 +159,135 @@ internal class TomlFileEncoder( ): CompositeEncoder { return when (val kind = descriptor.kind) { LIST -> { - if (forceInline || collectionSize == 0) { - FlowArrayEncoder(collectionSize) - } else { - BlockArrayEncoder(collectionSize, config.itemsPerLineInBlockArray) + when { + collectionSize == 0 -> FlowArrayEncoder(0) + forceInline -> FlowArrayEncoder(collectionSize) + else -> { + BlockArrayEncoder( + arraySize = collectionSize, + itemsPerLine = config.itemsPerLineInBlockArray + ) + } } } MAP -> { - if (forceInline || collectionSize == 0) { - FlowMapEncoder(collectionSize) - } else { - MapEncoder( - collectionSize = collectionSize, - valueDescriptor = descriptor.getElementDescriptor(1), - structured = true, - path = "" - ) + when { + collectionSize == 0 -> FlowMapEncoder(0) + forceInline -> FlowMapEncoder(collectionSize) + else -> { + MapEncoder( + mapSize = collectionSize, + valueDescriptor = descriptor.getElementDescriptor(1), + isStructured = true, + path = "" + ) + } } } - else -> throw UnsupportedSerialKindException(kind) + CLASS -> throwUnsupportedSerialKind("Please use beginStructure() instead") + OBJECT -> throwUnsupportedSerialKind("Please use beginStructure() instead") + else -> throwUnsupportedSerialKind(kind) + } + } + + private class InlineEncoder( + private val writer: TomlWriter, + private val delegate: TomlEncoder + ) : TomlEncoder by delegate { + override fun encodeByte(value: Byte) { + writer.writeString(value.toUByte().toString()) + } + + override fun encodeShort(value: Short) { + writer.writeString(value.toUShort().toString()) + } + + override fun encodeInt(value: Int) { + writer.writeString(value.toUInt().toString()) + } + + override fun encodeLong(value: Long) { + writer.writeString(value.toULong().toString()) + } + + override fun encodeInline(descriptor: SerialDescriptor): Encoder { + return if (descriptor.isUnsignedInteger) this else delegate + } + + override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { + return delegate.beginCollection(descriptor, collectionSize) } } - internal abstract inner class AbstractEncoder : Encoder, CompositeEncoder, TomlEncoder { - final override val serializersModule: SerializersModule + private abstract inner class AbstractEncoder : TomlEncoder, CompositeEncoder { + override val serializersModule: SerializersModule get() = this@TomlFileEncoder.serializersModule - final override fun encodeBoolean(value: Boolean) { + override fun encodeBoolean(value: Boolean) { this@TomlFileEncoder.encodeBoolean(value) } - final override fun encodeByte(value: Byte) { + override fun encodeByte(value: Byte) { this@TomlFileEncoder.encodeByte(value) } private fun encodeByteWithBase(value: Byte, base: TomlInteger.Base) { - require(value > 0) { - "Negative integer cannot be represented by other bases" + require(value >= 0 || base == TomlInteger.Base.Dec) { + "Negative integer cannot be represented by other bases, but found $value" } writer.writeString(base.prefix) writer.writeString(value.toString(base.value)) } - final override fun encodeShort(value: Short) { + override fun encodeShort(value: Short) { this@TomlFileEncoder.encodeShort(value) } private fun encodeShortWithBase(value: Short, base: TomlInteger.Base) { - require(value > 0) { - "Negative integer cannot be represented by other bases" + require(value >= 0 || base == TomlInteger.Base.Dec) { + "Negative integer cannot be represented by other bases, but found $value" } writer.writeString(base.prefix) writer.writeString(value.toString(base.value)) } - final override fun encodeInt(value: Int) { + override fun encodeInt(value: Int) { this@TomlFileEncoder.encodeInt(value) } private fun encodeIntWithBase(value: Int, base: TomlInteger.Base) { - require(value > 0) { - "Negative integer cannot be represented by other bases" + require(value >= 0 || base == TomlInteger.Base.Dec) { + "Negative integer cannot be represented by other bases, but found $value" } writer.writeString(base.prefix) writer.writeString(value.toString(base.value)) } - final override fun encodeLong(value: Long) { + override fun encodeLong(value: Long) { this@TomlFileEncoder.encodeLong(value) } private fun encodeLongWithBase(value: Long, base: TomlInteger.Base) { - require(value > 0) { - "Negative integer cannot be represented by other bases" + require(value >= 0 || base == TomlInteger.Base.Dec) { + "Negative integer cannot be represented by other bases, but found $value" } writer.writeString(base.prefix) writer.writeString(value.toString(base.value)) } - final override fun encodeFloat(value: Float) { + override fun encodeFloat(value: Float) { this@TomlFileEncoder.encodeFloat(value) } - final override fun encodeDouble(value: Double) { + override fun encodeDouble(value: Double) { this@TomlFileEncoder.encodeDouble(value) } - final override fun encodeChar(value: Char) { + override fun encodeChar(value: Char) { this@TomlFileEncoder.encodeChar(value) } - final override fun encodeString(value: String) { + override fun encodeString(value: String) { this@TomlFileEncoder.encodeString(value) } @@ -246,14 +300,14 @@ internal class TomlFileEncoder( private fun encodeLiteralString(value: String) { require('\'' !in value && '\n' !in value) { - "Cannot have '\\'' or '\\n' in literal string" + "Cannot have '\\'' or '\\n' in literal string, but found $value" } writer.writeString(value.singleQuoted) } private fun encodeMultilineLiteralString(value: String) { require("'''" !in value) { - "Cannot have \"\\'\\'\\'\" in multiline literal string" + "Cannot have \"\\'\\'\\'\" in multiline literal string, but found $value" } writer.writeString("'''") writer.writeLineFeed() @@ -261,19 +315,39 @@ internal class TomlFileEncoder( writer.writeString("'''") } - final override fun encodeNull() { + override fun encodeNull() { this@TomlFileEncoder.encodeNull() } - final override fun encodeTomlElement(value: TomlElement) { - this@TomlFileEncoder.encodeTomlElement(value) + override fun encodeTomlElement(value: TomlElement) { + when (value) { + TomlNull -> { + writer.writeNull() + } + is TomlLiteral -> { + writer.writeString(value.toString()) + } + is TomlArray -> { + TomlArray.serializer().serialize(this, value) + } + is TomlTable -> { + TomlTable.serializer().serialize(this, value) + } + } } final override fun encodeInline(descriptor: SerialDescriptor): Encoder { - return this + return if (descriptor.isUnsignedInteger) { + InlineEncoder( + writer = writer, + delegate = this + ) + } else { + this + } } - final override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { + override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { this@TomlFileEncoder.encodeEnum(enumDescriptor, index) } @@ -286,7 +360,9 @@ internal class TomlFileEncoder( } final override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) { - encodeSerializableElement(descriptor, index, Boolean.serializer(), value) + encodeElement(descriptor, index) { + encodeBoolean(value) + } } final override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte) { @@ -312,44 +388,57 @@ internal class TomlFileEncoder( encodeNormally: (T) -> Unit, encodeWithBase: (T, TomlInteger.Base) -> Unit ) { - head(descriptor, index) - val annotations = descriptor.getElementAnnotations(index).filterIsInstance() - if (annotations.isEmpty()) { - encodeNormally(value) - } else { - encodeWithBase(value, annotations[0].base) + encodeElement(descriptor, index) { + val annotations = descriptor.getElementAnnotations(index).filterIsInstance() + if (annotations.isEmpty()) { + encodeNormally(value) + } else { + encodeWithBase(value, annotations[0].base) + } } - tail(descriptor, index) } final override fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float) { - encodeSerializableElement(descriptor, index, Float.serializer(), value) + encodeElement(descriptor, index) { + encodeFloat(value) + } } final override fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double) { - encodeSerializableElement(descriptor, index, Double.serializer(), value) + encodeElement(descriptor, index) { + encodeDouble(value) + } } final override fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char) { - encodeSerializableElement(descriptor, index, Char.serializer(), value) + encodeElement(descriptor, index) { + encodeChar(value) + } } final override fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String) { - head(descriptor, index) - val annotations = descriptor.getElementAnnotations(index) - val isLiteral = annotations.hasAnnotation() - val isMultiline = annotations.hasAnnotation() - when { - !isLiteral && !isMultiline -> encodeString(value) - !isLiteral -> encodeMultilineString(value) - !isMultiline -> encodeLiteralString(value) - else -> encodeMultilineLiteralString(value) + encodeElement(descriptor, index) { + val annotations = descriptor.getElementAnnotations(index) + val isLiteral = annotations.hasAnnotation() + val isMultiline = annotations.hasAnnotation() + when { + !isLiteral && !isMultiline -> encodeString(value) + !isLiteral -> encodeMultilineString(value) + !isMultiline -> encodeLiteralString(value) + else -> encodeMultilineLiteralString(value) + } } - tail(descriptor, index) } final override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder { - TODO("Not yet implemented") + return if (descriptor.getElementDescriptor(index).isUnsignedInteger) { + InlineElementEncoder( + parentDescriptor = descriptor, + elementIndex = index + ) + } else { + this + } } final override fun encodeNullableSerializableElement( @@ -389,82 +478,121 @@ internal class TomlFileEncoder( serializer: SerializationStrategy, value: T ) { - head(descriptor, index) - serializer.serialize(this, value) - tail(descriptor, index) + encodeElement(descriptor, index) { + serializer.serialize(this, value) + } } - protected abstract fun head(descriptor: SerialDescriptor, index: Int) + protected inline fun encodeElement( + descriptor: SerialDescriptor, + index: Int, + block: () -> Unit + ) { + beginElement(descriptor, index) + block() + endElement(descriptor, index) + } - protected abstract fun tail(descriptor: SerialDescriptor, index: Int) - } + protected abstract fun beginElement( + descriptor: SerialDescriptor, + index: Int + ) - internal inner class BlockArrayEncoder( - private val collectionSize: Int, - private val itemsPerLine: Int - ) : AbstractEncoder() { - private var itemsInCurrentLine: Int = 0 + protected abstract fun endElement( + descriptor: SerialDescriptor, + index: Int + ) + + private inner class InlineElementEncoder( + private val parentDescriptor: SerialDescriptor, + private val elementIndex: Int + ) : TomlEncoder { + override val serializersModule: SerializersModule + get() = this@AbstractEncoder.serializersModule + + override fun encodeBoolean(value: Boolean) { + encodeBooleanElement(parentDescriptor, elementIndex, value) + } - init { - writer.writeChar('[') - writer.writeLineFeed() - } + override fun encodeByte(value: Byte) { + encodeElement(parentDescriptor, elementIndex) { + writer.writeString(value.toUByte().toString()) + } + } - override fun head(descriptor: SerialDescriptor, index: Int) { - if (itemsInCurrentLine == 0) { - writer.writeString(config.indentation.representation) - } else { - writer.writeChar(' ') + override fun encodeShort(value: Short) { + encodeElement(parentDescriptor, elementIndex) { + writer.writeString(value.toUShort().toString()) + } } - } - override fun tail(descriptor: SerialDescriptor, index: Int) { - if (index != collectionSize - 1) { - writer.writeChar(',') + override fun encodeInt(value: Int) { + encodeElement(parentDescriptor, elementIndex) { + writer.writeString(value.toUInt().toString()) + } } - if (++itemsInCurrentLine >= itemsPerLine) { - writer.writeLineFeed() - itemsInCurrentLine = 0 + + override fun encodeLong(value: Long) { + encodeElement(parentDescriptor, elementIndex) { + writer.writeString(value.toULong().toString()) + } } - } - override fun endStructure(descriptor: SerialDescriptor) { - if (itemsInCurrentLine != 0) { - writer.writeLineFeed() + override fun encodeFloat(value: Float) { + encodeFloatElement(parentDescriptor, elementIndex, value) } - writer.writeChar(']') - } - } - internal inner class FlowArrayEncoder( - private val collectionSize: Int - ) : AbstractEncoder() { - init { writer.writeString("[ ") } + override fun encodeDouble(value: Double) { + encodeDoubleElement(parentDescriptor, elementIndex, value) + } - override fun head(descriptor: SerialDescriptor, index: Int) {} + override fun encodeChar(value: Char) { + encodeCharElement(parentDescriptor, elementIndex, value) + } - override fun tail(descriptor: SerialDescriptor, index: Int) { - if (index != collectionSize - 1) { - writer.writeString(", ") + override fun encodeString(value: String) { + encodeStringElement(parentDescriptor, elementIndex, value) } - } - override fun endStructure(descriptor: SerialDescriptor) { - writer.writeString(" ]") + override fun encodeNull() { + encodeSerializableElement(parentDescriptor, elementIndex, TomlNull.serializer(), TomlNull) + } + + override fun encodeTomlElement(value: TomlElement) { + encodeSerializableElement(parentDescriptor, elementIndex, TomlElement.serializer(), value) + } + + override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) { + encodeElement(parentDescriptor, elementIndex) { + this@AbstractEncoder.encodeEnum(enumDescriptor, index) + } + } + + override fun encodeInline(descriptor: SerialDescriptor): Encoder { + return if (descriptor.isUnsignedInteger) this else this@AbstractEncoder + } + + override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { + return this@AbstractEncoder.beginStructure(descriptor) + } + + override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { + return this@AbstractEncoder.beginCollection(descriptor, collectionSize) + } } } - internal inner class FlowClassEncoder : AbstractEncoder() { + private inner class FlowClassEncoder : AbstractEncoder() { init { writer.writeString("{ ") } - override fun head(descriptor: SerialDescriptor, index: Int) { - val key = descriptor.getElementName(index) + override fun beginElement(descriptor: SerialDescriptor, index: Int) { + val currentKey = descriptor.getElementName(index) .escape() .doubleQuotedIfNotPure() - writer.writeKey(key) + writer.writeKey(currentKey) } - override fun tail(descriptor: SerialDescriptor, index: Int) { + override fun endElement(descriptor: SerialDescriptor, index: Int) { if (index != descriptor.elementsCount - 1) { writer.writeString(", ") } @@ -475,267 +603,349 @@ internal class TomlFileEncoder( } } - internal inner class FlowMapEncoder( - private val collectionSize: Int + private inner class FlowMapEncoder( + private val mapSize: Int ) : AbstractEncoder() { private var isKey: Boolean = true init { writer.writeString("{ ") } - override fun head(descriptor: SerialDescriptor, index: Int) {} - override fun encodeSerializableElement( descriptor: SerialDescriptor, index: Int, serializer: SerializationStrategy, value: T ) { - if (isKey) { - val key = value.toTomlKey() + if (isKey) { // This element is key. + val currentKey = value.toTomlKey() .escape() .doubleQuotedIfNotPure() - writer.writeKey(key) - } else { + writer.writeKey(currentKey) + } else { // This element is value (of the entry). serializer.serialize(this, value) + if (index != mapSize * 2 - 1) { + writer.writeString(", ") + } } - tail(descriptor, index) + isKey = !isKey } - override fun tail(descriptor: SerialDescriptor, index: Int) { - if (!isKey && index != collectionSize * 2 - 1) { + // This shouldn't be called. + override fun beginElement(descriptor: SerialDescriptor, index: Int) {} + + // This shouldn't be called. + override fun endElement(descriptor: SerialDescriptor, index: Int) {} + + override fun endStructure(descriptor: SerialDescriptor) { + writer.writeString(" }") + } + } + + private inner class FlowArrayEncoder( + private val arraySize: Int + ) : AbstractEncoder() { + init { writer.writeString("[ ") } + + override fun beginElement(descriptor: SerialDescriptor, index: Int) {} + + override fun endElement(descriptor: SerialDescriptor, index: Int) { + if (index != arraySize - 1) { writer.writeString(", ") } - isKey = !isKey } override fun endStructure(descriptor: SerialDescriptor) { - writer.writeString(" }") + writer.writeString(" ]") } } - internal abstract inner class TableLikeEncoder( - protected val structured: Boolean, - private val path: String + private inner class BlockArrayEncoder( + private val arraySize: Int, + private val itemsPerLine: Int ) : AbstractEncoder() { - protected var inlineChild: Boolean = false + private var itemsInCurrentLine: Int = 0 - protected lateinit var currentChildPath: String + init { + writer.writeChar('[') + writer.writeLineFeed() + } - protected var structuredChild: Boolean = false + override fun beginElement(descriptor: SerialDescriptor, index: Int) { + if (itemsInCurrentLine == 0) { + writer.writeString(config.indentation.representation) + } else { + writer.writeChar(' ') + } + } - override fun encodeSerializableElement( - descriptor: SerialDescriptor, - index: Int, - serializer: SerializationStrategy, - value: T - ) { - inlineChild = value.isNullLike - super.encodeSerializableElement(descriptor, index, serializer, value) + override fun endElement(descriptor: SerialDescriptor, index: Int) { + if (index != arraySize - 1) { + writer.writeChar(',') + } + if (++itemsInCurrentLine >= itemsPerLine) { + writer.writeLineFeed() + itemsInCurrentLine = 0 + } + } + + override fun endStructure(descriptor: SerialDescriptor) { + if (itemsInCurrentLine != 0) { + writer.writeLineFeed() + } + writer.writeChar(']') } + } + + private abstract inner class TableLikeEncoder( + protected val isStructured: Boolean, + private val path: String + ) : AbstractEncoder() { + protected lateinit var currentElementPath: String + + protected var shouldInlineCurrentElement: Boolean = false + + protected var shouldStructureCurrentElement: Boolean = false override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { return when (val kind = descriptor.kind) { CLASS -> { - if (inlineChild) { - FlowClassEncoder() - } else { - val childPath = if (structured && !structuredChild) { - currentChildPath.removePrefix("$path.") - } else { - currentChildPath + when { + shouldInlineCurrentElement -> FlowClassEncoder() + shouldStructureCurrentElement -> { + ClassEncoder( + structuredTableLikeIndex = calculateStructuredTableLikeIndex(descriptor), + isStructured = true, + path = currentElementPath + ) + } + else -> { + val realElementPath = getDroppedPath() + if (descriptor.elementsCount == 0) { + writer.writeKey(realElementPath) + FlowClassEncoder() + } else { + ClassEncoder( + structuredTableLikeIndex = Int.MAX_VALUE, + isStructured = false, + path = realElementPath + ) + } } - ClassEncoder( - structuredIndex = calculateStructuredIndex(descriptor), - structured = structuredChild, - path = childPath - ) } } OBJECT -> FlowClassEncoder() - else -> throw UnsupportedSerialKindException(kind) + LIST -> throwUnsupportedSerialKind("Please use beginCollection() instead") + MAP -> throwUnsupportedSerialKind("Please use beginCollection() instead") + else -> throwUnsupportedSerialKind(kind) } } - override fun beginCollection( - descriptor: SerialDescriptor, - collectionSize: Int - ): CompositeEncoder { + override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { return when (val kind = descriptor.kind) { LIST -> { when { - inlineChild || collectionSize == 0 -> FlowArrayEncoder(collectionSize) - structuredChild && descriptor.isArrayOfTables && blockArrayAnnotation == null -> { + collectionSize == 0 -> FlowArrayEncoder(0) + shouldInlineCurrentElement -> FlowArrayEncoder(collectionSize) + shouldStructureCurrentElement && blockArrayAnnotation == null && descriptor.isArrayOfTable -> { ArrayOfTableEncoder( - collectionSize = collectionSize, - path = currentChildPath + arraySize = collectionSize, + path = currentElementPath ) } else -> { - val itemsPerLine = blockArrayAnnotation?.itemsPerLine - ?: config.itemsPerLineInBlockArray + val itemsPerLine = blockArrayAnnotation?.itemsPerLine ?: config.itemsPerLineInBlockArray BlockArrayEncoder( - collectionSize = collectionSize, + arraySize = collectionSize, itemsPerLine = itemsPerLine ) } } } MAP -> { - if (inlineChild || collectionSize == 0) { - FlowMapEncoder(collectionSize) - } else { - MapEncoder( - collectionSize = collectionSize, - valueDescriptor = descriptor.getElementDescriptor(1), - structured = structuredChild, - path = currentChildPath - ) + when { + shouldInlineCurrentElement -> FlowMapEncoder(collectionSize) + shouldStructureCurrentElement -> { + MapEncoder( + mapSize = collectionSize, + valueDescriptor = descriptor.getElementDescriptor(1), + isStructured = true, + path = currentElementPath + ) + } + else -> { + val realElementPath = getDroppedPath() + if (collectionSize == 0) { + writer.writeKey(realElementPath) + FlowMapEncoder(0) + } else { + MapEncoder( + mapSize = collectionSize, + valueDescriptor = descriptor.getElementDescriptor(1), + isStructured = false, + path = realElementPath + ) + } + } } } - else -> throw UnsupportedSerialKindException(kind) + CLASS -> throwUnsupportedSerialKind("Please use beginStructure() instead") + OBJECT -> throwUnsupportedSerialKind("Please use beginStructure() instead") + else -> throwUnsupportedSerialKind(kind) } } protected open val blockArrayAnnotation: TomlBlockArray? get() = null - protected fun concatPath(childPath: String): String { - return if (path.isNotEmpty()) "$path.$childPath" else childPath + protected fun getConcatenatedPath(currentElementPath: String): String { + return if (path.isNotEmpty()) { + "$path.$currentElementPath" + } else { + currentElementPath + } + } + + private fun getDroppedPath(): String { + return if (isStructured) { + currentElementPath.removePrefix("$path.") + } else { + currentElementPath + } } override fun endStructure(descriptor: SerialDescriptor) {} } - internal inner class ClassEncoder( - private val structuredIndex: Int, - structured: Boolean, + private inner class ClassEncoder( + private val structuredTableLikeIndex: Int, + isStructured: Boolean, path: String - ) : TableLikeEncoder(structured, path) { + ) : TableLikeEncoder(isStructured, path) { + private lateinit var currentElementDescriptor: SerialDescriptor + + private lateinit var currentKey: String + override var blockArrayAnnotation: TomlBlockArray? = null - override fun head(descriptor: SerialDescriptor, index: Int) { - val elementName = descriptor.getElementName(index) + override fun encodeSerializableElement( + descriptor: SerialDescriptor, + index: Int, + serializer: SerializationStrategy, + value: T + ) { + if (value.isNullOrTomlNull) { + shouldInlineCurrentElement = true + } + super.encodeSerializableElement(descriptor, index, serializer, value) + } + + override fun beginElement(descriptor: SerialDescriptor, index: Int) { + currentElementDescriptor = descriptor.getElementDescriptor(index) + currentKey = descriptor.getElementName(index) .escape() .doubleQuotedIfNotPure() - inlineChild = inlineChild || descriptor.forceInlineAt(index) - currentChildPath = concatPath(elementName) - structuredChild = structured && index >= structuredIndex - val key = if (structured) elementName else currentChildPath - val elementDescriptor = descriptor.getElementDescriptor(index) - comment(descriptor, index) - when { - inlineChild -> { - writer.writeKey(key) - } - structuredChild -> { - if (elementDescriptor.isTable) { - writer.writeLineFeed() - writer.writeString("[$currentChildPath]") - writer.writeLineFeed() - } else if (elementDescriptor.isArrayOfTables) { - val annotations = descriptor.getElementAnnotations(index) - .filterIsInstance() - if (annotations.isNotEmpty()) { - writer.writeKey(key) - blockArrayAnnotation = annotations[0] - } - } - } - elementDescriptor.isTable.not() -> { - writer.writeKey(key) - } - } + currentElementPath = getConcatenatedPath(currentKey) + shouldInlineCurrentElement = shouldInlineCurrentElement || descriptor.shouldForceInlineAt(index) + shouldStructureCurrentElement = isStructured && index >= structuredTableLikeIndex + processElementAnnotations(descriptor.getElementAnnotations(index)) + if (currentElementDescriptor.isCollection.not()) { + appendKey() + } // For non-null collections, defer to beginCollection(). } - private fun comment(descriptor: SerialDescriptor, index: Int) { - if (!structured) { + private fun processElementAnnotations(annotations: List) { + if (!isStructured) { return } - val lines = mutableListOf() - val annotations = descriptor.getElementAnnotations(index) - var forceInline = !structuredChild + val commentLines = mutableListOf() + var shouldAddExtraLineFeed = shouldStructureCurrentElement for (annotation in annotations) { when (annotation) { - is TomlInline -> forceInline = true is TomlComment -> { annotation.text .trimIndent() .split('\n') .map(String::escape) - .forEach(lines::add) + .forEach(commentLines::add) + } + is TomlBlockArray -> { + blockArrayAnnotation = annotation + } + is TomlInline -> { + shouldAddExtraLineFeed = false } } } - if (lines.size > 0) { - val elementDescriptor = descriptor.getElementDescriptor(index) - if (elementDescriptor.isTableLike && !forceInline) { + if (commentLines.size > 0) { + if (shouldAddExtraLineFeed && currentElementDescriptor.isTableLike) { writer.writeLineFeed() } - for (line in lines) { + for (line in commentLines) { writer.writeString("# $line") writer.writeLineFeed() } } } - override fun tail(descriptor: SerialDescriptor, index: Int) { - if (index != descriptor.elementsCount - 1) { - writer.writeLineFeed() + override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { + // descriptor == currentElementDescriptor. + if (descriptor.isArrayOfTable) { + if (collectionSize == 0 || !shouldStructureCurrentElement) { + appendKeyDirectly() + } + } else { + appendKey() } - blockArrayAnnotation = null - } - } - - internal inner class ArrayOfTableEncoder( - private val collectionSize: Int, - path: String - ) : TableLikeEncoder(true, path) { - init { - currentChildPath = path - structuredChild = structured + return super.beginCollection(descriptor, collectionSize) } - override fun encodeSerializableElement( - descriptor: SerialDescriptor, - index: Int, - serializer: SerializationStrategy, - value: T - ) { - if (value.isNullLike) { - throw NullInArrayOfTableException() + private fun appendKey() { + when { + shouldInlineCurrentElement -> { + appendKeyDirectly() + } + currentElementDescriptor.isTable -> { + if (shouldStructureCurrentElement) { + writer.writeLineFeed() + writer.writeString("[$currentElementPath]") + writer.writeLineFeed() + } + } + else -> { + appendKeyDirectly() + } } - super.encodeSerializableElement(descriptor, index, serializer, value) } - override fun head(descriptor: SerialDescriptor, index: Int) { - writer.writeLineFeed() - writer.writeString("[[$currentChildPath]]") - writer.writeLineFeed() + private fun appendKeyDirectly() { + writer.writeKey(if (isStructured) currentKey else currentElementPath) } - override fun tail(descriptor: SerialDescriptor, index: Int) { - if (index != collectionSize - 1) { + override fun endElement(descriptor: SerialDescriptor, index: Int) { + if (index != descriptor.elementsCount - 1) { writer.writeLineFeed() } + shouldInlineCurrentElement = false + blockArrayAnnotation = null } } - internal inner class MapEncoder( - private val collectionSize: Int, + private inner class MapEncoder( + private val mapSize: Int, private val valueDescriptor: SerialDescriptor, - structured: Boolean, + isStructured: Boolean, path: String - ) : TableLikeEncoder(structured, path) { + ) : TableLikeEncoder(isStructured, path) { private var currentElementIndex: Int = 0 private val isKey: Boolean get() = currentElementIndex % 2 == 0 - private lateinit var key: String + private lateinit var currentKey: String init { - inlineChild = valueDescriptor.kind == CONTEXTUAL || valueDescriptor.isExactlyTomlTable - structuredChild = structured + shouldInlineCurrentElement = valueDescriptor.shouldForceInline + shouldStructureCurrentElement = isStructured } override fun encodeSerializableElement( @@ -744,97 +954,149 @@ internal class TomlFileEncoder( serializer: SerializationStrategy, value: T ) { - if (isKey) { - storeKey(value) - } else { - val valueKind = valueDescriptor.kind - if (value.isNullLike) { - appendKeyDirectly() - } else if (valueKind != LIST && valueKind != MAP) { - appendKey() // For non-null collections, defer to beginCollection() below. + if (isKey) { // This element is key. + currentKey = value.toTomlKey() + .escape() + .doubleQuotedIfNotPure() + currentElementPath = getConcatenatedPath(currentKey) + } else { // This element is value (of the entry). + when { + value.isNullOrTomlNull -> { + appendKeyDirectly() + } + valueDescriptor.isCollection.not() -> { + appendKey() + } + // For non-null collections, defer to beginCollection(). } serializer.serialize(this, value) + if (index != mapSize * 2 - 1) { + writer.writeLineFeed() + } } - tail(descriptor, index) - } - - override fun head(descriptor: SerialDescriptor, index: Int) {} - - private fun storeKey(value: T) { - key = value.toTomlKey() - .escape() - .doubleQuotedIfNotPure() - currentChildPath = concatPath(key) + currentElementIndex++ } override fun beginCollection(descriptor: SerialDescriptor, collectionSize: Int): CompositeEncoder { - if (descriptor.isArrayOfTables.not()) { - appendKey() - } else if (collectionSize == 0) { - if (currentElementIndex != 1) { - throw EmptyArrayOfTableInMapException() + // descriptor == valueDescriptor. + if (descriptor.isArrayOfTable) { + if (collectionSize == 0 || !isStructured) { + if (collectionSize == 0 && currentElementIndex != 1) { + // Only the first array of table is allowed to be empty when in a map. + throwEmptyArrayOfTableInMap() + } + appendKeyDirectly() } - appendKeyDirectly() + } else { + appendKey() } return super.beginCollection(descriptor, collectionSize) } private fun appendKey() { when { - inlineChild -> { + shouldInlineCurrentElement -> { appendKeyDirectly() } - structuredChild -> { - if (valueDescriptor.isTable) { + valueDescriptor.isTable -> { + if (isStructured) { writer.writeLineFeed() - writer.writeString("[$currentChildPath]") + writer.writeString("[$currentElementPath]") writer.writeLineFeed() - } else { - writer.writeKey(key) } } - valueDescriptor.isTable.not() -> { - writer.writeKey(currentChildPath) + else -> { + appendKeyDirectly() } } } private fun appendKeyDirectly() { - writer.writeKey(if (structured) key else currentChildPath) + writer.writeKey(if (isStructured) currentKey else currentElementPath) + } + + // This shouldn't be called. + override fun beginElement(descriptor: SerialDescriptor, index: Int) {} + + // This shouldn't be called. + override fun endElement(descriptor: SerialDescriptor, index: Int) {} + } + + private inner class ArrayOfTableEncoder( + private val arraySize: Int, + path: String + ) : TableLikeEncoder(true, path) { + init { + currentElementPath = path + shouldStructureCurrentElement = true } - override fun tail(descriptor: SerialDescriptor, index: Int) { - if (!isKey && index != collectionSize * 2 - 1) { + override fun encodeSerializableElement( + descriptor: SerialDescriptor, + index: Int, + serializer: SerializationStrategy, + value: T + ) { + if (value.isNullOrTomlNull) { + throwNullInArrayOfTable() + } + super.encodeSerializableElement(descriptor, index, serializer, value) + } + + override fun beginElement(descriptor: SerialDescriptor, index: Int) { + writer.writeLineFeed() + writer.writeString("[[$currentElementPath]]") + writer.writeLineFeed() + } + + override fun endElement(descriptor: SerialDescriptor, index: Int) { + if (index != arraySize - 1) { writer.writeLineFeed() } - currentElementIndex++ } } } -private val SerialDescriptor.isTableLike: Boolean - get() = isTable || isArrayOfTables - private val SerialDescriptor.isTable: Boolean get() { val kind = kind return kind == CLASS || kind == MAP } -private val SerialDescriptor.isArrayOfTables: Boolean - get() = kind == LIST && getElementDescriptor(0).isTable +private val SerialDescriptor.isCollection: Boolean + get() { + val kind = kind + return kind == LIST || kind == MAP + } + +private val SerialDescriptor.isArrayOfTable: Boolean + get() { + if (kind != LIST) { + return false + } + val elementDescriptor = getElementDescriptor(0) + return elementDescriptor.isTable && elementDescriptor.isInline.not() + } + +private val SerialDescriptor.isTableLike: Boolean + get() = isTable || isArrayOfTable private val SerialDescriptor.isExactlyTomlTable: Boolean get() = serialName.removeSuffix("?") == TomlTable.serializer().descriptor.serialName -private val Any?.isNullLike: Boolean +private val SerialDescriptor.shouldForceInline: Boolean + get() = kind == CONTEXTUAL || + isExactlyTomlTable || + isInline + +private val Any?.isNullOrTomlNull: Boolean get() = this == null || this == TomlNull -private fun calculateStructuredIndex(descriptor: SerialDescriptor): Int { +private fun calculateStructuredTableLikeIndex(descriptor: SerialDescriptor): Int { var structuredIndex = 0 for (i in descriptor.elementsCount - 1 downTo 0) { val elementDescriptor = descriptor.getElementDescriptor(i) - if (descriptor.forceInlineAt(i) || elementDescriptor.isTableLike.not()) { + if (descriptor.shouldForceInlineAt(i) || elementDescriptor.isTableLike.not()) { structuredIndex = i + 1 break } @@ -842,12 +1104,9 @@ private fun calculateStructuredIndex(descriptor: SerialDescriptor): Int { return structuredIndex } -private fun SerialDescriptor.forceInlineAt(index: Int): Boolean { +private fun SerialDescriptor.shouldForceInlineAt(index: Int): Boolean { val elementDescriptor = getElementDescriptor(index) - return getElementAnnotations(index).hasAnnotation() || - elementDescriptor.kind == CONTEXTUAL || - elementDescriptor.isExactlyTomlTable || - elementDescriptor.isInline + return getElementAnnotations(index).hasAnnotation() || elementDescriptor.shouldForceInline } private inline fun List.hasAnnotation(): Boolean { diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlSerializationExceptions.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlSerializationExceptions.kt index 2c3b9cd..ad3e10f 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlSerializationExceptions.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/TomlSerializationExceptions.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,48 +22,86 @@ import net.peanuuutz.tomlkt.internal.parser.Path // -------- Encoding -------- -internal sealed class TomlEncodingException : SerializationException { - constructor() - constructor(message: String) : super(message) +internal sealed class TomlEncodingException(message: String) : SerializationException(message) + +// ---- NonPrimitiveKeyException ---- + +internal class NonPrimitiveKeyException(message: String) : TomlEncodingException(message) + +internal fun throwNonPrimitiveKey(key: Any?): Nothing { + throw NonPrimitiveKeyException(key.toString()) +} + +// ---- UnsupportedSerialKindException ---- + +internal class UnsupportedSerialKindException(message: String) : TomlEncodingException(message) + +internal fun throwUnsupportedSerialKind(kind: SerialKind): Nothing { + throw UnsupportedSerialKindException(kind.toString()) +} + +internal fun throwUnsupportedSerialKind(message: String): Nothing { + throw UnsupportedSerialKindException(message) +} + +// ---- NullInArrayOfTableException ---- + +private const val NullInArrayOfTableMessage: String = "Null is not allowed in array of table, " + + "please mark the corresponding property as @TomlBlockArray or @TomlInline" + +internal class NullInArrayOfTableException : TomlEncodingException(NullInArrayOfTableMessage) + +internal fun throwNullInArrayOfTable(): Nothing { + throw NullInArrayOfTableException() } -internal class NonPrimitiveKeyException : TomlEncodingException() +// ---- EmptyArrayOfTableInMapException ---- -internal class UnsupportedSerialKindException(kind: SerialKind) : TomlEncodingException( - message = kind.toString() -) +private const val EmptyArrayOfTableInMap: String = "At most one empty array of table is allowed in a map" -internal class NullInArrayOfTableException : TomlEncodingException( - message = "Null is not allowed in array of table, " + - "please mark the corresponding property as @TomlBlockArray or @TomlInline" -) +internal class EmptyArrayOfTableInMapException : TomlEncodingException(EmptyArrayOfTableInMap) -internal class EmptyArrayOfTableInMapException : TomlEncodingException( - message = "At most one empty array of table is allowed in a map" -) +internal fun throwEmptyArrayOfTableInMap(): Nothing { + throw EmptyArrayOfTableInMapException() +} // -------- Decoding -------- -internal sealed class TomlDecodingException : SerializationException { - constructor() - constructor(message: String) : super(message) +internal sealed class TomlDecodingException(message: String) : SerializationException(message) + +// ---- UnexpectedTokenException ---- + +internal class UnexpectedTokenException(message: String) : TomlDecodingException(message) + +internal fun throwUnexpectedToken(token: Char, line: Int): Nothing { + val tokenRepresentation = if (token != '\'') token.escape() else "\\'" + val message = "'$tokenRepresentation' (L$line)" + throw UnexpectedTokenException(message) } -internal class UnexpectedTokenException(token: Char, line: Int) : TomlDecodingException( - message = run { - val tokenRepresentation = if (token != '\'') token.escape() else "\\'" - "'$tokenRepresentation' (L$line)" +// ---- IncompleteException ---- + +internal class IncompleteException(message: String) : TomlDecodingException(message) + +internal fun throwIncomplete(line: Int): Nothing { + throw IncompleteException("(L$line)") +} + +// ---- ConflictEntryException ---- + +internal class ConflictEntryException(message: String) : TomlDecodingException(message) + +internal fun throwConflictEntry(path: Path): Nothing { + val message = path.joinToString(".") { segment -> + segment.escape().doubleQuotedIfNotPure() } -) + throw ConflictEntryException(message) +} -internal class IncompleteException(line: Int) : TomlDecodingException( - message = "(L$line)" -) +// ---- UnknownKeyException ---- -internal class ConflictEntryException(path: Path) : TomlDecodingException( - message = path.joinToString(".") { it.escape().doubleQuotedIfNotPure() } -) +internal class UnknownKeyException(message: String) : TomlDecodingException(message) -internal class UnknownKeyException(key: String) : TomlDecodingException( - message = key -) +internal fun throwUnknownKey(key: String): Nothing { + throw UnknownKeyException(key) +} diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/UnsignedUtils.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/UnsignedUtils.kt new file mode 100644 index 0000000..38cd807 --- /dev/null +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/UnsignedUtils.kt @@ -0,0 +1,30 @@ +/* + Copyright 2023 Peanuuutz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +package net.peanuuutz.tomlkt.internal + +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor + +private val UnsignedIntegerDescriptors: Set = setOf( + UByte.serializer().descriptor, + UShort.serializer().descriptor, + UInt.serializer().descriptor, + ULong.serializer().descriptor +) + +internal val SerialDescriptor.isUnsignedInteger: Boolean + get() = this.isInline && this in UnsignedIntegerDescriptors diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt index e26d693..4e7f834 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TomlFileParser.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -35,11 +35,11 @@ import net.peanuuutz.tomlkt.internal.DefiniteNumberConstraints import net.peanuuutz.tomlkt.internal.EndArray import net.peanuuutz.tomlkt.internal.EndTable import net.peanuuutz.tomlkt.internal.HexadecimalConstraints -import net.peanuuutz.tomlkt.internal.IncompleteException import net.peanuuutz.tomlkt.internal.KeyValueDelimiter import net.peanuuutz.tomlkt.internal.StartArray import net.peanuuutz.tomlkt.internal.StartTable -import net.peanuuutz.tomlkt.internal.UnexpectedTokenException +import net.peanuuutz.tomlkt.internal.throwIncomplete +import net.peanuuutz.tomlkt.internal.throwUnexpectedToken import net.peanuuutz.tomlkt.internal.toNumber import net.peanuuutz.tomlkt.internal.unescape import kotlin.contracts.InvocationKind @@ -80,7 +80,7 @@ internal class TomlFileParser(private val source: String) { } private fun throwIncomplete(): Nothing { - throw IncompleteException(currentLineNumber) + throwIncomplete(currentLineNumber) } private inline fun throwIncompleteIf(predicate: () -> Boolean) { @@ -92,7 +92,7 @@ internal class TomlFileParser(private val source: String) { } private fun throwUnexpectedToken(token: Char): Nothing { - throw UnexpectedTokenException(token, currentLineNumber) + throwUnexpectedToken(token, currentLineNumber) } private inline fun throwUnexpectedTokenIf(token: Char, predicate: (Char) -> Boolean) { @@ -580,7 +580,8 @@ internal class TomlFileParser(private val source: String) { } } currentIndex-- - val number = builder.toString().toNumber( + val result = builder.toString() + val number = result.toNumber( positive = sign != '-', radix = radix, isDouble = isDouble, @@ -716,17 +717,14 @@ internal class TomlFileParser(private val source: String) { } '"' -> { if (!multiline) { - if (getChar(-1) != '\\') { - justEnded = true - break - } - } else { - throwIncompleteIf { isEof() } - if (getChar(1) == '"' && getChar(2) == '"') { - currentIndex += 2 - justEnded = true - break - } + justEnded = true + break + } + throwIncompleteIf { isEof() } + if (getChar(1) == '"' && getChar(2) == '"') { + currentIndex += 2 + justEnded = true + break } builder.append(current) } @@ -738,7 +736,7 @@ internal class TomlFileParser(private val source: String) { throwUnexpectedTokenIf(current) { !multiline } trim = true } - 'u', 'b', 't', 'n', 'f', 'r', '"', '\\' -> { + 'n', '"', '\\', 'u', 'U', 't', 'r', 'b', 'f' -> { builder.append(current).append(next) currentIndex++ } @@ -755,7 +753,8 @@ internal class TomlFileParser(private val source: String) { justStarted = true } throwIncompleteIf { !justEnded } - val content = builder.toString().unescape() + val result = builder.toString() + val content = result.unescape() return TomlLiteral(content) } @@ -817,8 +816,8 @@ internal class TomlFileParser(private val source: String) { justStarted = true } throwIncompleteIf { !justEnded } - val content = builder.toString() - return TomlLiteral(content) + val result = builder.toString() + return TomlLiteral(result) } // Start right on '[', end on ']'. diff --git a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TreeNode.kt b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TreeNode.kt index 9bae76f..9ae2f71 100644 --- a/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TreeNode.kt +++ b/core/src/commonMain/kotlin/net/peanuuutz/tomlkt/internal/parser/TreeNode.kt @@ -1,5 +1,5 @@ /* - Copyright 2022 Peanuuutz + Copyright 2023 Peanuuutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package net.peanuuutz.tomlkt.internal.parser import net.peanuuutz.tomlkt.TomlElement -import net.peanuuutz.tomlkt.internal.ConflictEntryException +import net.peanuuutz.tomlkt.internal.throwConflictEntry internal typealias Path = List @@ -78,7 +78,7 @@ private tailrec fun KeyNode.addByPathRecursively( val child = get(path[index]) if (index == path.lastIndex) { if (child != null) { - throw ConflictEntryException(path) + throwConflictEntry(path) } add(node) } else { @@ -99,7 +99,7 @@ private tailrec fun KeyNode.addByPathRecursively( grandChild.addByPathRecursively(path, node, arrayOfTableIndices, index + 1) } is ValueNode -> { - throw ConflictEntryException(path) + throwConflictEntry(path) } } } diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ArrayOfTableTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ArrayOfTableTest.kt index 616eb23..c186394 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ArrayOfTableTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ArrayOfTableTest.kt @@ -32,15 +32,35 @@ class ArrayOfTableTest { """.trimIndent() @Test - fun encodeClassElement() { + fun encodeCollectionLikeWithClassElement() { testEncode(M1.serializer(), m11, s11) } @Test - fun decodeClassElement() { + fun decodeCollectionLikeWithClassElement() { testDecode(M1.serializer(), s11, m11) } + val m12 = M1( + b = false, + cs = emptyList() + ) + + val s12 = """ + b = false + cs = [ ] + """.trimIndent() + + @Test + fun encodeEmptyCollectionLikeWithClassElement() { + testEncode(M1.serializer(), m12, s12) + } + + @Test + fun decodeEmptyCollectionLikeWithClassElement() { + testDecode(M1.serializer(), s12, m12) + } + @Serializable data class M2( val cs: List, @@ -64,12 +84,12 @@ class ArrayOfTableTest { """.trimIndent() @Test - fun encodeClassElementUnstructured() { + fun encodeCollectionLikeWithClassElementUnstructured() { testEncode(M2.serializer(), m21, s21) } @Test - fun decodeClassElementUnstructured() { + fun decodeCollectionLikeWithClassElementUnstructured() { testDecode(M2.serializer(), s21, m21) } @@ -79,17 +99,160 @@ class ArrayOfTableTest { """.trimIndent() @Test - fun decodeClassElementInline() { + fun decodeCollectionLikeWithClassElementInline() { testDecode(M2.serializer(), s22, m21) } + val m22 = M2( + cs = emptyList(), + b = false + ) + + val s23 = """ + cs = [ ] + b = false + """.trimIndent() + + @Test + fun encodeEmptyCollectionLikeWithClassElementUnstructured() { + testEncode(M2.serializer(), m22, s23) + } + + @Test + fun decodeEmptyCollectionLikeWithClassElementUnstructured() { + testDecode(M2.serializer(), s23, m22) + } + @Serializable data class M3( + val s: String, + val cs: List + ) + + @Serializable + class C2 { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is C2) return false + return true + } + + override fun hashCode(): Int { + return this::class.hashCode() + } + } + + val m31 = M3( + s = "title", + cs = listOf( + C2() + ) + ) + + val s31 = """ + s = "title" + + [[cs]] + + """.trimIndent() + + @Test + fun encodeCollectionLikeWithEmptyClassElement() { + testEncode(M3.serializer(), m31, s31) + } + + @Test + fun decodeCollectionLikeWithEmptyClassElement() { + testDecode(M3.serializer(), s31, m31) + } + + val m32 = M3( + s = "empty", + cs = emptyList() + ) + + val s32 = """ + s = "empty" + cs = [ ] + """.trimIndent() + + @Test + fun encodeEmptyCollectionLikeWithEmptyClassElement() { + testEncode(M3.serializer(), m32, s32) + } + + @Test + fun decodeEmptyCollectionLikeWithEmptyClassElement() { + testDecode(M3.serializer(), s32, m32) + } + + @Serializable + data class M4( + val cs: List, + val b: Boolean + ) + + val m41 = M4( + cs = listOf( + C2() + ), + b = true + ) + + val s41 = """ + cs = [ + { } + ] + b = true + """.trimIndent() + + @Test + fun encodeCollectionLikeWithEmptyClassElementUnstructured() { + testEncode(M4.serializer(), m41, s41) + } + + @Test + fun decodeCollectionLikeWithEmptyClassElementUnstructured() { + testDecode(M4.serializer(), s41, m41) + } + + val s42 = """ + cs = [ { } ] + b = true + """.trimIndent() + + @Test + fun decodeCollectionLikeWithEmptyClassElementInline() { + testDecode(M4.serializer(), s42, m41) + } + + val m42 = M4( + cs = emptyList(), + b = false + ) + + val s43 = """ + cs = [ ] + b = false + """.trimIndent() + + @Test + fun encodeEmptyCollectionLikeWithEmptyClassElementUnstructured() { + testEncode(M4.serializer(), m42, s43) + } + + @Test + fun decodeEmptyCollectionLikeWithEmptyClassElementUnstructured() { + testDecode(M4.serializer(), s43, m42) + } + + @Serializable + data class M5( val i: Int, val ms: List> ) - val m31 = M3( + val m51 = M5( i = 0, ms = listOf( mapOf( @@ -98,7 +261,7 @@ class ArrayOfTableTest { ) ) - val s31 = """ + val s51 = """ i = 0 [[ms]] @@ -106,22 +269,66 @@ class ArrayOfTableTest { """.trimIndent() @Test - fun encodeMapLikeElement() { - testEncode(M3.serializer(), m31, s31) + fun encodeCollectionLikeWithMapLikeElement() { + testEncode(M5.serializer(), m51, s51) } @Test - fun decodeMapLikeElement() { - testDecode(M3.serializer(), s31, m31) + fun decodeCollectionLikeWithMapLikeElement() { + testDecode(M5.serializer(), s51, m51) + } + + val m52 = M5( + i = 0, + ms = listOf( + emptyMap() + ) + ) + + val s52 = """ + i = 0 + + [[ms]] + + """.trimIndent() + + @Test + fun encodeCollectionLikeWithEmptyMapLikeElement() { + testEncode(M5.serializer(), m52, s52) + } + + @Test + fun decodeCollectionLikeWithEmptyMapLikeElement() { + testDecode(M5.serializer(), s52, m52) + } + + val m53 = M5( + i = 0, + ms = emptyList() + ) + + val s53 = """ + i = 0 + ms = [ ] + """.trimIndent() + + @Test + fun encodeEmptyCollectionLikeWithMapLikeElement() { + testEncode(M5.serializer(), m53, s53) + } + + @Test + fun decodeEmptyCollectionLikeWithMapLikeElement() { + testDecode(M5.serializer(), s53, m53) } @Serializable - data class M4( + data class M6( val ms: List>, val i: Int ) - val m41 = M4( + val m61 = M6( ms = listOf( mapOf( "0" to "0" @@ -130,7 +337,7 @@ class ArrayOfTableTest { i = 0 ) - val s41 = """ + val s61 = """ ms = [ { 0 = "0" } ] @@ -138,22 +345,76 @@ class ArrayOfTableTest { """.trimIndent() @Test - fun encodeMapLikeElementUnstructured() { - testEncode(M4.serializer(), m41, s41) + fun encodeCollectionLikeWithMapLikeElementUnstructured() { + testEncode(M6.serializer(), m61, s61) } @Test - fun decodeMapLikeElementUnstructured() { - testDecode(M4.serializer(), s41, m41) + fun decodeCollectionLikeWithMapLikeElementUnstructured() { + testDecode(M6.serializer(), s61, m61) } - val s42 = """ + val s62 = """ ms = [ { 0 = "0" } ] i = 0 """.trimIndent() @Test - fun decodeMapLikeElementInline() { - testDecode(M4.serializer(), s42, m41) + fun decodeCollectionLikeWithMapLikeElementInline() { + testDecode(M6.serializer(), s62, m61) + } + + val m62 = M6( + ms = listOf( + emptyMap() + ), + i = 0 + ) + + val s63 = """ + ms = [ + { } + ] + i = 0 + """.trimIndent() + + @Test + fun encodeCollectionLikeWithEmptyMapLikeElementUnstructured() { + testEncode(M6.serializer(), m62, s63) + } + + @Test + fun decodeCollectionLikeWithEmptyMapLikeElementUnstructured() { + testDecode(M6.serializer(), s63, m62) + } + + val s64 = """ + ms = [ { } ] + i = 0 + """.trimIndent() + + @Test + fun decodeCollectionLikeWithEmptyMapLikeElementInline() { + testDecode(M6.serializer(), s64, m62) + } + + val m63 = M6( + ms = emptyList(), + i = 1 + ) + + val s65 = """ + ms = [ ] + i = 1 + """.trimIndent() + + @Test + fun encodeEmptyCollectionLikeWithMapLikeElementUnstructured() { + testEncode(M6.serializer(), m63, s65) + } + + @Test + fun decodeEmptyCollectionLikeWithMapLikeElementUnstructured() { + testDecode(M6.serializer(), s65, m63) } } diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ArrayTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ArrayTest.kt index 54e6ff1..aef9c45 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ArrayTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ArrayTest.kt @@ -11,32 +11,32 @@ class ArrayTest { val m11 = M1( fs = listOf( - 0.0f + 0.1f ) ) val s11 = """ fs = [ - 0.0 + 0.1 ] """.trimIndent() @Test - fun encodePrimitiveElement() { + fun encodeCollectionLikeWithPrimitiveElement() { testEncode(M1.serializer(), m11, s11) } @Test - fun decodePrimitiveElement() { + fun decodeCollectionLikeWithPrimitiveElement() { testDecode(M1.serializer(), s11, m11) } val s12 = """ - fs = [ 0.0 ] + fs = [ 0.1 ] """.trimIndent() @Test - fun decodePrimitiveElementInline() { + fun decodeCollectionLikeWithPrimitiveElementInline() { testDecode(M1.serializer(), s12, m11) } @@ -49,23 +49,21 @@ class ArrayTest { """.trimIndent() @Test - fun encodeEmptyPrimitiveElement() { + fun encodeEmptyCollectionLikeWithPrimitiveElement() { testEncode(M1.serializer(), m12, s13) } @Test - fun decodeEmptyPrimitiveElement() { + fun decodeEmptyCollectionLikeWithPrimitiveElement() { testDecode(M1.serializer(), s13, m12) } @Serializable data class M2( - val d: Double, val ls: List> ) val m21 = M2( - d = 0.0, ls = listOf( listOf( "0" @@ -74,49 +72,133 @@ class ArrayTest { ) val s21 = """ - d = 0.0 ls = [ [ "0" ] ] """.trimIndent() @Test - fun encodeCollectionLikeElement() { + fun encodeCollectionLikeWithCollectionLikeElement() { testEncode(M2.serializer(), m21, s21) } @Test - fun decodeCollectionLikeElement() { + fun decodeCollectionLikeWithCollectionLikeElement() { testDecode(M2.serializer(), s21, m21) } val s22 = """ - d = 0.0 ls = [ [ "0" ] ] """.trimIndent() @Test - fun decodeCollectionLikeElementInline() { + fun decodeCollectionLikeWithCollectionLikeElementInline() { testDecode(M2.serializer(), s22, m21) } val m22 = M2( - d = -3.14, - ls = listOf() + ls = listOf( + emptyList() + ) ) val s23 = """ - d = -3.14 - ls = [ ] + ls = [ + [ ] + ] """.trimIndent() @Test - fun encodeEmptyCollectionLikeElement() { + fun encodeCollectionLikeWithEmptyCollectionLikeElement() { testEncode(M2.serializer(), m22, s23) } @Test - fun decodeEmptyCollectionLikeElement() { + fun decodeCollectionLikeWithEmptyCollectionLikeElement() { testDecode(M2.serializer(), s23, m22) } + + val s24 = """ + ls = [ [ ] ] + """.trimIndent() + + @Test + fun decodeCollectionLikeWithEmptyCollectionLikeElementInline() { + testDecode(M2.serializer(), s24, m22) + } + + val m23 = M2( + ls = emptyList() + ) + + val s25 = """ + ls = [ ] + """.trimIndent() + + @Test + fun encodeEmptyCollectionLikeWithCollectionLikeElement() { + testEncode(M2.serializer(), m23, s25) + } + + @Test + fun decodeEmptyCollectionLikeWithCollectionLikeElement() { + testDecode(M2.serializer(), s25, m23) + } + + @Serializable + class M3( + @TomlInline + val fs: FloatArray + ) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is M3) return false + if (!fs.contentEquals(other.fs)) return false + return true + } + + override fun hashCode(): Int { + return fs.contentHashCode() + } + + override fun toString(): String { + return "M3(fs=${fs.contentToString()})" + } + } + + val m31 = M3( + fs = floatArrayOf(0.5f) + ) + + val s31 = """ + fs = [ 0.5 ] + """.trimIndent() + + @Test + fun encodeCollectionLikeWithPrimitiveElementInline() { + testEncode(M3.serializer(), m31, s31) + } + + @Serializable + data class M4( + @TomlInline + val ls: List> + ) + + val m41 = M4( + ls = listOf( + listOf( + "Hi" + ) + ) + ) + + val s41 = """ + ls = [ [ "Hi" ] ] + """.trimIndent() + + @Test + fun encodeCollectionLikeWithCollectionLikeElementInline() { + testEncode(M4.serializer(), m41, s41) + } } diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/CommentTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/CommentTest.kt index 946b358..d14349d 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/CommentTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/CommentTest.kt @@ -80,7 +80,7 @@ class CommentTest { val m31 = M3( c = C( - f = 1.0f + f = 1.1f ) ) @@ -89,7 +89,7 @@ class CommentTest { # test [c] - f = 1.0 + f = 1.1 """.trimIndent() @Test @@ -104,7 +104,7 @@ class CommentTest { val s32 = """ [c] # test - f = 1.0 + f = 1.1 """.trimIndent() @Test @@ -121,7 +121,7 @@ class CommentTest { val m41 = M4( cs = listOf( C( - f = 1.0f + f = 1.1f ) ) ) @@ -131,7 +131,7 @@ class CommentTest { # test [[cs]] - f = 1.0 + f = 1.1 """.trimIndent() @Test @@ -146,7 +146,7 @@ class CommentTest { val s42 = """ [[cs]] # test - f = 1.0 + f = 1.1 """.trimIndent() @Test @@ -222,14 +222,14 @@ class CommentTest { val m71 = M7( i = 0, - f = 0.0f + f = 0.1f ) val s71 = """ # text i = 0 # another - f = 0.0 + f = 0.1 """.trimIndent() @Test @@ -253,7 +253,7 @@ class CommentTest { val m81 = M8( i = 0, c = C( - f = 0.0f + f = 0.1f ) ) @@ -264,7 +264,7 @@ class CommentTest { # another [c] - f = 0.0 + f = 0.1 """.trimIndent() @Test diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/CustomSerializerTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/CustomSerializerTest.kt index d58e489..2a24c5c 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/CustomSerializerTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/CustomSerializerTest.kt @@ -26,13 +26,13 @@ class CustomSerializerTest { override fun serialize(encoder: Encoder, value: Any) { when (value) { - is String -> String.serializer().serialize(encoder, value) - is Int -> Int.serializer().serialize(encoder, value) + is String -> encoder.encodeString(value) + is Int -> encoder.encodeInt(value) } } override fun deserialize(decoder: Decoder): Any { - val literal = decoder.asTomlDecoder().decodeTomlElement().toTomlLiteral() + val literal = decoder.asTomlDecoder().decodeTomlElement().asTomlLiteral() return literal.toIntOrNull() ?: literal.content } } @@ -46,20 +46,27 @@ class CustomSerializerTest { """.trimIndent() @Test - fun encodeAnyInClass() { + fun encodeClassWithSerializableProperty() { testEncode(M1.serializer(), m11, s11) } + @Test + fun decodeClassWithSerializableProperty() { + testDecode(M1.serializer(), s11, m11) + } + val m12: Map = mapOf( - "1" to "YES" + "a" to "YES", + "b" to 1 ) val s12 = """ - 1 = "YES" + a = "YES" + b = 1 """.trimIndent() @Test - fun encodeAnyInMap() { + fun encodeMapLikeWithSerializableProperty() { val serializer = MapSerializer( keySerializer = String.serializer(), valueSerializer = StringOrIntSerializer @@ -67,4 +74,14 @@ class CustomSerializerTest { testEncode(serializer, m12, s12) } + + @Test + fun decodeMapLikeWithSerializableProperty() { + val serializer = MapSerializer( + keySerializer = String.serializer(), + valueSerializer = StringOrIntSerializer + ) + + testDecode(serializer, s12, m12) + } } diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/FloatTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/FloatTest.kt index ad241e1..ce49525 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/FloatTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/FloatTest.kt @@ -2,6 +2,8 @@ package net.peanuuutz.tomlkt import kotlinx.serialization.Serializable import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue class FloatTest { @Serializable @@ -17,9 +19,19 @@ class FloatTest { f = 12345.0 """.trimIndent() + val s11a = """ + f = 12345 + """.trimIndent() + @Test fun encodePositive() { - testEncode(M1.serializer(), m11, s11) + val r = Toml.encodeToString(M1.serializer(), m11) + try { + assertEquals(s11, r) + } catch (e: AssertionError) { + // For JS. + assertEquals(s11a, r) + } } @Test @@ -44,9 +56,19 @@ class FloatTest { f = 0.0 """.trimIndent() + val s13a = """ + f = 0 + """.trimIndent() + @Test fun encodeZero() { - testEncode(M1.serializer(), m12, s13) + val r = Toml.encodeToString(M1.serializer(), m12) + try { + assertEquals(s13, r) + } catch (e: AssertionError) { + // For JS. + assertEquals(s13a, r) + } } @Test @@ -73,7 +95,8 @@ class FloatTest { @Test fun decodeZeroWithNegativeSign() { - testDecode(M1.serializer(), s15, m13) + val r = Toml.decodeFromString(M1.serializer(), s15) + assertEquals(m13.f, r.f, 0.0f) } val m14 = M1( @@ -158,7 +181,8 @@ class FloatTest { @Test fun decodeNaN() { - testDecode(M1.serializer(), s113, m15) + val r = Toml.decodeFromString(M1.serializer(), s113) + assertTrue { r.f.isNaN() } } val s114 = """ @@ -167,7 +191,8 @@ class FloatTest { @Test fun decodeNaNWithPositiveSign() { - testDecode(M1.serializer(), s114, m15) + val r = Toml.decodeFromString(M1.serializer(), s114) + assertTrue { r.f.isNaN() } } val s115 = """ @@ -176,7 +201,8 @@ class FloatTest { @Test fun decodeNaNWithNegativeSign() { - testDecode(M1.serializer(), s115, m15) + val r = Toml.decodeFromString(M1.serializer(), s115) + assertTrue { r.f.isNaN() } } val m16 = M1( diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/KeyTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/KeyTest.kt index 9f29fc2..17f7d7e 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/KeyTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/KeyTest.kt @@ -345,7 +345,7 @@ class KeyTest { val m131 = M13( c = C4( i = 0, - f = 0.0f + f = 0.1f ), b = true ) @@ -353,7 +353,7 @@ class KeyTest { val s131 = """ c.i = 0 b = true - c.f = 0.0 + c.f = 0.1 """.trimIndent() @Test diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/NestTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/NestTest.kt new file mode 100644 index 0000000..a064849 --- /dev/null +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/NestTest.kt @@ -0,0 +1,336 @@ +package net.peanuuutz.tomlkt + +import kotlinx.serialization.Serializable +import kotlin.test.Test + +class NestTest { + @Serializable + data class M1( + val s: String, + val clm: Map> + ) + + @Serializable + data class C1( + val s: String + ) + + val m11 = M1( + s = "1", + clm = mapOf( + "1" to listOf( + C1( + s = "1" + ) + ) + ) + ) + + val s11 = """ + s = "1" + + [clm] + + [[clm.1]] + s = "1" + """.trimIndent() + + @Test + fun encodeDeeplyNested1() { + testEncode(M1.serializer(), m11, s11) + } + + @Test + fun decodeDeeplyNested1() { + testDecode(M1.serializer(), s11, m11) + } + + val m12 = M1( + s = "1", + clm = mapOf( + "1" to emptyList() + ) + ) + + val s12 = """ + s = "1" + + [clm] + 1 = [ ] + """.trimIndent() + + @Test + fun encodeDeeplyNested2() { + testEncode(M1.serializer(), m12, s12) + } + + @Test + fun decodeDeeplyNested2() { + testDecode(M1.serializer(), s12, m12) + } + + val m13 = M1( + s = "1", + clm = emptyMap() + ) + + val s13 = """ + s = "1" + + [clm] + + """.trimIndent() + + @Test + fun encodeDeeplyNested3() { + testEncode(M1.serializer(), m13, s13) + } + + @Test + fun decodeDeeplyNested3() { + testDecode(M1.serializer(), s13, m13) + } + + @Serializable + data class M2( + val clm: Map>, + val s: String + ) + + val m21 = M2( + clm = mapOf( + "1" to listOf( + C1( + s = "1" + ) + ) + ), + s = "1" + ) + + val s21 = """ + clm.1 = [ + { s = "1" } + ] + s = "1" + """.trimIndent() + + @Test + fun encodeDeeplyNested4() { + testEncode(M2.serializer(), m21, s21) + } + + @Test + fun decodeDeeplyNested4() { + testDecode(M2.serializer(), s21, m21) + } + + val m22 = M2( + clm = mapOf( + "1" to emptyList() + ), + s = "1" + ) + + val s22 = """ + clm.1 = [ ] + s = "1" + """.trimIndent() + + @Test + fun encodeDeeplyNested5() { + testEncode(M2.serializer(), m22, s22) + } + + @Test + fun decodeDeeplyNested5() { + testDecode(M2.serializer(), s22, m22) + } + + val m23 = M2( + clm = emptyMap(), + s = "1" + ) + + val s23 = """ + clm = { } + s = "1" + """.trimIndent() + + @Test + fun encodeDeeplyNested6() { + testEncode(M2.serializer(), m23, s23) + } + + @Test + fun decodeDeeplyNested6() { + testDecode(M2.serializer(), s23, m23) + } + + @Serializable + data class M3( + val s: String, + val clm: Map> + ) + + @Serializable + class C2 { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is C2) return false + return true + } + + override fun hashCode(): Int { + return this::class.hashCode() + } + } + + val m31 = M3( + s = "A", + clm = mapOf( + "O" to listOf( + C2() + ) + ) + ) + + val s31 = """ + s = "A" + + [clm] + + [[clm.O]] + + """.trimIndent() + + @Test + fun encodeDeeplyNested7() { + testEncode(M3.serializer(), m31, s31) + } + + @Test + fun decodeDeeplyNested7() { + testDecode(M3.serializer(), s31, m31) + } + + val m32 = M3( + s = "A", + clm = mapOf( + "O" to emptyList() + ) + ) + + val s32 = """ + s = "A" + + [clm] + O = [ ] + """.trimIndent() + + @Test + fun encodeDeeplyNested8() { + testEncode(M3.serializer(), m32, s32) + } + + @Test + fun decodeDeeplyNested8() { + testDecode(M3.serializer(), s32, m32) + } + + val m33 = M3( + s = "A", + clm = emptyMap() + ) + + val s33 = """ + s = "A" + + [clm] + + """.trimIndent() + + @Test + fun encodeDeeplyNested9() { + testEncode(M3.serializer(), m33, s33) + } + + @Test + fun decodeDeeplyNested9() { + testDecode(M3.serializer(), s33, m33) + } + + @Serializable + data class M4( + val clm: Map>, + val s: String + ) + + val m41 = M4( + clm = mapOf( + "1" to listOf( + C2() + ) + ), + s = "" + ) + + val s41 = """ + clm.1 = [ + { } + ] + s = "" + """.trimIndent() + + @Test + fun encodeDeeplyNested10() { + testEncode(M4.serializer(), m41, s41) + } + + @Test + fun decodeDeeplyNested10() { + testDecode(M4.serializer(), s41, m41) + } + + val m42 = M4( + clm = mapOf( + "1" to emptyList() + ), + s = "" + ) + + val s42 = """ + clm.1 = [ ] + s = "" + """.trimIndent() + + @Test + fun encodeDeeplyNested11() { + testEncode(M4.serializer(), m42, s42) + } + + @Test + fun decodeDeeplyNested11() { + testDecode(M4.serializer(), s42, m42) + } + + val m43 = M4( + clm = emptyMap(), + s = "" + ) + + val s43 = """ + clm = { } + s = "" + """.trimIndent() + + @Test + fun encodeDeeplyNested12() { + testEncode(M4.serializer(), m43, s43) + } + + @Test + fun decodeDeeplyNested12() { + testDecode(M4.serializer(), s43, m43) + } +} diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/NullTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/NullTest.kt index d841d1f..dadf3dc 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/NullTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/NullTest.kt @@ -18,12 +18,12 @@ class NullTest { """.trimIndent() @Test - fun encode() { + fun encodeRegularly() { testEncode(M1.serializer(), m11, s11) } @Test - fun decode() { + fun decodeRegularly() { testDecode(M1.serializer(), s11, m11) } diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/StringTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/StringTest.kt index b93da75..248cafb 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/StringTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/StringTest.kt @@ -1,5 +1,246 @@ package net.peanuuutz.tomlkt +import kotlinx.serialization.Serializable +import kotlin.test.Test + class StringTest { + @Serializable + data class M1( + val s: String + ) + + val m11 = M1( + s = "beam" + ) + + val s11 = """ + s = "beam" + """.trimIndent() + + @Test + fun encodeRegularly() { + testEncode(M1.serializer(), m11, s11) + } + + @Test + fun decodeRegularly() { + testDecode(M1.serializer(), s11, m11) + } + + val m12 = M1( + s = "'a'" + ) + + val s12 = """ + s = "'a'" + """.trimIndent() + + @Test + fun encodeSingleQuote() { + testEncode(M1.serializer(), m12, s12) + } + + @Test + fun decodeSingleQuote() { + testDecode(M1.serializer(), s12, m12) + } + + val m13 = M1( + s = "\b \t \n " + 12.toChar() + " \r \" \\" + ) + + val s13 = """ + s = "\b \t \n \f \r \" \\" + """.trimIndent() + + @Test + fun encodePopularEscapedChars() { + testEncode(M1.serializer(), m13, s13) + } + + @Test + fun decodePopularEscapedChars() { + testDecode(M1.serializer(), s13, m13) + } + + val m14 = M1( + s = "\u539f\u795e\uff0c\u542f\u52a8\uff01" + ) + + val s14 = """ + s = "\u539f\u795e\uff0c\u542f\u52a8\uff01" + """.trimIndent() + + @Test + fun decodeUnicode() { + testDecode(M1.serializer(), s14, m14) + } + + @Serializable + data class M2( + @TomlMultilineString + val s: String + ) + + val m21 = M2( + s = """ + R + G + B + """.trimIndent() + ) + + val s21 = """ + s = ""${'"'} + R + G + B""${'"'} + """.trimIndent() + + @Test + fun encodeMultiline() { + testEncode(M2.serializer(), m21, s21) + } + + @Test + fun decodeMultiline() { + testDecode(M2.serializer(), s21, m21) + } + + val s22 = """ + s = ""${'"'} + R + \ + + G + \ + B""${'"'} + """.trimIndent() + + @Test + fun decodeTrimmer() { + testDecode(M2.serializer(), s22, m21) + } + + val m22 = M2( + s = """ + R \ + G + B + """.trimIndent() + ) + + val s23 = """ + s = ""${'"'} + R \\ + G + B""${'"'} + """.trimIndent() + + @Test + fun encodeMultilineWithBackslash() { + testEncode(M2.serializer(), m22, s23) + } + + @Test + fun decodeMultilineWithBackslash() { + testDecode(M2.serializer(), s23, m22) + } + + val m23 = M2( + s = """Here are two quotation marks: "".""" + ) + + val s24 = """ + s = ""${'"'}Here are two quotation marks: "".""${'"'} + """.trimIndent() + + @Test + fun decodeManyDoubleQuote1() { + testDecode(M2.serializer(), s24, m23) + } + + val m24 = M2( + s = """Here are nine quotation marks: ""${'"'}""${'"'}""${'"'}.""" + ) + + val s25 = """ + s = ""${'"'}Here are nine quotation marks: ""\""${'"'}\""${'"'}\".""${'"'} + """.trimIndent() + + @Test + fun decodeManyDoubleQuote2() { + testDecode(M2.serializer(), s25, m24) + } + + val m25 = M2( + s = """"This," she said, "is just a pointless statement."""" + ) + + val s26 = """ + s = ""${'"'}"This," she said, "is just a pointless statement.\""${'"'}" + """.trimIndent() + + @Test + fun decodeManyDoubleQuote3() { + testDecode(M2.serializer(), s26, m25) + } + + @Serializable + data class M3( + @TomlLiteralString + val s: String + ) + + val m31 = M3( + s = """<\i\c*\s*>""" + ) + + val s31 = """ + s = '<\i\c*\s*>' + """.trimIndent() + + @Test + fun encodeLiteral() { + testEncode(M3.serializer(), m31, s31) + } + + @Test + fun decodeLiteral() { + testDecode(M3.serializer(), s31, m31) + } + + @Serializable + data class M4( + @TomlMultilineString + @TomlLiteralString + val s: String + ) + + val m41 = M4( + s = """ + DON'T! + 1 + 2 + + """.trimIndent() + ) + + val s41 = """ + s = ''' + DON'T! + 1 + 2 + ''' + """.trimIndent() + + @Test + fun encodeMultilineLiteral() { + testEncode(M4.serializer(), m41, s41) + } + @Test + fun decodeMultilineLiteral() { + testDecode(M4.serializer(), s41, m41) + } } diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TableTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TableTest.kt index 8770da5..94ee09d 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TableTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TableTest.kt @@ -3,6 +3,117 @@ package net.peanuuutz.tomlkt import kotlinx.serialization.Serializable import kotlin.test.Test +/* + [1] + elements = [ "p", "c" ] + tests = [ + { type: "n", format: "[c]" } + ] + + [2] + elements = [ "c", "p" ] + tests = [ + { type: "n", format: "c.k = v" }, + { type: "i", format: "c = v" } + ] + + [3] + elements = [ "p", "ec" ] + tests = [ + { type: "n", format: "[c]" }, + { type: "i", format: "c = v" } + ] + + [4] + elements = [ "p", "pm" ] + tests = [ + { type: "n", format: "[pm]" }, + { type: "e", format: "[pm]" } + ] + + [5] + elements = [ "pm", "p" ] + tests = [ + { type: "n", format: "pm.k = v" }, + { type: "i", format: "pm = v" }, + { type: "e", format: "pm = v" } + ] + + [6] + elements = [ "p", "cm" ] + tests = [ + { type: "n", format: [ "[cm.k]", "k = v" ] ] }, + { type: "i", format: [ "[cm]", "k = v" ] ] }, + { type: "em", format: "[cm]" }, + { type: "s", description: "omit super table" } + ] + + [7] + elements = [ "cm", "p" ] + tests = [ + { type: "n", format: "cm.k.k = v" }, + { type: "i", format: "cm.k = v" }, + { type: "i", format: "cm = v" }, + { type: "em", format: "cm = v" } + ] + + [8] + elements = [ "p", "ecm" ] + tests = [ + { type: "n", format: "[cm.k]" }, + { type: "i", format: [ "[cm]", "k = v" ] }, + { type: "em", format: "[cm]" } + ] + + [9] + elements = [ "ecm", "p" ] + tests = [ + { type: "n", format: "cm.k = v" }, + { type: "i", format: "cm = v" }, + { type: "em", format: "cm = v" } + ] + + [10] + elements = [ "p", "lm" ] + tests = [ + { type: "n", format: [ "[lm]", "k = v" ] }, + { type: "i", format: [ "[lm]", "k = v" ] }, + { type: "el", format: [ "[lm]", "k = v" ] }, + { type: "em", format: "[lm]" } + ] + + [11] + elements = [ "lm", "p" ] + tests = [ + { type: "n", format: "lm.k = v" }, + { type: "i", format: "lm.k = v" }, + { type: "i", format: "lm = v" }, + { type: "el", format: "lm.k = v" }, + { type: [ "el", "i" ], format: "lm = v" }, + { type: "em", format: "lm = v" } + ] + + [12] + elements = [ "p", "mm" ] + tests = [ + { type: "n", format: [ "[mm.k]", "k = v" ] }, + { type: "i", format: [ "[mm]", "k = v" ] ] }, + { type: "eim", format: "[mm.k]" }, + { type: [ "eim", 'i' ], format: [ "[mm]", "k = v" ] }, + { type: "eom", format: "[mm]" } + ] + + [13] + elements = [ "mm", "p" ] + tests = [ + { type: "n", format: "mm.k.k = v" }, + { type: "i", format: "mm.k = v" }, + { type: "i", format: "mm = v" }, + { type: "eim", format: "mm.k = v" }, + { type: [ "eim", "i" ], format: "mm = v" }, + { type: "eom", format: "mm = v" } + ] + */ class TableTest { @Serializable data class M1( @@ -79,18 +190,69 @@ class TableTest { @Serializable data class M3( + val b: Boolean, + val c: C2 + ) + + @Serializable + class C2 { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is C2) return false + return true + } + + override fun hashCode(): Int { + return this::class.hashCode() + } + } + + val m31 = M3( + b = true, + c = C2() + ) + + val s31 = """ + b = true + + [c] + + """.trimIndent() + + @Test + fun encodeEmptyClass() { + testEncode(M3.serializer(), m31, s31) + } + + @Test + fun decodeEmptyClass() { + testDecode(M3.serializer(), s31, m31) + } + + val s32 = """ + b = true + c = { } + """.trimIndent() + + @Test + fun decodeEmptyClassInline() { + testDecode(M3.serializer(), s32, m31) + } + + @Serializable + data class M4( val s: String, val sm: Map ) - val m31 = M3( + val m41 = M4( s = "", sm = mapOf( "a" to "" ) ) - val s31 = """ + val s41 = """ s = "" [sm] @@ -99,59 +261,101 @@ class TableTest { @Test fun encodeMapLikeWithPrimitiveValue() { - testEncode(M3.serializer(), m31, s31) + testEncode(M4.serializer(), m41, s41) } @Test fun decodeMapLikeWithPrimitiveValue() { - testDecode(M3.serializer(), s31, m31) + testDecode(M4.serializer(), s41, m41) + } + + val m42 = M4( + s = "1", + sm = emptyMap() + ) + + val s42 = """ + s = "1" + + [sm] + + """.trimIndent() + + @Test + fun encodeEmptyMapLikeWithPrimitiveValue() { + testEncode(M4.serializer(), m42, s42) + } + + @Test + fun decodeEmptyMapLikeWithPrimitiveValue() { + testDecode(M4.serializer(), s42, m42) } @Serializable - data class M4( + data class M5( val sm: Map, val s: String ) - val m41 = M4( + val m51 = M5( sm = mapOf( "a" to "" ), s = "" ) - val s41 = """ + val s51 = """ sm.a = "" s = "" """.trimIndent() @Test fun encodeMapLikeWithPrimitiveValueUnstructured() { - testEncode(M4.serializer(), m41, s41) + testEncode(M5.serializer(), m51, s51) } @Test fun decodeMapLikeWithPrimitiveValueUnstructured() { - testDecode(M4.serializer(), s41, m41) + testDecode(M5.serializer(), s51, m51) } - val s42 = """ + val s52 = """ sm = { a = "" } s = "" """.trimIndent() @Test fun decodeMapLikeWithPrimitiveValueInline() { - testDecode(M4.serializer(), s42, m41) + testDecode(M5.serializer(), s52, m51) + } + + val m52 = M5( + sm = emptyMap(), + s = "A" + ) + + val s53 = """ + sm = { } + s = "A" + """.trimIndent() + + @Test + fun encodeEmptyMapLikeWithPrimitiveValueUnstructured() { + testEncode(M5.serializer(), m52, s53) + } + + @Test + fun decodeEmptyMapLikeWithPrimitiveValueUnstructured() { + testDecode(M5.serializer(), s53, m52) } @Serializable - data class M5( + data class M6( val s: String, val cm: Map ) - val m51 = M5( + val m61 = M6( s = "", cm = mapOf( "1" to C1( @@ -160,7 +364,7 @@ class TableTest { ) ) - val s51 = """ + val s61 = """ s = "" [cm] @@ -171,15 +375,49 @@ class TableTest { @Test fun encodeMapLikeWithClassValue() { - testEncode(M5.serializer(), m51, s51) + testEncode(M6.serializer(), m61, s61) } @Test fun decodeMapLikeWithClassValue() { - testDecode(M5.serializer(), s51, m51) + testDecode(M6.serializer(), s61, m61) } - val s52 = """ + val s62 = """ + s = "" + + [cm] + 1 = { s = "1" } + """.trimIndent() + + @Test + fun decodeMapLikeWithClassValueInline1() { + testDecode(M6.serializer(), s62, m61) + } + + val m62 = M6( + s = "", + cm = emptyMap() + ) + + val s63 = """ + s = "" + + [cm] + + """.trimIndent() + + @Test + fun encodeEmptyMapLikeWithClassValue() { + testEncode(M6.serializer(), m62, s63) + } + + @Test + fun decodeEmptyMapLikeWithClassValue() { + testDecode(M6.serializer(), s63, m62) + } + + val s64 = """ s = "" [cm.1] @@ -188,16 +426,16 @@ class TableTest { @Test fun decodeWithOmittedSuperTable() { - testDecode(M5.serializer(), s52, m51) + testDecode(M6.serializer(), s64, m61) } @Serializable - data class M6( + data class M7( val cm: Map, val s: String ) - val m61 = M6( + val m71 = M7( cm = mapOf( "1" to C1( s = "1" @@ -206,114 +444,787 @@ class TableTest { s = "" ) - val s61 = """ + val s71 = """ cm.1.s = "1" s = "" """.trimIndent() @Test fun encodeMapLikeWithClassValueUnstructured() { - testEncode(M6.serializer(), m61, s61) + testEncode(M7.serializer(), m71, s71) } @Test fun decodeMapLikeWithClassValueUnstructured() { - testDecode(M6.serializer(), s61, m61) + testDecode(M7.serializer(), s71, m71) } - val s62 = """ + val s72 = """ cm.1 = { s = "1" } s = "" """.trimIndent() @Test - fun decodeMapLikeWithClassValueInline1() { - testDecode(M6.serializer(), s62, m61) + fun decodeMapLikeWithClassValueInline2() { + testDecode(M7.serializer(), s72, m71) } - val s63 = """ + val s73 = """ cm = { 1 = { s = "1" } } s = "" """.trimIndent() @Test - fun decodeMapLikeWithClassValueInline2() { - testDecode(M6.serializer(), s63, m61) + fun decodeMapLikeWithClassValueInline3() { + testDecode(M7.serializer(), s73, m71) + } + + val m72 = M7( + cm = emptyMap(), + s = "" + ) + + val s74 = """ + cm = { } + s = "" + """.trimIndent() + + @Test + fun encodeEmptyMapLikeWithClassValueUnstructured() { + testEncode(M7.serializer(), m72, s74) + } + + @Test + fun decodeEmptyMapLikeWithClassValueUnstructured() { + testDecode(M7.serializer(), s74, m72) } @Serializable - data class M7( + data class M8( val s: String, - val lm: Map> + val cm: Map ) - val m71 = M7( + val m81 = M8( s = "", - lm = mapOf( - "1" to listOf( - "a" - ) + cm = mapOf( + "1" to C2() ) ) - val s71 = """ + val s81 = """ s = "" - [lm] - 1 = [ - "a" - ] + [cm] + + [cm.1] + """.trimIndent() @Test - fun encodeMapLikeWithCollectionLikeValue() { - testEncode(M7.serializer(), m71, s71) + fun encodeMapLikeWithEmptyClassValue() { + testEncode(M8.serializer(), m81, s81) } @Test - fun decodeMapLikeWithCollectionLikeValue() { - testDecode(M7.serializer(), s71, m71) + fun decodeMapLikeWithEmptyClassValue() { + testDecode(M8.serializer(), s81, m81) + } + + val s82 = """ + s = "" + + [cm] + 1 = { } + """.trimIndent() + + @Test + fun decodeMapLikeWithEmptyClassValueInline1() { + testDecode(M8.serializer(), s82, m81) + } + + val m82 = M8( + s = "", + cm = emptyMap() + ) + + val s83 = """ + s = "" + + [cm] + + """.trimIndent() + + @Test + fun encodeEmptyMapLikeWithEmptyClassValue() { + testEncode(M8.serializer(), m82, s83) + } + + @Test + fun decodeEmptyMapLikeWithEmptyClassValue() { + testDecode(M8.serializer(), s83, m82) } @Serializable - data class M8( - val lm: Map>, + data class M9( + val cm: Map, val s: String ) - val m81 = M8( - lm = mapOf( - "0" to listOf( - "1" - ) + val m91 = M9( + cm = mapOf( + "1" to C2() ), s = "" ) - val s81 = """ - lm.0 = [ - "1" - ] + val s91 = """ + cm.1 = { } s = "" """.trimIndent() @Test - fun encodeMapLikeWithCollectionLikeValueUnstructured() { - testEncode(M8.serializer(), m81, s81) + fun encodeMapLikeWithEmptyClassValueUnstructured() { + testEncode(M9.serializer(), m91, s91) } @Test - fun decodeMapLikeWithCollectionLikeValueUnstructured() { - testDecode(M8.serializer(), s81, m81) + fun decodeMapLikeWithEmptyClassValueUnstructured() { + testDecode(M9.serializer(), s91, m91) } - val s82 = """ - lm = { 0 = [ "1" ] } + val s92 = """ + cm = { 1 = { } } s = "" """.trimIndent() @Test - fun decodeMapLikeWithCollectionLikeValueInline() { - testDecode(M8.serializer(), s82, m81) + fun decodeMapLikeWithEmptyClassValueInline2() { + testDecode(M9.serializer(), s92, m91) + } + + val m92 = M9( + cm = emptyMap(), + s = "" + ) + + val s93 = """ + cm = { } + s = "" + """.trimIndent() + + @Test + fun encodeEmptyMapLikeWithEmptyClassValueUnstructured() { + testEncode(M9.serializer(), m92, s93) + } + + @Test + fun decodeEmptyMapLikeWithEmptyClassValueUnstructured() { + testDecode(M9.serializer(), s93, m92) + } + + @Serializable + data class M10( + val s: String, + val lm: Map> + ) + + val m101 = M10( + s = "", + lm = mapOf( + "1" to listOf( + "a" + ) + ) + ) + + val s101 = """ + s = "" + + [lm] + 1 = [ + "a" + ] + """.trimIndent() + + @Test + fun encodeMapLikeWithCollectionLikeValue() { + testEncode(M10.serializer(), m101, s101) + } + + @Test + fun decodeMapLikeWithCollectionLikeValue() { + testDecode(M10.serializer(), s101, m101) + } + + val s102 = """ + s = "" + + [lm] + 1 = [ "a" ] + """.trimIndent() + + @Test + fun decodeMapLikeWithCollectionLikeValueInline1() { + testDecode(M10.serializer(), s102, m101) + } + + val m102 = M10( + s = "", + lm = mapOf( + "1" to emptyList() + ) + ) + + val s103 = """ + s = "" + + [lm] + 1 = [ ] + """.trimIndent() + + @Test + fun encodeMapLikeWithEmptyCollectionLikeValue() { + testEncode(M10.serializer(), m102, s103) + } + + @Test + fun decodeMapLikeWithEmptyCollectionLikeValue() { + testDecode(M10.serializer(), s103, m102) + } + + val m103 = M10( + s = "", + lm = emptyMap() + ) + + val s104 = """ + s = "" + + [lm] + + """.trimIndent() + + @Test + fun encodeEmptyMapLikeWithCollectionLikeValue() { + testEncode(M10.serializer(), m103, s104) + } + + @Test + fun decodeEmptyMapLikeWithCollectionLikeValue() { + testDecode(M10.serializer(), s104, m103) + } + + @Serializable + data class M11( + val lm: Map>, + val s: String + ) + + val m111 = M11( + lm = mapOf( + "0" to listOf( + "1" + ) + ), + s = "" + ) + + val s111 = """ + lm.0 = [ + "1" + ] + s = "" + """.trimIndent() + + @Test + fun encodeMapLikeWithCollectionLikeValueUnstructured() { + testEncode(M11.serializer(), m111, s111) + } + + @Test + fun decodeMapLikeWithCollectionLikeValueUnstructured() { + testDecode(M11.serializer(), s111, m111) + } + + val s112 = """ + lm.0 = [ "1" ] + s = "" + """.trimIndent() + + @Test + fun decodeMapLikeWithCollectionLikeValueInline2() { + testDecode(M11.serializer(), s112, m111) + } + + val s113 = """ + lm = { 0 = [ "1" ] } + s = "" + """.trimIndent() + + @Test + fun decodeMapLikeWithCollectionLikeValueInline3() { + testDecode(M11.serializer(), s113, m111) + } + + val m112 = M11( + lm = mapOf( + "1" to emptyList() + ), + s = "1" + ) + + val s114 = """ + lm.1 = [ ] + s = "1" + """.trimIndent() + + @Test + fun encodeMapLikeWithEmptyCollectionLikeValueUnstructured() { + testEncode(M11.serializer(), m112, s114) + } + + @Test + fun decodeMapLikeWithEmptyCollectionLikeValueUnstructured() { + testDecode(M11.serializer(), s114, m112) + } + + val s115 = """ + lm = { 1 = [ ] } + s = "1" + """.trimIndent() + + @Test + fun decodeMapLikeWithEmptyCollectionLikeValueInline3() { + testDecode(M11.serializer(), s115, m112) + } + + val m113 = M11( + lm = emptyMap(), + s = "" + ) + + val s116 = """ + lm = { } + s = "" + """.trimIndent() + + @Test + fun encodeEmptyMapLikeWithCollectionLikeValueUnstructured() { + testEncode(M11.serializer(), m113, s116) + } + + @Test + fun decodeEmptyMapLikeWithCollectionLikeValueUnstructured() { + testDecode(M11.serializer(), s116, m113) + } + + @Serializable + data class M12( + val i: Int, + val mm: Map> + ) + + val m121 = M12( + i = 0, + mm = mapOf( + "1" to mapOf( + "." to "" + ) + ) + ) + + val s121 = """ + i = 0 + + [mm] + + [mm.1] + "." = "" + """.trimIndent() + + @Test + fun encodeMapLikeWithMapLikeValue() { + testEncode(M12.serializer(), m121, s121) + } + + @Test + fun decodeMapLikeWithMapLikeValue() { + testDecode(M12.serializer(), s121, m121) + } + + val s122 = """ + i = 0 + + [mm] + 1 = { "." = "" } + """.trimIndent() + + @Test + fun decodeMapLikeWithMapLikeValueInline1() { + testDecode(M12.serializer(), s122, m121) + } + + val m122 = M12( + i = 0, + mm = mapOf( + "1" to emptyMap() + ) + ) + + val s123 = """ + i = 0 + + [mm] + + [mm.1] + + """.trimIndent() + + @Test + fun encodeMapLikeWithEmptyMapLikeValue() { + testEncode(M12.serializer(), m122, s123) + } + + @Test + fun decodeMapLikeWithEmptyMapLikeValue() { + testDecode(M12.serializer(), s123, m122) + } + + val s124 = """ + i = 0 + + [mm] + 1 = { } + """.trimIndent() + + @Test + fun decodeMapLikeWithEmptyMapLikeValueInline1() { + testDecode(M12.serializer(), s124, m122) + } + + val m123 = M12( + i = 0, + mm = emptyMap() + ) + + val s125 = """ + i = 0 + + [mm] + + """.trimIndent() + + @Test + fun encodeEmptyMapLikeWithMapLikeValue() { + testEncode(M12.serializer(), m123, s125) + } + + @Test + fun decodeEmptyMapLikeWithMapLikeValue() { + testDecode(M12.serializer(), s125, m123) + } + + @Serializable + data class M13( + val mm: Map>, + val i: Int + ) + + val m131 = M13( + mm = mapOf( + "1" to mapOf( + "" to "" + ) + ), + i = 0 + ) + + val s131 = """ + mm.1."" = "" + i = 0 + """.trimIndent() + + @Test + fun encodeMapLikeWithMapLikeValueUnstructured() { + testEncode(M13.serializer(), m131, s131) + } + + @Test + fun decodeMapLikeWithMapLikeValueUnstructured() { + testDecode(M13.serializer(), s131, m131) + } + + val s132 = """ + mm.1 = { "" = "" } + i = 0 + """.trimIndent() + + @Test + fun decodeMapLikeWithMapLikeValueInline2() { + testDecode(M13.serializer(), s132, m131) + } + + val s133 = """ + mm = { 1 = { "" = "" } } + i = 0 + """.trimIndent() + + @Test + fun decodeMapLikeWithMapLikeValueInline3() { + testDecode(M13.serializer(), s133, m131) + } + + val m132 = M13( + mm = mapOf( + "1" to emptyMap() + ), + i = 0 + ) + + val s134 = """ + mm.1 = { } + i = 0 + """.trimIndent() + + @Test + fun encodeMapLikeWithEmptyMapLikeValueUnstructured() { + testEncode(M13.serializer(), m132, s134) + } + + @Test + fun decodeMapLikeWithEmptyMapLikeValueUnstructured() { + testDecode(M13.serializer(), s134, m132) + } + + val s135 = """ + mm = { 1 = { } } + i = 0 + """.trimIndent() + + @Test + fun decodeMapLikeWithEmptyMapLikeValueInline2() { + testDecode(M13.serializer(), s135, m132) + } + + val m133 = M13( + mm = emptyMap(), + i = 0 + ) + + val s136 = """ + mm = { } + i = 0 + """.trimIndent() + + @Test + fun encodeEmptyMapLikeWithMapLikeValueUnstructured() { + testEncode(M13.serializer(), m133, s136) + } + + @Test + fun decodeEmptyMapLikeWithMapLikeValueUnstructured() { + testDecode(M13.serializer(), s136, m133) + } + + @Serializable + data class M14( + @TomlInline + val c1: C1, + val c2: C2 + ) + + val m141 = M14( + c1 = C1( + s = "inline" + ), + c2 = C2() + ) + + val s141 = """ + c1 = { s = "inline" } + + [c2] + + """.trimIndent() + + @Test + fun encodeClassInline() { + testEncode(M14.serializer(), m141, s141) + } + + @Serializable + data class M15( + @TomlInline + val bm1: Map, + val bm2: Map + ) + + val m151 = M15( + bm1 = mapOf( + "a" to true + ), + bm2 = mapOf( + "b" to false + ) + ) + + val s151 = """ + bm1 = { a = true } + + [bm2] + b = false + """.trimIndent() + + @Test + fun encodeMapLikeWithPrimitiveValueInline() { + testEncode(M15.serializer(), m151, s151) + } + + @Serializable + data class M16( + @TomlInline + val cm1: Map, + val cm2: Map + ) + + val m161 = M16( + cm1 = mapOf( + "a" to C1( + s = "no" + ) + ), + cm2 = mapOf( + "b" to C2() + ) + ) + + val s161 = """ + cm1 = { a = { s = "no" } } + + [cm2] + + [cm2.b] + + """.trimIndent() + + @Test + fun encodeMapLikeWithClassValueInline() { + testEncode(M16.serializer(), m161, s161) + } + + @Serializable + data class M17( + @TomlInline + val cm1: Map, + val cm2: Map + ) + + val m171 = M17( + cm1 = mapOf( + "a" to C2() + ), + cm2 = mapOf( + "b" to C1( + s = "false" + ) + ) + ) + + val s171 = """ + cm1 = { a = { } } + + [cm2] + + [cm2.b] + s = "false" + """.trimIndent() + + @Test + fun encodeMapLikeWithEmptyClassValueInline() { + testEncode(M17.serializer(), m171, s171) + } + + @Serializable + data class M18( + @TomlInline + val lm: Map + ) + + val m181 = M18( + lm = mapOf( + "a" to byteArrayOf(0) + ) + ) + + val s181 = """ + lm = { a = [ 0 ] } + """.trimIndent() + + @Test + fun encodeMapLikeWithCollectionLikeValueInline() { + testEncode(M18.serializer(), m181, s181) + } + + val m182 = M18( + lm = mapOf( + "b" to byteArrayOf() + ) + ) + + val s182 = """ + lm = { b = [ ] } + """.trimIndent() + + @Test + fun encodeMapLikeWithEmptyCollectionLikeValueInline() { + testEncode(M18.serializer(), m182, s182) + } + + @Serializable + data class M19( + @TomlInline + val mm1: Map>, + val mm2: Map> + ) + + val m191 = M19( + mm1 = mapOf( + "1" to mapOf( + "1" to "1" + ) + ), + mm2 = emptyMap() + ) + + val s191 = """ + mm1 = { 1 = { 1 = "1" } } + + [mm2] + + """.trimIndent() + + @Test + fun encodeMapLikeWithMapLikeValueInline() { + testEncode(M19.serializer(), m191, s191) + } + + val m192 = M19( + mm1 = mapOf( + "2" to emptyMap() + ), + mm2 = emptyMap() + ) + + val s192 = """ + mm1 = { 2 = { } } + + [mm2] + + """.trimIndent() + + @Test + fun encodeMapLikeWithEmptyMapLikeValueInline() { + testEncode(M19.serializer(), m192, s192) } } diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TestUtils.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TestUtils.kt index 320dd28..a9f64e9 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TestUtils.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TestUtils.kt @@ -15,6 +15,16 @@ fun testEncode( assertEquals(expect, actual) } +fun testEncodeTomlElement( + serializer: SerializationStrategy, + value: T, + expect: TomlElement +) { + val actual = Toml.encodeToTomlElement(serializer, value) + + assertEquals(expect, actual) +} + fun testDecode( deserializer: DeserializationStrategy, string: String, @@ -25,6 +35,16 @@ fun testDecode( assertEquals(expect, actual) } +fun testDecodeTomlElement( + deserializer: DeserializationStrategy, + value: TomlElement, + expect: T +) { + val actual = Toml.decodeFromTomlElement(deserializer, value) + + assertEquals(expect, actual) +} + fun testInvalid(string: String) { assertFails { Toml.parseToTomlTable(string) } } diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TomlLiteralTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TomlLiteralTest.kt index ecc8c71..f54b439 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TomlLiteralTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TomlLiteralTest.kt @@ -1,5 +1,484 @@ package net.peanuuutz.tomlkt +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + class TomlLiteralTest { + val m01: Boolean = true + + val l01 = TomlLiteral(true) + + val m02: Boolean = false + + val l02 = TomlLiteral(false) + + @Test + fun convertBoolean() { + assertEquals(l01.toBoolean(), m01) + assertEquals(l02.toBoolean(), m02) + } + + val m03: Byte = Byte.MIN_VALUE + + val l03 = TomlLiteral(Byte.MIN_VALUE) + + val m04: Byte = -0 + + val l04 = TomlLiteral(-0) + + val m05: Byte = 0 + + val l05 = TomlLiteral(0) + + val m06: Byte = +0 + + val l06 = TomlLiteral(+0) + + val m07: Byte = Byte.MAX_VALUE + + val l07 = TomlLiteral(Byte.MAX_VALUE) + + @Test + fun convertByte() { + assertEquals(l03.toByte(), m03) + assertEquals(l04.toByte(), m04) + assertEquals(l05.toByte(), m05) + assertEquals(l06.toByte(), m06) + assertEquals(l07.toByte(), m07) + } + + val m08: Short = Short.MIN_VALUE + + val l08 = TomlLiteral(Short.MIN_VALUE) + + val m09: Short = -0 + + val l09 = TomlLiteral(-0) + + val m010: Short = 0 + + val l010 = TomlLiteral(0) + + val m011: Short = +0 + + val l011 = TomlLiteral(+0) + + val m012: Short = Short.MAX_VALUE + + val l012 = TomlLiteral(Short.MAX_VALUE) + + @Test + fun convertShort() { + assertEquals(l08.toShort(), m08) + assertEquals(l09.toShort(), m09) + assertEquals(l010.toShort(), m010) + assertEquals(l011.toShort(), m011) + assertEquals(l012.toShort(), m012) + } + + val m013: Int = Int.MIN_VALUE + + val l013 = TomlLiteral(Int.MIN_VALUE) + + val m014: Int = -0 + + val l014 = TomlLiteral(-0) + + val m015: Int = 0 + + val l015 = TomlLiteral(0) + + val m016: Int = +0 + + val l016 = TomlLiteral(+0) + + val m017: Int = Int.MAX_VALUE + + val l017 = TomlLiteral(Int.MAX_VALUE) + + @Test + fun convertInt() { + assertEquals(l013.toInt(), m013) + assertEquals(l014.toInt(), m014) + assertEquals(l015.toInt(), m015) + assertEquals(l016.toInt(), m016) + assertEquals(l017.toInt(), m017) + } + + val m018: Long = Long.MIN_VALUE + + val l018 = TomlLiteral(Long.MIN_VALUE) + + val m019: Long = -0 + + val l019 = TomlLiteral(-0) + + val m020: Long = 0 + + val l020 = TomlLiteral(0) + + val m021: Long = +0 + + val l021 = TomlLiteral(+0) + + val m022: Long = Long.MAX_VALUE + + val l022 = TomlLiteral(Long.MAX_VALUE) + + @Test + fun convertLong() { + assertEquals(l018.toLong(), m018) + assertEquals(l019.toLong(), m019) + assertEquals(l020.toLong(), m020) + assertEquals(l021.toLong(), m021) + assertEquals(l022.toLong(), m022) + } + + val m023: Float = Float.NEGATIVE_INFINITY + + val l023 = TomlLiteral(Float.NEGATIVE_INFINITY) + + val m024: Float = -Float.MAX_VALUE + + val l024 = TomlLiteral(-Float.MAX_VALUE) + + val m025: Float = -Float.MIN_VALUE + + val l025 = TomlLiteral(-Float.MIN_VALUE) + + val m026: Float = -0.0f + + val l026 = TomlLiteral(-0.0f) + + val m027: Float = 0.0f + + val l027 = TomlLiteral(0.0f) + + val m028: Float = +0.0f + + val l028 = TomlLiteral(+0.0f) + + val m029: Float = Float.MIN_VALUE + + val l029 = TomlLiteral(Float.MIN_VALUE) + + val m030: Float = Float.MAX_VALUE + + val l030 = TomlLiteral(Float.MAX_VALUE) + + val m031: Float = Float.POSITIVE_INFINITY + + val l031 = TomlLiteral(Float.POSITIVE_INFINITY) + + val m032: Float = Float.NaN + + val l032 = TomlLiteral(Float.NaN) + + @Test + fun convertFloat() { + assertEquals(l023.toFloat(), m023, 0.0f) + assertEquals(l024.toFloat(), m024, 0.0f) + assertEquals(l025.toFloat(), m025, 0.0f) + assertEquals(l026.toFloat(), m026, 0.0f) + assertEquals(l027.toFloat(), m027, 0.0f) + assertEquals(l028.toFloat(), m028, 0.0f) + assertEquals(l029.toFloat(), m029, 0.0f) + assertEquals(l030.toFloat(), m030, 0.0f) + assertEquals(l031.toFloat(), m031, 0.0f) + assertTrue { l032.toFloat().isNaN() == m032.isNaN() } + } + + val m033: Double = Double.NEGATIVE_INFINITY + + val l033 = TomlLiteral(Double.NEGATIVE_INFINITY) + + val m034: Double = -Double.MAX_VALUE + + val l034 = TomlLiteral(-Double.MAX_VALUE) + + val m035: Double = -Double.MIN_VALUE + + val l035 = TomlLiteral(-Double.MIN_VALUE) + + val m036: Double = -0.0 + + val l036 = TomlLiteral(-0.0) + + val m037: Double = 0.0 + + val l037 = TomlLiteral(0.0) + + val m038: Double = +0.0 + + val l038 = TomlLiteral(+0.0) + + val m039: Double = Double.MIN_VALUE + + val l039 = TomlLiteral(Double.MIN_VALUE) + + val m040: Double = Double.MAX_VALUE + + val l040 = TomlLiteral(Double.MAX_VALUE) + + val m041: Double = Double.POSITIVE_INFINITY + + val l041 = TomlLiteral(Double.POSITIVE_INFINITY) + + val m042: Double = Double.NaN + + val l042 = TomlLiteral(Double.NaN) + + @Test + fun convertDouble() { + assertEquals(l033.toDouble(), m033, 0.0) + assertEquals(l034.toDouble(), m034, 0.0) + assertEquals(l035.toDouble(), m035, 0.0) + assertEquals(l036.toDouble(), m036, 0.0) + assertEquals(l037.toDouble(), m037, 0.0) + assertEquals(l038.toDouble(), m038, 0.0) + assertEquals(l039.toDouble(), m039, 0.0) + assertEquals(l040.toDouble(), m040, 0.0) + assertEquals(l041.toDouble(), m041, 0.0) + assertTrue { l042.toDouble().isNaN() == m042.isNaN() } + } + + val m043: UByte = UByte.MIN_VALUE + + val l043 = TomlLiteral(UByte.MIN_VALUE) + + val m044: UByte = UByte.MAX_VALUE + + val l044 = TomlLiteral(UByte.MAX_VALUE) + + @Test + fun convertUByte() { + assertEquals(l043.content.toUByte(), m043) + assertEquals(l044.content.toUByte(), m044) + } + + val m045: UShort = UShort.MIN_VALUE + + val l045 = TomlLiteral(UShort.MIN_VALUE) + + val m046: UShort = UShort.MAX_VALUE + + val l046 = TomlLiteral(UShort.MAX_VALUE) + + @Test + fun convertUShort() { + assertEquals(l045.content.toUShort(), m045) + assertEquals(l046.content.toUShort(), m046) + } + + val m047: UInt = UInt.MIN_VALUE + + val l047 = TomlLiteral(UInt.MIN_VALUE) + + val m048: UInt = UInt.MAX_VALUE + + val l048 = TomlLiteral(UInt.MAX_VALUE) + + @Test + fun convertUInt() { + assertEquals(l047.content.toUInt(), m047) + assertEquals(l048.content.toUInt(), m048) + } + + val m049: ULong = ULong.MIN_VALUE + + val l049 = TomlLiteral(ULong.MIN_VALUE) + + val m050: ULong = ULong.MAX_VALUE + + val l050 = TomlLiteral(ULong.MAX_VALUE) + + @Test + fun convertULong() { + assertEquals(l049.content.toULong(), m049) + assertEquals(l050.content.toULong(), m050) + } + + val m051: Char = 'a' + + val l051 = TomlLiteral('a') + + val m052: Char = ' ' + + val l052 = TomlLiteral(' ') + + val m053: Char = '\n' + + val l053 = TomlLiteral('\n') + + @Test + fun covertChar() { + assertEquals(l051.toChar(), m051) + assertEquals(l052.toChar(), m052) + assertEquals(l053.toChar(), m053) + } + + val m054: String = "a \n" + + val l054 = TomlLiteral("a \n") + + @Test + fun convertString() { + assertEquals(l054.content, m054) + } + + val m055: TomlLocalDateTime = TomlLocalDateTime("2023-09-08T18:48:00") + + val l055 = TomlLiteral(TomlLocalDateTime("2023-09-08T18:48:00")) + + val m056: TomlLocalDateTime = TomlLocalDateTime("2023-09-08T18:48:00.500") + + val l056 = TomlLiteral(TomlLocalDateTime("2023-09-08T18:48:00.500")) + + @Test + fun convertLocalDateTime() { + assertEquals(l055.toLocalDateTime(), m055) + assertEquals(l056.toLocalDateTime(), m056) + } + + val m057: TomlOffsetDateTime = TomlOffsetDateTime("2023-09-08T18:48:00-09:00") + + val l057 = TomlLiteral(TomlOffsetDateTime("2023-09-08T18:48:00-09:00")) + + val m058: TomlOffsetDateTime = TomlOffsetDateTime("2023-09-08T18:48:00-00:00") + + val l058 = TomlLiteral(TomlOffsetDateTime("2023-09-08T18:48:00-00:00")) + + val m059: TomlOffsetDateTime = TomlOffsetDateTime("2023-09-08T18:48:00Z") + + val l059 = TomlLiteral(TomlOffsetDateTime("2023-09-08T18:48:00Z")) + + val m060: TomlOffsetDateTime = TomlOffsetDateTime("2023-09-08T18:48:00+00:00") + + val l060 = TomlLiteral(TomlOffsetDateTime("2023-09-08T18:48:00+00:00")) + + val m061: TomlOffsetDateTime = TomlOffsetDateTime("2023-09-08T18:48:00+09:00") + + val l061 = TomlLiteral(TomlOffsetDateTime("2023-09-08T18:48:00+09:00")) + + val m062: TomlOffsetDateTime = TomlOffsetDateTime("2023-09-08T18:48:00.500+09:00") + + val l062 = TomlLiteral(TomlOffsetDateTime("2023-09-08T18:48:00.500+09:00")) + + @Test + fun convertOffsetDateTime() { + assertEquals(l057.toOffsetDateTime(), m057) + assertEquals(l058.toOffsetDateTime(), m058) + assertEquals(l059.toOffsetDateTime(), m059) + assertEquals(l060.toOffsetDateTime(), m060) + assertEquals(l061.toOffsetDateTime(), m061) + assertEquals(l062.toOffsetDateTime(), m062) + } + + val m063: TomlLocalDate = TomlLocalDate("2023-09-08") + + val l063 = TomlLiteral(TomlLocalDate("2023-09-08")) + + @Test + fun convertLocalDate() { + assertEquals(l063.toLocalDate(), m063) + } + + val m064: TomlLocalTime = TomlLocalTime("18:53:00") + + val l064 = TomlLiteral(TomlLocalTime("18:53:00")) + + val m065: TomlLocalTime = TomlLocalTime("18:53:00.500") + + val l065 = TomlLiteral(TomlLocalTime("18:53:00.500")) + + val m066: TomlLocalTime = TomlLocalTime("18:53") + + val l066 = TomlLiteral(TomlLocalTime("18:53")) + + @Test + fun convertLocalTime() { + assertEquals(l064.toLocalTime(), m064) + assertEquals(l065.toLocalTime(), m065) + assertEquals(l066.toLocalTime(), m066) + } + + @Serializable + enum class E { + @SerialName("bruh") A, + B, + C + } + + val m067: E = E.B + + val l067 = TomlLiteral(E.B) + + @Test + fun convertEnum() { + assertEquals(l067.toEnum(), m067) + } + + @Serializable + data class M1( + val l: TomlLiteral + ) + + val m11 = M1( + l = TomlLiteral(E.A) + ) + + val s11 = """ + l = "bruh" + """.trimIndent() + + @Test + fun encodeRegularly() { + testEncode(M1.serializer(), m11, s11) + } + + @Test + fun decodeRegularly() { + testDecode(M1.serializer(), s11, m11) + } + + @Serializable + data class M2( + val e: TomlElement + ) + + val m21 = M2( + e = TomlLiteral(TomlLocalDate("2001-01-23")) + ) + + val s21 = """ + e = 2001-01-23 + """.trimIndent() + + @Test + fun encodeAsElement() { + testEncode(M2.serializer(), m21, s21) + } + + @Test + fun decodeAsElement() { + testDecode(M2.serializer(), s21, m21) + } + + val m31 = 0xFEE490 + + val l31 = TomlLiteral(0xFEE490) + + @Test + fun encodeToTomlLiteral() { + testEncodeTomlElement(Int.serializer(), m31, l31) + } + @Test + fun decodeFromTomlLiteral() { + testDecodeTomlElement(Int.serializer(), l31, m31) + } } diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TomlNullTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TomlNullTest.kt index 70d81aa..eb62380 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TomlNullTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/TomlNullTest.kt @@ -1,5 +1,85 @@ package net.peanuuutz.tomlkt +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.NothingSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.test.Test + class TomlNullTest { + @Serializable + data class M1( + val n: TomlNull + ) + + val m11 = M1( + n = TomlNull + ) + + val s11 = """ + n = null + """.trimIndent() + + @Test + fun encodeRegularly() { + testEncode(M1.serializer(), m11, s11) + } + + @Test + fun decodeRegularly() { + testDecode(M1.serializer(), s11, m11) + } + + @Serializable + data class M2( + val e: TomlElement + ) + + val m21 = M2( + e = TomlNull + ) + + val s21 = """ + e = null + """.trimIndent() + + @Test + fun encodeAsElement() { + testEncode(M2.serializer(), m21, s21) + } + + @Test + fun decodeAsElement() { + testDecode(M2.serializer(), s21, m21) + } + + val m31 = null + + val e31 = TomlNull + + // The official doesn't provide a serializer for null only value... + object NullSerializer : KSerializer { + override val descriptor: SerialDescriptor + get() = NothingSerializer().descriptor + + override fun serialize(encoder: Encoder, value: Nothing?) { + encoder.encodeNull() + } + + override fun deserialize(decoder: Decoder): Nothing? { + return decoder.decodeNull() + } + } + + @Test + fun encodeToTomlNull() { + testEncodeTomlElement(NullSerializer, m31, e31) + } + @Test + fun decodeFromTomlNull() { + testDecodeTomlElement(NullSerializer, e31, m31) + } } diff --git a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ValueClassTest.kt b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ValueClassTest.kt index f7d9189..1844beb 100644 --- a/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ValueClassTest.kt +++ b/core/src/commonTest/kotlin/net/peanuuutz/tomlkt/ValueClassTest.kt @@ -4,6 +4,7 @@ import kotlinx.serialization.Serializable import kotlin.jvm.JvmInline import kotlin.test.Test +@OptIn(ExperimentalUnsignedTypes::class) class ValueClassTest { @Serializable data class M1( @@ -70,24 +71,89 @@ class ValueClassTest { @Serializable data class M3( - val u: UByte + val v: V3 ) + @Serializable + @JvmInline + value class V3(val ns: String?) + val m31 = M3( - u = 1.toUByte() + v = V3(null) ) val s31 = """ - u = 1 + v = null """.trimIndent() @Test - fun encodeUnsigned() { + fun encodeNullableBacked() { testEncode(M3.serializer(), m31, s31) } @Test - fun decodeUnsigned() { + fun decodeNullableBacked() { testDecode(M3.serializer(), s31, m31) } + + @Serializable + data class M4( + val u: UByte + ) + + val m41 = M4( + u = 255u + ) + + val s41 = """ + u = 255 + """.trimIndent() + + @Test + fun encodeUnsigned() { + testEncode(M4.serializer(), m41, s41) + } + + @Test + fun decodeUnsigned() { + testDecode(M4.serializer(), s41, m41) + } + + @Serializable + class M5( + val ua: UByteArray + ) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is M5) return false + if (!ua.contentEquals(other.ua)) return false + return true + } + + override fun hashCode(): Int { + return ua.contentHashCode() + } + } + + val m51 = M5( + ua = ubyteArrayOf(255u, 1u, 0u) + ) + + val s51 = """ + ua = [ + 255, + 1, + 0 + ] + """.trimIndent() + + @Test + fun encodeUnsignedArray() { + testEncode(M5.serializer(), m51, s51) + } + + @Test + fun decodeUnsignedArray() { + testDecode(M5.serializer(), s51, m51) + } } diff --git a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.jvm.kt b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.jvm.kt index dea3cf6..4507e1d 100644 --- a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.jvm.kt +++ b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.jvm.kt @@ -1,3 +1,19 @@ +/* + Copyright 2023 Peanuuutz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package net.peanuuutz.tomlkt import java.time.format.DateTimeParseException diff --git a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt index 9ffef12..10e034a 100644 --- a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt +++ b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreamWriter.kt @@ -1,3 +1,19 @@ +/* + Copyright 2023 Peanuuutz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package net.peanuuutz.tomlkt import net.peanuuutz.tomlkt.internal.LineFeedCode diff --git a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreams.kt b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreams.kt index c3e8514..86b985b 100644 --- a/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreams.kt +++ b/core/src/jvmMain/kotlin/net/peanuuutz/tomlkt/TomlStreams.kt @@ -1,3 +1,19 @@ +/* + Copyright 2023 Peanuuutz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package net.peanuuutz.tomlkt import kotlinx.serialization.SerializationStrategy diff --git a/core/src/kotlinxMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.kotlinx.kt b/core/src/kotlinxMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.kotlinx.kt index 6a60d5c..c1c7aa0 100644 --- a/core/src/kotlinxMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.kotlinx.kt +++ b/core/src/kotlinxMain/kotlin/net/peanuuutz/tomlkt/NativeDateTime.kotlinx.kt @@ -1,3 +1,19 @@ +/* + Copyright 2023 Peanuuutz + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + package net.peanuuutz.tomlkt import kotlinx.datetime.Instant