Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for "inf" and "nan" values #180

Merged
merged 4 commits into from
Jan 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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}]" +
Expand Down Expand Up @@ -116,11 +123,17 @@ public abstract class TomlAbstractDecoder : AbstractDecoder() {
}
}

private inline fun <reified T> 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 <reified T> 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,
Expand All @@ -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)",
Expand All @@ -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")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -126,4 +129,33 @@ class StringDecoderTest {
decoded
)
}

@Test
fun emptyStringTest() {
var test = """
winpath =
winpath2 = ''
quoted = ""
regex = ''
"""

val decoded = Toml().decodeFromString<Literals>(test)
assertEquals(
Literals(
null,
"",
"",
""
),
decoded
)

test = """
winpath =
""".trimIndent()

assertFailsWith<ParseException> {
Toml(TomlInputConfig(allowEmptyValues = false)).decodeFromString<Literals>(test)
}
}
}