Skip to content

Commit

Permalink
parse all edge cases of ints and floats
Browse files Browse the repository at this point in the history
  • Loading branch information
lbialy committed Jul 16, 2024
1 parent cfc168a commit cc1dd26
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 7 deletions.
49 changes: 42 additions & 7 deletions core/shared/src/main/scala/org/virtuslab/yaml/YamlDecoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,27 +85,62 @@ object YamlDecoder extends YamlDecoderCompanionCrossCompat {
)

implicit def forInt: YamlDecoder[Int] = YamlDecoder { case s @ ScalarNode(value, _) =>
Try(java.lang.Integer.decode(value.replaceAll("_", "")).toInt).toEither.left
val normalizedValue =
if value.startsWith("0o") then value.stripPrefix("0o").prepended('0') else value

Try(java.lang.Integer.decode(normalizedValue.replaceAll("_", "")).toInt).toEither.left
.map(ConstructError.from(_, "Int", s))
}

implicit def forLong: YamlDecoder[Long] = YamlDecoder { case s @ ScalarNode(value, _) =>
Try(java.lang.Long.decode(value.replaceAll("_", "")).toLong).toEither.left
val normalizedValue =
if value.startsWith("0o") then value.stripPrefix("0o").prepended('0') else value

Try(java.lang.Long.decode(normalizedValue.replaceAll("_", "")).toLong).toEither.left
.map(ConstructError.from(_, "Long", s))
}

private val infinityRegex = """([-+]?)(\.inf|\.Inf|\.INF)""".r

private val nanRegex = """(\.nan|\.NaN|\.NAN)""".r

implicit def forDouble: YamlDecoder[Double] = YamlDecoder { case s @ ScalarNode(value, _) =>
Try(java.lang.Double.parseDouble(value.replaceAll("_", ""))).toEither.left
.map(ConstructError.from(_, "Double", s))
infinityRegex.findFirstMatchIn(value) match
case Some(m) =>
Right(m.group(1) match
case "-" => Double.NegativeInfinity
case _ => Double.PositiveInfinity
)
case None =>
nanRegex.findFirstMatchIn(value) match
case Some(_) =>
Right(Double.NaN)
case None =>
Try(java.lang.Double.parseDouble(value.replaceAll("_", ""))).toEither.left
.map(ConstructError.from(_, "Double", s))
}

implicit def forFloat: YamlDecoder[Float] = YamlDecoder { case s @ ScalarNode(value, _) =>
Try(java.lang.Float.parseFloat(value.replaceAll("_", ""))).toEither.left
.map(ConstructError.from(_, "Float", s))
infinityRegex.findFirstMatchIn(value) match
case Some(m) =>
Right(m.group(1) match
case "-" => Float.NegativeInfinity
case _ => Float.PositiveInfinity
)
case None =>
nanRegex.findFirstMatchIn(value) match
case Some(_) =>
Right(Float.NaN)
case None =>
Try(java.lang.Float.parseFloat(value.replaceAll("_", ""))).toEither.left
.map(ConstructError.from(_, "Float", s))
}

implicit def forShort: YamlDecoder[Short] = YamlDecoder { case s @ ScalarNode(value, _) =>
Try(java.lang.Short.decode(value.replaceAll("_", "")).toShort).toEither.left
val normalizedValue =
if value.startsWith("0o") then value.stripPrefix("0o").prepended('0') else value

Try(java.lang.Short.decode(normalizedValue.replaceAll("_", "")).toShort).toEither.left
.map(ConstructError.from(_, "Short", s))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,60 @@ class DecoderSuite extends munit.FunSuite:
assertEquals(foo, Right(List(Some(Foo(1, "1")), None)))
}

test("issue 222 - parse edge cases of booleans floats doubles and integers") {
case class Data(
booleans: List[Boolean],
integers: List[Int],
floats: List[Float],
`also floats`: List[Float],
`also doubles`: List[Double]
) derives YamlCodec

val yaml = """booleans: [ true, True, false, FALSE ]
|integers: [ 0, 0o7, 0x3A, -19 ]
|floats: [
| 0., -0.0, .5, +12e03, -2E+05 ]
|also floats: [
| .inf, -.Inf, +.INF, .NAN, .nan, .NaN]
|also doubles: [
| .inf, -.Inf, +.INF, .NAN, .nan, .NaN]""".stripMargin

val expected = Data(
booleans = List(true, true, false, false),
integers = List(0, 7, 58, -19),
floats = List(0.0f, -0.0f, 0.5f, 12000.0f, -200000.0f),
`also floats` = List(
Float.PositiveInfinity,
Float.NegativeInfinity,
Float.PositiveInfinity,
Float.NaN,
Float.NaN,
Float.NaN
),
`also doubles` = List(
Double.PositiveInfinity,
Double.NegativeInfinity,
Double.PositiveInfinity,
Double.NaN,
Double.NaN,
Double.NaN
)
)

yaml.as[Data] match
case Left(error: YamlError) => throw error
case Right(data) =>
assertEquals(data.booleans, expected.booleans)
assertEquals(data.integers, expected.integers)
assertEquals(data.floats, expected.floats)
data.`also floats`.zipAll(expected.`also floats`, 0f, 0f).foreach { case (a, b) =>
assertEqualsFloat(a, b, 0f)
}
data.`also doubles`.zipAll(expected.`also doubles`, 0.0d, 0.0d).foreach { case (a, b) =>
assertEqualsDouble(a, b, 0.0d)
}
}

test("issue 281 - parse multiline string") {
case class Data(description: String) derives YamlCodec

Expand Down

0 comments on commit cc1dd26

Please sign in to comment.