diff --git a/js/src/main/scala-2.10/scalajson.ast/JValue.scala b/js/src/main/scala-2.10/scalajson.ast/JValue.scala index df9cacc..7c6e547 100644 --- a/js/src/main/scala-2.10/scalajson.ast/JValue.scala +++ b/js/src/main/scala-2.10/scalajson.ast/JValue.scala @@ -146,6 +146,16 @@ final class JNumber private[ast] (val value: String) extends JValue { case jNumberRegex(_ *) => new JNumber(value) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Int] = scalajson.ast.toInt(value) + + def toBigInt: Option[BigInt] = scalajson.ast.toBigInt(value) + + def toLong: Option[Long] = scalajson.ast.toLong(value) + + def toDouble: Option[Double] = scalajson.ast.toDouble(value) + + def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value) } /** Represents a JSON Boolean value, which can either be a diff --git a/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala b/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala index d1b7ca8..0f57473 100644 --- a/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala +++ b/js/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala @@ -100,6 +100,16 @@ final case class JNumber(value: String) extends JValue { case n if n.isInfinity => null case n => n } + + def toInt: Option[Int] = scalajson.ast.toInt(value) + + def toBigInt: Option[BigInt] = scalajson.ast.toBigInt(value) + + def toLong: Option[Long] = scalajson.ast.toLong(value) + + def toDouble: Option[Double] = scalajson.ast.toDouble(value) + + def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value) } /** Represents a JSON Boolean value, which can either be a diff --git a/js/src/main/scala/scalajson/ast/JValue.scala b/js/src/main/scala/scalajson/ast/JValue.scala index 6d5eb47..5f4b715 100644 --- a/js/src/main/scala/scalajson/ast/JValue.scala +++ b/js/src/main/scala/scalajson/ast/JValue.scala @@ -98,13 +98,6 @@ object JNumber { */ final case class JNumber private[ast] (value: String) extends JValue { - /** - * Javascript specification for numbers specify a [[scala.Double]], so this is the default export method to `Javascript` - * - * @param value - */ - def this(value: Double) = this(value.toString) - override def toUnsafe: unsafe.JValue = unsafe.JNumber(value) override def toJsAny: js.Any = value.toDouble match { @@ -128,6 +121,16 @@ final case class JNumber private[ast] (value: String) extends JValue { case jNumberRegex(_ *) => new JNumber(value) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Int] = scalajson.ast.toInt(value) + + def toBigInt: Option[BigInt] = scalajson.ast.toBigInt(value) + + def toLong: Option[Long] = scalajson.ast.toLong(value) + + def toDouble: Option[Double] = scalajson.ast.toDouble(value) + + def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value) } /** Represents a JSON Boolean value, which can either be a diff --git a/js/src/main/scala/scalajson/ast/unsafe/JValue.scala b/js/src/main/scala/scalajson/ast/unsafe/JValue.scala index 9ea4882..9a544f0 100644 --- a/js/src/main/scala/scalajson/ast/unsafe/JValue.scala +++ b/js/src/main/scala/scalajson/ast/unsafe/JValue.scala @@ -98,6 +98,16 @@ final case class JNumber(value: String) extends JValue { case n if n.isInfinity => null case n => n } + + def toInt: Option[Int] = scalajson.ast.toInt(value) + + def toBigInt: Option[BigInt] = scalajson.ast.toBigInt(value) + + def toLong: Option[Long] = scalajson.ast.toLong(value) + + def toDouble: Option[Double] = scalajson.ast.toDouble(value) + + def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value) } /** Represents a JSON Boolean value, which can either be a diff --git a/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala b/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala index d65381f..39b3c69 100644 --- a/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala +++ b/jvm/src/main/scala-2.10/scalajson.ast/JValue.scala @@ -125,6 +125,16 @@ final class JNumber private[ast] (val value: String) extends JValue { case jNumberRegex(_ *) => new JNumber(value) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Int] = scalajson.ast.toInt(value) + + def toBigInt: Option[BigInt] = scalajson.ast.toBigInt(value) + + def toLong: Option[Long] = scalajson.ast.toLong(value) + + def toDouble: Option[Double] = scalajson.ast.toDouble(value) + + def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value) } /** Represents a JSON Boolean value, which can either be a diff --git a/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala b/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala index 3437473..ba87956 100644 --- a/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala +++ b/jvm/src/main/scala-2.10/scalajson.ast/unsafe/JValue.scala @@ -76,6 +76,16 @@ final case class JNumber(value: String) extends JValue { case jNumberRegex(_ *) => new ast.JNumber(value) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Int] = scalajson.ast.toInt(value) + + def toBigInt: Option[BigInt] = scalajson.ast.toBigInt(value) + + def toLong: Option[Long] = scalajson.ast.toLong(value) + + def toDouble: Option[Double] = scalajson.ast.toDouble(value) + + def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value) } /** Represents a JSON Boolean value, which can either be a diff --git a/jvm/src/main/scala/scalajson/ast/JValue.scala b/jvm/src/main/scala/scalajson/ast/JValue.scala index eafbe62..ba62f3d 100644 --- a/jvm/src/main/scala/scalajson/ast/JValue.scala +++ b/jvm/src/main/scala/scalajson/ast/JValue.scala @@ -106,6 +106,16 @@ final case class JNumber private[ast] (value: String) extends JValue { case jNumberRegex(_ *) => new JNumber(value) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Int] = scalajson.ast.toInt(value) + + def toBigInt: Option[BigInt] = scalajson.ast.toBigInt(value) + + def toLong: Option[Long] = scalajson.ast.toLong(value) + + def toDouble: Option[Double] = scalajson.ast.toDouble(value) + + def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value) } /** Represents a JSON Boolean value, which can either be a diff --git a/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala b/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala index 435451c..738e55d 100644 --- a/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala +++ b/jvm/src/main/scala/scalajson/ast/unsafe/JValue.scala @@ -76,6 +76,16 @@ final case class JNumber(value: String) extends JValue { case jNumberRegex(_ *) => new ast.JNumber(value) case _ => throw new NumberFormatException(value) } + + def toInt: Option[Int] = scalajson.ast.toInt(value) + + def toBigInt: Option[BigInt] = scalajson.ast.toBigInt(value) + + def toLong: Option[Long] = scalajson.ast.toLong(value) + + def toDouble: Option[Double] = scalajson.ast.toDouble(value) + + def toBigDecimal: Option[BigDecimal] = scalajson.ast.toBigDecimal(value) } /** Represents a JSON Boolean value, which can either be a diff --git a/shared/src/main/scala-2.10/scalajson.ast/package.scala b/shared/src/main/scala-2.10/scalajson.ast/package.scala index 3f6f7b3..d2ec6f7 100644 --- a/shared/src/main/scala-2.10/scalajson.ast/package.scala +++ b/shared/src/main/scala-2.10/scalajson.ast/package.scala @@ -1,5 +1,7 @@ package scalajson +import java.math.MathContext + import scala.util.matching.Regex package object ast { @@ -343,4 +345,280 @@ package object ast { } } else pa == pb } + + @inline private[ast] def radix: Int = 10 + + private[ast] def toInt(value: String): Option[Int] = { + @inline def maxLengthConstant: Int = 10 + var limit: Int = -Integer.MAX_VALUE + var decimalFlag = false + var result: BigInt = 0 + var negative = false + var multmin: Int = 0 + var char: Char = 0 + var i = 0 + var eFlag = false + var trailingZeroes: BigInt = 0 + var negativeEFlag: Boolean = false + var resultNegativeEFlag: BigInt = 0 + var digitLength: BigInt = 0 + multmin = limit / radix + if (value.charAt(0) == '-') { + limit = Integer.MIN_VALUE + negative = true + i += 1 + } + + while (i < value.length) { + char = value.charAt(i) + if (char == '.') + decimalFlag = true + else if ((char | 0x20) == 'e') { + eFlag = true + val charNext = value.charAt(i + 1) + if (charNext == '-') + negativeEFlag = true + if (negativeEFlag || charNext == '+') { + i += 1 + } + } else { + if (!(eFlag || decimalFlag)) + digitLength += 1 + + val digit = Character.digit(char, radix) + + if (digit == 0) + if (!decimalFlag) + trailingZeroes += 1 + else if (!decimalFlag) + trailingZeroes = 0 + + if (digit != 0 && (decimalFlag || eFlag)) + if (!negativeEFlag) + return None + else { + if (trailingZeroes != 0) { + resultNegativeEFlag *= radix + resultNegativeEFlag += digit + if (trailingZeroes >= resultNegativeEFlag) { + var i2: Int = 0 + while (i2 < resultNegativeEFlag) { + result = result / radix + i2 += 1 + } + } else + return None + } + } + + val maxLenCheck = digitLength <= maxLengthConstant + + if (result < multmin && maxLenCheck && !eFlag) + return None + + if (!(digit == 0 && (decimalFlag || eFlag))) { + + if (!negativeEFlag) { + result *= radix + if (result < limit + digit && maxLenCheck) + return None + result -= digit + } + } + } + i += 1 + } + if (result < limit) + None + else { + if (negative) + Some(result.toInt) + else + Some(-result.toInt) + } + } + + private[ast] def toLong(value: String): Option[Long] = { + @inline def maxLengthConstant: Int = 19 + var limit: Long = -Long.MaxValue + var decimalFlag = false + var result: BigInt = 0 + var negative = false + var multmin: Long = 0 + var char: Char = 0 + var i = 0 + var eFlag = false + var trailingZeroes: BigInt = 0 + var negativeEFlag: Boolean = false + var resultNegativeEFlag: BigInt = 0 + var digitLength: BigInt = 0 + multmin = limit / radix + if (value.charAt(0) == '-') { + limit = Long.MinValue + negative = true + i += 1 + } + + while (i < value.length) { + char = value.charAt(i) + if (char == '.') + decimalFlag = true + else if ((char | 0x20) == 'e') { + eFlag = true + val charNext = value.charAt(i + 1) + if (charNext == '-') + negativeEFlag = true + if (negativeEFlag || charNext == '+') { + i += 1 + } + } else { + if (!(eFlag || decimalFlag)) + digitLength += 1 + + val digit = Character.digit(char, radix) + + if (digit == 0) + if (!decimalFlag) + trailingZeroes += 1 + else if (!decimalFlag) + trailingZeroes = 0 + + if (digit != 0 && (decimalFlag || eFlag)) + if (!negativeEFlag) + return None + else { + if (trailingZeroes != 0) { + resultNegativeEFlag *= radix + resultNegativeEFlag += digit + if (trailingZeroes >= resultNegativeEFlag) { + var i2: Int = 0 + while (i2 < resultNegativeEFlag) { + result = result / radix + i2 += 1 + } + } else + return None + } + } + + val maxLenCheck = digitLength <= maxLengthConstant + + if (result < multmin && maxLenCheck && !eFlag) + return None + + if (!(digit == 0 && (decimalFlag || eFlag))) { + + if (!negativeEFlag) { + result *= radix + if (result < limit + digit && maxLenCheck) + return None + result -= digit + } + } + } + i += 1 + } + if (result < limit) + None + else { + if (negative) + Some(result.toLong) + else + Some(-result.toLong) + } + } + + private[ast] def toBigInt(value: String): Option[BigInt] = { + var decimalFlag = false + var result: BigInt = 0 + var negative = false + var char: Char = 0 + var i = 0 + var eFlag = false + var trailingZeroes: BigInt = 0 + var negativeEFlag: Boolean = false + var resultNegativeEFlag: BigInt = 0 + var digitLength: BigInt = 0 + if (value.charAt(0) == '-') { + negative = true + i += 1 + } + + while (i < value.length) { + char = value.charAt(i) + if (char == '.') + decimalFlag = true + else if ((char | 0x20) == 'e') { + eFlag = true + val charNext = value.charAt(i + 1) + if (charNext == '-') + negativeEFlag = true + if (negativeEFlag || charNext == '+') { + i += 1 + } + } else { + if (!(eFlag || decimalFlag)) + digitLength += 1 + + val digit = Character.digit(char, radix) + + if (digit == 0) + if (!decimalFlag) + trailingZeroes += 1 + else if (!decimalFlag) + trailingZeroes = 0 + + if (digit != 0 && (decimalFlag || eFlag)) + if (!negativeEFlag) + return None + else { + if (trailingZeroes != 0) { + resultNegativeEFlag *= radix + resultNegativeEFlag += digit + if (trailingZeroes >= resultNegativeEFlag) { + var i2: Int = 0 + while (i2 < resultNegativeEFlag) { + result = result / radix + i2 += 1 + } + } else + return None + } + } + + if (!(digit == 0 && (decimalFlag || eFlag))) { + + if (!negativeEFlag) { + result *= radix + result -= digit + } + } + } + i += 1 + } + if (negative) + Some(result) + else + Some(-result) + } + + private[ast] def toDouble(value: String): Option[Double] = { + try { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } catch { + case _: java.lang.NumberFormatException => None + } + } + + private[ast] def toBigDecimal(value: String): Option[BigDecimal] = { + try { + Some(BigDecimal(value, MathContext.UNLIMITED)) + } catch { + case _: java.lang.NumberFormatException => None + } + } } diff --git a/shared/src/main/scala/scalajson/ast/package.scala b/shared/src/main/scala/scalajson/ast/package.scala index 3f6f7b3..d2ec6f7 100644 --- a/shared/src/main/scala/scalajson/ast/package.scala +++ b/shared/src/main/scala/scalajson/ast/package.scala @@ -1,5 +1,7 @@ package scalajson +import java.math.MathContext + import scala.util.matching.Regex package object ast { @@ -343,4 +345,280 @@ package object ast { } } else pa == pb } + + @inline private[ast] def radix: Int = 10 + + private[ast] def toInt(value: String): Option[Int] = { + @inline def maxLengthConstant: Int = 10 + var limit: Int = -Integer.MAX_VALUE + var decimalFlag = false + var result: BigInt = 0 + var negative = false + var multmin: Int = 0 + var char: Char = 0 + var i = 0 + var eFlag = false + var trailingZeroes: BigInt = 0 + var negativeEFlag: Boolean = false + var resultNegativeEFlag: BigInt = 0 + var digitLength: BigInt = 0 + multmin = limit / radix + if (value.charAt(0) == '-') { + limit = Integer.MIN_VALUE + negative = true + i += 1 + } + + while (i < value.length) { + char = value.charAt(i) + if (char == '.') + decimalFlag = true + else if ((char | 0x20) == 'e') { + eFlag = true + val charNext = value.charAt(i + 1) + if (charNext == '-') + negativeEFlag = true + if (negativeEFlag || charNext == '+') { + i += 1 + } + } else { + if (!(eFlag || decimalFlag)) + digitLength += 1 + + val digit = Character.digit(char, radix) + + if (digit == 0) + if (!decimalFlag) + trailingZeroes += 1 + else if (!decimalFlag) + trailingZeroes = 0 + + if (digit != 0 && (decimalFlag || eFlag)) + if (!negativeEFlag) + return None + else { + if (trailingZeroes != 0) { + resultNegativeEFlag *= radix + resultNegativeEFlag += digit + if (trailingZeroes >= resultNegativeEFlag) { + var i2: Int = 0 + while (i2 < resultNegativeEFlag) { + result = result / radix + i2 += 1 + } + } else + return None + } + } + + val maxLenCheck = digitLength <= maxLengthConstant + + if (result < multmin && maxLenCheck && !eFlag) + return None + + if (!(digit == 0 && (decimalFlag || eFlag))) { + + if (!negativeEFlag) { + result *= radix + if (result < limit + digit && maxLenCheck) + return None + result -= digit + } + } + } + i += 1 + } + if (result < limit) + None + else { + if (negative) + Some(result.toInt) + else + Some(-result.toInt) + } + } + + private[ast] def toLong(value: String): Option[Long] = { + @inline def maxLengthConstant: Int = 19 + var limit: Long = -Long.MaxValue + var decimalFlag = false + var result: BigInt = 0 + var negative = false + var multmin: Long = 0 + var char: Char = 0 + var i = 0 + var eFlag = false + var trailingZeroes: BigInt = 0 + var negativeEFlag: Boolean = false + var resultNegativeEFlag: BigInt = 0 + var digitLength: BigInt = 0 + multmin = limit / radix + if (value.charAt(0) == '-') { + limit = Long.MinValue + negative = true + i += 1 + } + + while (i < value.length) { + char = value.charAt(i) + if (char == '.') + decimalFlag = true + else if ((char | 0x20) == 'e') { + eFlag = true + val charNext = value.charAt(i + 1) + if (charNext == '-') + negativeEFlag = true + if (negativeEFlag || charNext == '+') { + i += 1 + } + } else { + if (!(eFlag || decimalFlag)) + digitLength += 1 + + val digit = Character.digit(char, radix) + + if (digit == 0) + if (!decimalFlag) + trailingZeroes += 1 + else if (!decimalFlag) + trailingZeroes = 0 + + if (digit != 0 && (decimalFlag || eFlag)) + if (!negativeEFlag) + return None + else { + if (trailingZeroes != 0) { + resultNegativeEFlag *= radix + resultNegativeEFlag += digit + if (trailingZeroes >= resultNegativeEFlag) { + var i2: Int = 0 + while (i2 < resultNegativeEFlag) { + result = result / radix + i2 += 1 + } + } else + return None + } + } + + val maxLenCheck = digitLength <= maxLengthConstant + + if (result < multmin && maxLenCheck && !eFlag) + return None + + if (!(digit == 0 && (decimalFlag || eFlag))) { + + if (!negativeEFlag) { + result *= radix + if (result < limit + digit && maxLenCheck) + return None + result -= digit + } + } + } + i += 1 + } + if (result < limit) + None + else { + if (negative) + Some(result.toLong) + else + Some(-result.toLong) + } + } + + private[ast] def toBigInt(value: String): Option[BigInt] = { + var decimalFlag = false + var result: BigInt = 0 + var negative = false + var char: Char = 0 + var i = 0 + var eFlag = false + var trailingZeroes: BigInt = 0 + var negativeEFlag: Boolean = false + var resultNegativeEFlag: BigInt = 0 + var digitLength: BigInt = 0 + if (value.charAt(0) == '-') { + negative = true + i += 1 + } + + while (i < value.length) { + char = value.charAt(i) + if (char == '.') + decimalFlag = true + else if ((char | 0x20) == 'e') { + eFlag = true + val charNext = value.charAt(i + 1) + if (charNext == '-') + negativeEFlag = true + if (negativeEFlag || charNext == '+') { + i += 1 + } + } else { + if (!(eFlag || decimalFlag)) + digitLength += 1 + + val digit = Character.digit(char, radix) + + if (digit == 0) + if (!decimalFlag) + trailingZeroes += 1 + else if (!decimalFlag) + trailingZeroes = 0 + + if (digit != 0 && (decimalFlag || eFlag)) + if (!negativeEFlag) + return None + else { + if (trailingZeroes != 0) { + resultNegativeEFlag *= radix + resultNegativeEFlag += digit + if (trailingZeroes >= resultNegativeEFlag) { + var i2: Int = 0 + while (i2 < resultNegativeEFlag) { + result = result / radix + i2 += 1 + } + } else + return None + } + } + + if (!(digit == 0 && (decimalFlag || eFlag))) { + + if (!negativeEFlag) { + result *= radix + result -= digit + } + } + } + i += 1 + } + if (negative) + Some(result) + else + Some(-result) + } + + private[ast] def toDouble(value: String): Option[Double] = { + try { + val asDouble = value.toDouble + if (BigDecimal(value) == BigDecimal(asDouble)) + Some(asDouble) + else + None + } catch { + case _: java.lang.NumberFormatException => None + } + } + + private[ast] def toBigDecimal(value: String): Option[BigDecimal] = { + try { + Some(BigDecimal(value, MathContext.UNLIMITED)) + } catch { + case _: java.lang.NumberFormatException => None + } + } }