diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt index b42a814c..8c64bc1c 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt @@ -45,17 +45,24 @@ public abstract class TomlAbstractDecoder : AbstractDecoder() { try { (value.content as String).convertSpecialCharacters(keyValue.lineNo).single() } catch (ex: NoSuchElementException) { - throw IllegalTypeException("Empty value is not allowed for type [Char], " + - "please check the value: [${value.content}] or use [String] type for deserialization of " + - "[${keyValue.key}] instead", keyValue.lineNo) + throw IllegalTypeException( + "Empty value is not allowed for type [Char], " + + "please check the value: [${value.content}] or use [String] type for deserialization of " + + "[${keyValue.key}] instead", keyValue.lineNo + ) } catch (ex: IllegalArgumentException) { - throw IllegalTypeException("[Char] type should be used for decoding of single character, but " + - "received multiple characters instead: [${value.content}]. " + - "If you really want to decode multiple chars, use [String] instead.", keyValue.lineNo) + throw IllegalTypeException( + "[Char] type should be used for decoding of single character, but " + + "received multiple characters instead: [${value.content}]. " + + "If you really want to decode multiple chars, use [String] instead.", keyValue.lineNo + ) } // to avoid confusion, we prohibit basic strings with double quotes for decoding to a Char type - is TomlBasicString -> throw IllegalTypeException("Double quotes were used in the input for deserialization " + - "of [Char]. Use [String] type or single quotes ('') instead for: [${value.content}]", keyValue.lineNo) + is TomlBasicString -> throw IllegalTypeException( + "Double quotes were used in the input for deserialization " + + "of [Char]. Use [String] type or single quotes ('') instead for: [${value.content}]", + keyValue.lineNo + ) // all other toml tree types are not supported else -> throw IllegalTypeException( "Cannot decode the key [${keyValue.key.last()}] with the value [${keyValue.value.content}]" + @@ -116,11 +123,17 @@ public abstract class TomlAbstractDecoder : AbstractDecoder() { } } - private inline fun decodeFloatingPoint(content: Double, lineNo: Int): T = when (T::class) { - Float::class -> validateAndConvertFloatingPoint(content, lineNo, FLOAT) { num: Double -> num.toFloat() as T } - Double::class -> validateAndConvertFloatingPoint(content, lineNo, DOUBLE) { num: Double -> num as T } - else -> invalidType(T::class.toString(), "Signed Type") - } + private inline fun decodeFloatingPoint(content: Double, lineNo: Int): T = + when (T::class) { + Float::class -> validateAndConvertFloatingPoint( + content, + lineNo, + FLOAT + ) { num: Double -> num.toFloat() as T } + + Double::class -> validateAndConvertFloatingPoint(content, lineNo, DOUBLE) { num: Double -> num as T } + else -> invalidType(T::class.toString(), "Signed Type") + } /** * ktoml parser treats all integer literals as Long and all floating-point literals as Double, @@ -131,10 +144,10 @@ public abstract class TomlAbstractDecoder : AbstractDecoder() { lineNo: Int, limits: FloatingPointLimitsEnum, conversion: (Double) -> T, - ): T = if (content in limits.min..limits.max) { - conversion(content) - } else { - throw IllegalTypeException( + ): T = when { + content.isInfinite() || content.isNaN() -> conversion(content) + content in limits.min..limits.max -> conversion(content) + else -> throw IllegalTypeException( "The floating point literal, that you have provided is <$content>, " + "but the type for deserialization is <${T::class}>. You will get an overflow, " + "so we advise you to check the data or use other type for deserialization (Long, for example)", @@ -159,6 +172,7 @@ public abstract class TomlAbstractDecoder : AbstractDecoder() { "Deserialized floating-point number should have a dot: <$content.0>", lineNo ) + else -> invalidType(T::class.toString(), "Signed Type") } diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValue.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValue.kt index 56ef52e4..687e9119 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValue.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/TomlKeyValue.kt @@ -124,6 +124,10 @@ public fun String.splitKeyValue(lineNo: Int, config: TomlInputConfig = TomlInput * @return parsed TomlNode value */ public fun String.parseValue(lineNo: Int, config: TomlInputConfig): TomlValue = when (this) { + // ===== special values + "+inf", "inf" -> TomlDouble(Double.POSITIVE_INFINITY) + "-inf" -> TomlDouble(Double.NEGATIVE_INFINITY) + "-nan", "+nan", "nan", "-NaN", "+NaN", "NaN" -> TomlDouble(Double.NaN) // ===== null values "null", "nil", "NULL", "NIL", "" -> if (config.allowNullValues) { TomlNull(lineNo) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDouble.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDouble.kt index daac6327..d87f9088 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDouble.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/nodes/pairs/values/TomlDouble.kt @@ -15,6 +15,8 @@ internal constructor( ) : TomlValue() { public constructor(content: String, lineNo: Int) : this(content.toDouble()) + public constructor(content: Double, lineNo: Int) : this(content) + override fun write( emitter: TomlEmitter, config: TomlOutputConfig, diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PrimitivesDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PrimitivesDecoderTest.kt index ea9cbc19..ee1ee00a 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PrimitivesDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/PrimitivesDecoderTest.kt @@ -215,6 +215,9 @@ class PrimitivesDecoderTest { test(-1f, "-1.0") test(-128f, "-128.0") test(127f, "127.0") + test(Float.NEGATIVE_INFINITY, "-inf") + test(Float.POSITIVE_INFINITY, "+inf") + test(Float.NaN, "nan") } @Test @@ -247,12 +250,14 @@ class PrimitivesDecoderTest { assertEquals(expected, data.value) } - test(0.0, "0.0") test(1.0, "1.0") test(-1.0, "-1.0") test(-128.0, "-128.0") test(127.0, "127.0") + test(Double.NEGATIVE_INFINITY, "-inf") + test(Double.POSITIVE_INFINITY, "+inf") + test(Double.NaN, "nan") } @Test diff --git a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt index 2717fc33..8c212e75 100644 --- a/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt +++ b/ktoml-core/src/commonTest/kotlin/com/akuleshov7/ktoml/decoders/StringsDecoderTest.kt @@ -1,15 +1,18 @@ package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.exceptions.ParseException import kotlinx.serialization.decodeFromString import kotlinx.serialization.Serializable import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith class StringDecoderTest { @Serializable data class Literals( - val winpath: String, + val winpath: String?, val winpath2: String, val quoted: String, val regex: String, @@ -126,4 +129,33 @@ class StringDecoderTest { decoded ) } + + @Test + fun emptyStringTest() { + var test = """ + winpath = + winpath2 = '' + quoted = "" + regex = '' + """ + + val decoded = Toml().decodeFromString(test) + assertEquals( + Literals( + null, + "", + "", + "" + ), + decoded + ) + + test = """ + winpath = + """.trimIndent() + + assertFailsWith { + Toml(TomlInputConfig(allowEmptyValues = false)).decodeFromString(test) + } + } }