mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-11 04:45:57 +03:00
Daml lf type safty (Decimal) (#1098)
* daml-lf: make Decimal type safe * daml-lf: create Utf8String type * daml-lf: cleanup in data package * Address Stephen Comments * daml-lf: remove UTF8String
This commit is contained in:
parent
388a2b49f7
commit
c7df212d42
@ -43,7 +43,7 @@ abstract class Reader[+Pkg] {
|
||||
final def readArchiveAndVersion(lf: DamlLf.Archive): (Pkg, LanguageMajorVersion) = {
|
||||
lf.getHashFunction match {
|
||||
case DamlLf.HashFunction.SHA256 =>
|
||||
val payload = lf.getPayload.toByteArray()
|
||||
val payload = lf.getPayload.toByteArray
|
||||
val theirHash = PackageId.fromString(lf.getHash) match {
|
||||
case Right(hash) => hash
|
||||
case Left(err) => throw ParseError(s"Invalid hash: $err")
|
||||
|
@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package com.digitalasset.daml.lf.data
|
||||
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
final class ArrayFactory[T](implicit classTag: ClassTag[T]) {
|
||||
|
||||
def apply(xs: T*): Array[T] = xs.toArray
|
||||
|
||||
def ofDim(n: Int): Array[T] = Array.ofDim(n)
|
||||
|
||||
val empty: Array[T] = ofDim(0)
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.digitalasset.daml.lf.data
|
||||
import java.math.MathContext
|
||||
|
||||
import scala.math.BigDecimal
|
||||
|
||||
/** The model of our floating point decimal numbers.
|
||||
*
|
||||
* These are numbers of precision 38 (38 decimal digits), and scale 10 (10 digits after the comma)
|
||||
*/
|
||||
object Decimal {
|
||||
type Decimal = BigDecimal
|
||||
|
||||
val scale: Int = 10
|
||||
val context: MathContext = new MathContext(38, java.math.RoundingMode.HALF_EVEN)
|
||||
|
||||
private[this] def unlimitedBigDecimal(s: String) =
|
||||
BigDecimal.decimal(new java.math.BigDecimal(s), MathContext.UNLIMITED)
|
||||
private[this] def unlimitedBigDecimal(x: Long) =
|
||||
BigDecimal(new java.math.BigDecimal(x, MathContext.UNLIMITED))
|
||||
|
||||
// we use these to compare only, therefore set the precision to unlimited to make sure
|
||||
// we can compare every number we're given
|
||||
val max: Decimal = unlimitedBigDecimal("9999999999999999999999999999.9999999999")
|
||||
|
||||
val min: Decimal = unlimitedBigDecimal("-9999999999999999999999999999.9999999999")
|
||||
|
||||
/** Checks that a `Decimal` falls between `min` and `max`, and
|
||||
* round the number according to `scale`. Note that it does _not_
|
||||
* fail if the number contains data beyond `scale`.
|
||||
*/
|
||||
def checkWithinBoundsAndRound(x0: Decimal): Either[String, Decimal] = {
|
||||
if (x0 > max || x0 < min) {
|
||||
Left(s"out-of-bounds Decimal $x0")
|
||||
} else {
|
||||
val x1 = new BigDecimal(x0.bigDecimal, context)
|
||||
val x2 = x1.setScale(scale, BigDecimal.RoundingMode.HALF_EVEN)
|
||||
Right(x2)
|
||||
}
|
||||
}
|
||||
|
||||
/** Like `checkWithinBoundsAndRound`, but _fails_ if the given number contains
|
||||
* any data beyond `scale`.
|
||||
*/
|
||||
def checkWithinBoundsAndWithinScale(x0: Decimal): Either[String, Decimal] = {
|
||||
for {
|
||||
x1 <- checkWithinBoundsAndRound(x0)
|
||||
// if we've lost any data at all, it means that we weren't within the
|
||||
// scale.
|
||||
x2 <- Either.cond(x0 == x1, x1, s"out-of-bounds Decimal $x0")
|
||||
} yield x2
|
||||
}
|
||||
|
||||
def add(x: Decimal, y: Decimal): Either[String, Decimal] = checkWithinBoundsAndRound(x + y)
|
||||
def div(x: Decimal, y: Decimal): Either[String, Decimal] = checkWithinBoundsAndRound(x / y)
|
||||
def mult(x: Decimal, y: Decimal): Either[String, Decimal] = checkWithinBoundsAndRound(x * y)
|
||||
def sub(x: Decimal, y: Decimal): Either[String, Decimal] = checkWithinBoundsAndRound(x - y)
|
||||
|
||||
def round(newScale: Long, x0: Decimal): Either[String, Decimal] =
|
||||
// check to make sure the rounding mode is OK
|
||||
checkWithinBoundsAndRound(x0).flatMap(x =>
|
||||
if (newScale > scale || newScale < -27) {
|
||||
Left(s"Bad scale $newScale, must be between -27 and $scale")
|
||||
} else {
|
||||
// we know toIntExact won't crash because we checked the scale above
|
||||
// we set the scale again to make sure that every Decimal has scale 10, which
|
||||
// affects equality
|
||||
Right(
|
||||
x.setScale(Math.toIntExact(newScale), BigDecimal.RoundingMode.HALF_EVEN)
|
||||
.setScale(scale))
|
||||
})
|
||||
|
||||
private val hasExpectedFormat =
|
||||
"""[+-]?\d{1,28}(\.\d{1,10})?""".r.pattern
|
||||
|
||||
def fromString(s: String): Either[String, Decimal] =
|
||||
if (hasExpectedFormat.matcher(s).matches())
|
||||
checkWithinBoundsAndWithinScale(unlimitedBigDecimal(s))
|
||||
else
|
||||
Left(s"""Could not read Decimal string "$s"""")
|
||||
|
||||
def assertFromString(s: String): Decimal =
|
||||
assert(fromString(s))
|
||||
|
||||
def toString(d: Decimal): String = {
|
||||
// Strip the trailing zeros (which BigDecimal keeps if the string
|
||||
// it was created from had them), and use the plain notation rather
|
||||
// than scientific notation.
|
||||
//
|
||||
// Moreover, add a single trailing zero if we have no decimal part.
|
||||
// this mimicks the behavior of `formatScientific Fixed Nothing` which
|
||||
// we've been using in Haskell to render Decimals
|
||||
// http://hackage.haskell.org/package/scientific-0.3.6.2/docs/Data-Scientific.html#v:formatScientific
|
||||
val s = d.bigDecimal.stripTrailingZeros.toPlainString
|
||||
if (s.contains(".")) {
|
||||
s
|
||||
} else {
|
||||
s + ".0"
|
||||
}
|
||||
}
|
||||
|
||||
def fromLong(x: Long): Decimal =
|
||||
BigDecimal(new java.math.BigDecimal(x, context)).setScale(scale)
|
||||
|
||||
private val toLongLowerBound = unlimitedBigDecimal(Long.MinValue) - 1
|
||||
private val toLongUpperBound = unlimitedBigDecimal(Long.MaxValue) + 1
|
||||
|
||||
def toLong(x: Decimal): Either[String, Long] = {
|
||||
if (toLongLowerBound < x && x < toLongUpperBound)
|
||||
Right(x.longValue)
|
||||
else
|
||||
Left(s"Decimal $x does not fit into an Int64")
|
||||
}
|
||||
|
||||
private def assert[X](either: Either[String, X]): X =
|
||||
either.fold(e => throw new IllegalArgumentException(e), identity)
|
||||
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.digitalasset.daml.lf.data
|
||||
|
||||
import java.math.MathContext
|
||||
|
||||
import scala.math.BigDecimal
|
||||
|
||||
/** The model of our floating point decimal numbers.
|
||||
*
|
||||
* These are numbers of precision 38 (38 decimal digits), and scale 10 (10 digits after the comma)
|
||||
*/
|
||||
abstract class DecimalModule {
|
||||
|
||||
type T <: BigDecimal
|
||||
|
||||
protected def cast(x: BigDecimal): T
|
||||
|
||||
val scale: Int = 10
|
||||
val context: MathContext = new MathContext(38, java.math.RoundingMode.HALF_EVEN)
|
||||
|
||||
private def unlimitedBigDecimal(s: String): T =
|
||||
cast(BigDecimal.decimal(new java.math.BigDecimal(s), MathContext.UNLIMITED))
|
||||
|
||||
private def unlimitedBigDecimal(x: Long): T =
|
||||
cast(BigDecimal(new java.math.BigDecimal(x, MathContext.UNLIMITED)))
|
||||
|
||||
// we use these to compare only, therefore set the precision to unlimited to make sure
|
||||
// we can compare every number we're given
|
||||
val max: T = unlimitedBigDecimal("9999999999999999999999999999.9999999999")
|
||||
|
||||
val min: T = unlimitedBigDecimal("-9999999999999999999999999999.9999999999")
|
||||
|
||||
/** Checks that a `T` falls between `min` and `max`, and
|
||||
* round the number according to `scale`. Note that it does _not_
|
||||
* fail if the number contains data beyond `scale`.
|
||||
*/
|
||||
private def checkWithinBoundsAndRound(x0: BigDecimal): Either[String, T] = {
|
||||
if (x0 > max || x0 < min) {
|
||||
Left(s"out-of-bounds Decimal $x0")
|
||||
} else {
|
||||
val x1 = new BigDecimal(x0.bigDecimal, context)
|
||||
val x2 = x1.setScale(scale, BigDecimal.RoundingMode.HALF_EVEN)
|
||||
Right(cast(x2))
|
||||
}
|
||||
}
|
||||
|
||||
/** Like `checkWithinBoundsAndRound`, but _fails_ if the given number contains
|
||||
* any data beyond `scale`.
|
||||
*/
|
||||
final def fromBigDecimal(x0: BigDecimal): Either[String, T] =
|
||||
for {
|
||||
x1 <- checkWithinBoundsAndRound(x0)
|
||||
// if we've lost any data at all, it means that we weren't within the
|
||||
// scale.
|
||||
x2 <- Either.cond(x0 == x1, x1, s"out-of-bounds Decimal $x0")
|
||||
} yield x2
|
||||
|
||||
final def assertFromBigDecimal(x: BigDecimal): T =
|
||||
assert(fromBigDecimal(x))
|
||||
|
||||
final def add(x: T, y: T): Either[String, T] = checkWithinBoundsAndRound(x + y)
|
||||
|
||||
final def div(x: T, y: T): Either[String, T] = checkWithinBoundsAndRound(x / y)
|
||||
|
||||
final def mult(x: T, y: T): Either[String, T] = checkWithinBoundsAndRound(x * y)
|
||||
|
||||
final def sub(x: T, y: T): Either[String, T] = checkWithinBoundsAndRound(x - y)
|
||||
|
||||
final def round(newScale: Long, x0: T): Either[String, T] =
|
||||
// check to make sure the rounding mode is OK
|
||||
checkWithinBoundsAndRound(x0).flatMap(
|
||||
x =>
|
||||
if (newScale > scale || newScale < -27)
|
||||
Left(s"Bad scale $newScale, must be between -27 and $scale")
|
||||
else
|
||||
// we know toIntExact won't crash because we checked the scale above
|
||||
// we set the scale again to make sure that every Decimal has scale 10, which
|
||||
// affects equality
|
||||
Right(
|
||||
cast(
|
||||
x.setScale(Math.toIntExact(newScale), BigDecimal.RoundingMode.HALF_EVEN)
|
||||
.setScale(scale))))
|
||||
|
||||
private val hasExpectedFormat =
|
||||
"""[+-]?\d{1,28}(\.\d{1,10})?""".r.pattern
|
||||
|
||||
final def fromString(s: String): Either[String, T] =
|
||||
if (hasExpectedFormat.matcher(s).matches())
|
||||
fromBigDecimal(unlimitedBigDecimal(s))
|
||||
else
|
||||
Left(s"""Could not read Decimal string "$s"""")
|
||||
|
||||
@throws[IllegalArgumentException]
|
||||
final def assertFromString(s: String): T =
|
||||
assert(fromString(s))
|
||||
|
||||
final def toString(d: T): String = {
|
||||
// Strip the trailing zeros (which BigDecimal keeps if the string
|
||||
// it was created from had them), and use the plain notation rather
|
||||
// than scientific notation.
|
||||
//
|
||||
// Moreover, add a single trailing zero if we have no decimal part.
|
||||
// this mimicks the behavior of `formatScientific Fixed Nothing` which
|
||||
// we've been using in Haskell to render Decimals
|
||||
// http://hackage.haskell.org/package/scientific-0.3.6.2/docs/Data-Scientific.html#v:formatScientific
|
||||
val s = d.bigDecimal.stripTrailingZeros.toPlainString
|
||||
if (s.contains(".")) s else s + ".0"
|
||||
}
|
||||
|
||||
final def fromLong(x: Long): T =
|
||||
cast(BigDecimal(new java.math.BigDecimal(x, context)).setScale(scale))
|
||||
|
||||
private val toLongLowerBound = unlimitedBigDecimal(Long.MinValue) - 1
|
||||
private val toLongUpperBound = unlimitedBigDecimal(Long.MaxValue) + 1
|
||||
|
||||
final def toLong(x: T): Either[String, Long] =
|
||||
Either.cond(
|
||||
toLongLowerBound < x && x < toLongUpperBound,
|
||||
x.longValue,
|
||||
s"Decimal $x does not fit into an Int64")
|
||||
|
||||
}
|
@ -1,21 +1,20 @@
|
||||
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.digitalasset.daml.lf.data
|
||||
|
||||
import scalaz.Equal
|
||||
|
||||
import scala.reflect.ClassTag
|
||||
import scala.util.matching.Regex
|
||||
|
||||
sealed abstract class MatchingStringModule {
|
||||
|
||||
type T <: String
|
||||
|
||||
def fromString(s: String): Either[String, T]
|
||||
|
||||
@throws[IllegalArgumentException]
|
||||
final def assertFromString(s: String): T =
|
||||
fromString(s).fold(e => throw new IllegalArgumentException(e), identity)
|
||||
assert(fromString(s))
|
||||
|
||||
def equalInstance: Equal[T]
|
||||
|
||||
@ -25,16 +24,6 @@ sealed abstract class MatchingStringModule {
|
||||
// * https://github.com/digital-asset/daml/pull/983#discussion_r282513324
|
||||
// * https://github.com/scala/bug/issues/9565
|
||||
val Array: ArrayFactory[T]
|
||||
|
||||
}
|
||||
|
||||
sealed abstract class ArrayFactory[T](implicit classTag: ClassTag[T]) {
|
||||
|
||||
def apply(xs: T*): Array[T] = xs.toArray
|
||||
|
||||
def ofDim(n: Int): Array[T] = Array.ofDim(n)
|
||||
|
||||
val empty: Array[T] = ofDim(0)
|
||||
}
|
||||
|
||||
object MatchingStringModule extends (Regex => MatchingStringModule) {
|
||||
@ -49,7 +38,7 @@ object MatchingStringModule extends (Regex => MatchingStringModule) {
|
||||
|
||||
def equalInstance: Equal[T] = scalaz.std.string.stringInstance
|
||||
|
||||
val Array: ArrayFactory[T] = new ArrayFactory[T] {}
|
||||
val Array: ArrayFactory[T] = new ArrayFactory[T]
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -55,6 +55,8 @@ object Ref {
|
||||
}
|
||||
|
||||
object DottedName {
|
||||
type T = DottedName
|
||||
|
||||
def fromString(s: String): Either[String, DottedName] =
|
||||
if (s.isEmpty)
|
||||
Left(s"Expected a non-empty string")
|
||||
@ -62,7 +64,7 @@ object Ref {
|
||||
fromSegments(split(s, '.').toSeq)
|
||||
|
||||
@throws[IllegalArgumentException]
|
||||
def assertFromString(s: String): DottedName =
|
||||
final def assertFromString(s: String): T =
|
||||
assert(fromString(s))
|
||||
|
||||
def fromSegments(strings: Iterable[String]): Either[String, DottedName] = {
|
||||
@ -102,6 +104,8 @@ object Ref {
|
||||
def qualifiedName: String = toString
|
||||
}
|
||||
object QualifiedName {
|
||||
type T = QualifiedName
|
||||
|
||||
def fromString(s: String): Either[String, QualifiedName] = {
|
||||
val segments = split(s, ':')
|
||||
if (segments.length != 2)
|
||||
@ -115,7 +119,7 @@ object Ref {
|
||||
}
|
||||
|
||||
@throws[IllegalArgumentException]
|
||||
def assertFromString(s: String): QualifiedName =
|
||||
final def assertFromString(s: String): T =
|
||||
assert(fromString(s))
|
||||
}
|
||||
|
||||
@ -153,7 +157,4 @@ object Ref {
|
||||
type TypeConName = Identifier
|
||||
val TypeConName = Identifier
|
||||
|
||||
private def assert[X](either: Either[String, X]): X =
|
||||
either.fold(e => throw new IllegalArgumentException(e), identity)
|
||||
|
||||
}
|
||||
|
@ -4,13 +4,14 @@
|
||||
package com.digitalasset.daml.lf.data
|
||||
|
||||
import scalaz.Equal
|
||||
import scalaz.std.string._
|
||||
import scalaz.std.tuple._
|
||||
import scalaz.std.string._
|
||||
import scalaz.syntax.equal._
|
||||
|
||||
import scala.collection.immutable.HashMap
|
||||
|
||||
/** We use this container to pass around DAML-LF maps as flat lists in various parts of the codebase. */
|
||||
// Note that keys are ordered using Utf8 ordering
|
||||
final class SortedLookupList[+X] private (entries: ImmArray[(String, X)]) extends Equals {
|
||||
|
||||
def mapValue[Y](f: X => Y) = new SortedLookupList(entries.map { case (k, v) => k -> f(v) })
|
||||
@ -40,16 +41,13 @@ final class SortedLookupList[+X] private (entries: ImmArray[(String, X)]) extend
|
||||
|
||||
object SortedLookupList {
|
||||
|
||||
// Note: it's important that this ordering is the same as the DAML-LF ordering.
|
||||
private implicit val keyOrdering: Ordering[String] = UTF8.ordering
|
||||
|
||||
def fromImmArray[X](entries: ImmArray[(String, X)]): Either[String, SortedLookupList[X]] = {
|
||||
entries.toSeq
|
||||
.groupBy(_._1)
|
||||
.collectFirst {
|
||||
case (k, l) if l.size > 1 => s"key $k duplicated when trying to build map"
|
||||
}
|
||||
.toLeft(new SortedLookupList(entries.toSeq.sortBy(_._1).toImmArray))
|
||||
.toLeft(new SortedLookupList(entries.toSeq.sortBy(_._1)(Utf8.Ordering).toImmArray))
|
||||
}
|
||||
|
||||
def fromSortedImmArray[X](entries: ImmArray[(String, X)]): Either[String, SortedLookupList[X]] = {
|
||||
@ -58,7 +56,7 @@ object SortedLookupList {
|
||||
.toSeq
|
||||
.sliding(2)
|
||||
.collectFirst {
|
||||
case Seq(k1, k2) if keyOrdering.gteq(k1, k2) => s"the list $entries is not sorted by key"
|
||||
case Seq(k1, k2) if Utf8.Ordering.gteq(k1, k2) => s"the list $entries is not sorted by key"
|
||||
}
|
||||
.toLeft(new SortedLookupList(entries))
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ object Time {
|
||||
|
||||
object Date {
|
||||
|
||||
type T = Date
|
||||
|
||||
private def apply(days: Int): Date =
|
||||
new Date(days)
|
||||
|
||||
@ -59,13 +61,13 @@ object Time {
|
||||
.map(_ => s"cannot interpret $str as Date")
|
||||
.flatMap(fromDaysSinceEpoch)
|
||||
|
||||
@throws[IllegalArgumentException]
|
||||
final def assertFromString(s: String): T =
|
||||
assert(fromString(s))
|
||||
|
||||
def assertFromDaysSinceEpoch(days: Int): Date =
|
||||
assert(fromDaysSinceEpoch(days))
|
||||
|
||||
@throws[IllegalArgumentException]
|
||||
def assertFromString(str: String): Date =
|
||||
assert(fromString(str))
|
||||
|
||||
}
|
||||
|
||||
case class Timestamp private (micros: Long) extends Ordered[Timestamp] {
|
||||
@ -89,6 +91,8 @@ object Time {
|
||||
|
||||
object Timestamp {
|
||||
|
||||
type T = Timestamp
|
||||
|
||||
private def apply(micros: Long): Timestamp =
|
||||
new Timestamp(micros)
|
||||
|
||||
@ -129,8 +133,8 @@ object Time {
|
||||
.flatMap(fromLong)
|
||||
|
||||
@throws[IllegalArgumentException]
|
||||
def assertFromString(str: String): Timestamp =
|
||||
assertFromLong(assertMicrosFromString(str))
|
||||
final def assertFromString(s: String): T =
|
||||
assert(fromString(s))
|
||||
|
||||
def fromInstant(i: Instant): Either[String, Timestamp] =
|
||||
Try(assertMicrosFromInstant(i)).toEither.left
|
||||
@ -142,10 +146,4 @@ object Time {
|
||||
|
||||
}
|
||||
|
||||
private def assert[X](e: Either[String, X]): X =
|
||||
e match {
|
||||
case Left(err) => throw new IllegalArgumentException(err)
|
||||
case Right(date) => date
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,70 +0,0 @@
|
||||
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.digitalasset.daml.lf.data
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.mutable
|
||||
|
||||
// This object defines functions to emulates UTF8 string
|
||||
// behavior while still using underlying UTF-16 encoding.
|
||||
object UTF8 {
|
||||
|
||||
// The DAML-LF strings are supposed to be UTF-8.
|
||||
// However standard "exploding" java/scala methods like
|
||||
// _.toList split in Character which are not Unicode codepoint.
|
||||
def explode(s: String): ImmArray[String] = {
|
||||
val len = s.length
|
||||
val arr = new mutable.ArraySeq[String](s.codePointCount(0, len))
|
||||
var i = 0
|
||||
var j = 0
|
||||
while (i < len) {
|
||||
// if s(i) is a high surrogate the current codepoint uses 2 chars
|
||||
val next = if (s(i).isHighSurrogate) i + 2 else i + 1
|
||||
arr(j) = s.substring(i, next)
|
||||
j += 1
|
||||
i = next
|
||||
}
|
||||
ImmArray.unsafeFromArraySeq(arr)
|
||||
}
|
||||
|
||||
// The DAML-LF should sort string according UTF-8 encoding.
|
||||
// Java standard string ordering uses UTF-16, which does not match
|
||||
// expected one. Note that unlike UTF-16, UTF-8 ordering matches
|
||||
// Unicode ordering, so we can order by codepoints.
|
||||
//
|
||||
// For instance consider the two following unicode code points:
|
||||
//
|
||||
// 。 (Unicode 0x00ff61, UTF-8 [0xef, 0xbd, 0xa1], , UTF-16 [0xff61])
|
||||
// 😂 (Unicode 0x01f602, UTF-8 [0xf0, 0x9f, 0x98, 0x82], UTF-16 [0xd83d, 0xde02])
|
||||
//
|
||||
// The comparison "。" < "😂" returns false in java/scala, but it
|
||||
// should return true. Note it returns True in Haskell.
|
||||
//
|
||||
// See https://ssl.icu-project.org/docs/papers/utf16_code_point_order.html
|
||||
// for more explanations.
|
||||
|
||||
val ordering: Ordering[String] = new Ordering[String] {
|
||||
override def compare(xs: String, ys: String): Int = {
|
||||
val lim = xs.length min ys.length
|
||||
@tailrec def lp(i: Int): Int =
|
||||
if (i < lim) {
|
||||
val x = xs(i)
|
||||
val y = ys(i)
|
||||
if (x != y) {
|
||||
// If x is a low surrogate, then the current codepoint starts at the
|
||||
// previous char, otherwise the codepoint starts at the current char.
|
||||
val j = if (x.isLowSurrogate) i - 1 else i
|
||||
xs.codePointAt(j) - ys.codePointAt(j)
|
||||
} else lp(i + 1)
|
||||
} else xs.length - ys.length
|
||||
lp(0)
|
||||
}
|
||||
}
|
||||
|
||||
def getBytes(s: String): Array[Byte] =
|
||||
s.getBytes(StandardCharsets.UTF_8)
|
||||
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.digitalasset.daml.lf.data
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
// The DAML-LF strings are supposed to be UTF-8 while standard java strings are UTF16
|
||||
// Note number of UTF16 operations are not Utf8 equivalent (for instance length, charAt, ordering ...)
|
||||
// This module provide UTF8 emulation functions.
|
||||
object Utf8 {
|
||||
|
||||
// The DAML-LF strings are supposed to be UTF-8.
|
||||
// However standard "exploding" java/scala methods like
|
||||
// _.toList split in Character which are not Unicode codepoint.
|
||||
def explode(s: String): ImmArray[String] = {
|
||||
val len = s.length
|
||||
val arr = ImmArray.newBuilder[String]
|
||||
var i = 0
|
||||
var j = 0
|
||||
while (i < len) {
|
||||
// if s(i) is a high surrogate the current codepoint uses 2 chars
|
||||
val next = if (s(i).isHighSurrogate) i + 2 else i + 1
|
||||
arr += s.substring(i, next)
|
||||
j += 1
|
||||
i = next
|
||||
}
|
||||
arr.result()
|
||||
}
|
||||
|
||||
def getBytes(s: String): Array[Byte] =
|
||||
s.getBytes(StandardCharsets.UTF_8)
|
||||
|
||||
def sha256(s: String): String = {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
val array = digest.digest(getBytes(s))
|
||||
array.map("%02x" format _).mkString
|
||||
}
|
||||
|
||||
def implode(ts: ImmArray[String]): String =
|
||||
ts.toSeq.mkString
|
||||
|
||||
val Ordering: Ordering[String] = (xs: String, ys: String) => {
|
||||
val lim = xs.length min ys.length
|
||||
|
||||
@tailrec
|
||||
def lp(i: Int): Int =
|
||||
if (i < lim) {
|
||||
val x = xs(i)
|
||||
val y = ys(i)
|
||||
if (x != y) {
|
||||
// If x is a low surrogate, then the current codepoint starts at the
|
||||
// previous char, otherwise the codepoint starts at the current char.
|
||||
val j = if (x.isLowSurrogate) i - 1 else i
|
||||
xs.codePointAt(j) - ys.codePointAt(j)
|
||||
} else lp(i + 1)
|
||||
} else xs.length - ys.length
|
||||
|
||||
lp(0)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.digitalasset.daml.lf
|
||||
|
||||
import scala.math.BigDecimal
|
||||
|
||||
package object data {
|
||||
|
||||
val Decimal: DecimalModule = new DecimalModule {
|
||||
type T = BigDecimal
|
||||
protected def cast(x: BigDecimal): T = x
|
||||
}
|
||||
type Decimal = Decimal.T
|
||||
|
||||
private[data] def assert[X](either: Either[String, X]): X =
|
||||
either.fold(e => throw new IllegalArgumentException(e), identity)
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.digitalasset.daml.lf.data
|
||||
|
||||
import org.scalatest.prop.PropertyChecks
|
||||
import org.scalatest.prop.{PropertyChecks}
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
|
||||
class SortedLookupListSpec extends WordSpec with Matchers with PropertyChecks {
|
||||
@ -15,7 +15,8 @@ class SortedLookupListSpec extends WordSpec with Matchers with PropertyChecks {
|
||||
ImmArray.empty[(String, Int)],
|
||||
ImmArray("1" -> 1),
|
||||
ImmArray("1" -> 1, "2" -> 2, "3" -> 3),
|
||||
ImmArray("2" -> 2, "3" -> 3, "1" -> 1))
|
||||
ImmArray("2" -> 2, "3" -> 3, "1" -> 1)
|
||||
)
|
||||
|
||||
val positiveTestCases =
|
||||
Table("list", ImmArray("1" -> 1, "1" -> 2), ImmArray("1" -> 1, "2" -> 2, "3" -> 3, "1" -> 2))
|
||||
@ -39,7 +40,8 @@ class SortedLookupListSpec extends WordSpec with Matchers with PropertyChecks {
|
||||
"list",
|
||||
ImmArray("1" -> 1, "1" -> 2),
|
||||
ImmArray("1" -> 1, "2" -> 2, "3" -> 3, "1" -> 2),
|
||||
ImmArray("2" -> 2, "3" -> 3, "1" -> 1))
|
||||
ImmArray("2" -> 2, "3" -> 3, "1" -> 1)
|
||||
)
|
||||
|
||||
forAll(negativeTestCases)(l => SortedLookupList.fromSortedImmArray(l) shouldBe 'right)
|
||||
|
||||
|
@ -12,7 +12,7 @@ import scala.collection.JavaConverters._
|
||||
import scala.util.Random
|
||||
|
||||
@SuppressWarnings(Array("org.wartremover.warts.Any"))
|
||||
class UTF8Spec extends WordSpec with Matchers {
|
||||
class Utf8Spec extends WordSpec with Matchers {
|
||||
|
||||
private def codepointToString(cp: Int): String =
|
||||
Character.toChars(cp).mkString
|
||||
@ -47,7 +47,7 @@ class UTF8Spec extends WordSpec with Matchers {
|
||||
|
||||
"explode properly counter example" in {
|
||||
"a¶‱😂".toList shouldNot be(List("a", "¶", "‱", "😂"))
|
||||
UTF8.explode("a¶‱😂") shouldBe ImmArray("a", "¶", "‱", "😂")
|
||||
Utf8.explode("a¶‱😂") shouldBe ImmArray("a", "¶", "‱", "😂")
|
||||
}
|
||||
|
||||
"explode in a same way a naive implementation" in {
|
||||
@ -55,7 +55,7 @@ class UTF8Spec extends WordSpec with Matchers {
|
||||
ImmArray(s.codePoints().iterator().asScala.map(codepointToString(_)).toIterable)
|
||||
|
||||
forAll(strings) { s =>
|
||||
naiveExplode(s) == UTF8.explode(s)
|
||||
naiveExplode(s) == Utf8.explode(s)
|
||||
}
|
||||
|
||||
}
|
||||
@ -63,33 +63,33 @@ class UTF8Spec extends WordSpec with Matchers {
|
||||
|
||||
"Unicode.Ordering" should {
|
||||
|
||||
"do not have basic UTF16 ordering issue" in {
|
||||
"do not have basic Utf16 ordering issue" in {
|
||||
val s1 = "。"
|
||||
val s2 = "😂"
|
||||
val List(cp1) = s1.codePoints().iterator().asScala.toList
|
||||
val List(cp2) = s2.codePoints().iterator().asScala.toList
|
||||
|
||||
Ordering.String.lt(s1, s2) shouldNot be(Ordering.Int.lt(cp1, cp2))
|
||||
UTF8.ordering.lt(s1, s2) shouldBe Ordering.Int.lt(cp1, cp2)
|
||||
Utf8.Ordering.lt(s1, s2) shouldBe Ordering.Int.lt(cp1, cp2)
|
||||
}
|
||||
|
||||
"be reflexive" in {
|
||||
forAll(strings) { x =>
|
||||
UTF8.ordering.compare(x, x) == 0
|
||||
Utf8.Ordering.compare(x, x) == 0
|
||||
}
|
||||
}
|
||||
|
||||
"consistent when flipping its arguments" in {
|
||||
forAll(strings, strings) { (x, y) =>
|
||||
UTF8.ordering.compare(x, y).signum == -UTF8.ordering.compare(y, x).signum
|
||||
Utf8.Ordering.compare(x, y).signum == -Utf8.Ordering.compare(y, x).signum
|
||||
}
|
||||
}
|
||||
|
||||
"be transitive" in {
|
||||
import UTF8.ordering.lteq
|
||||
import Utf8.Ordering.lteq
|
||||
|
||||
forAll(strings, strings, strings) { (x_, y_, z_) =>
|
||||
val List(x, y, z) = List(x_, y_, z_).sorted(UTF8.ordering)
|
||||
val List(x, y, z) = List(x_, y_, z_).sorted(Utf8.Ordering)
|
||||
lteq(x, y) && lteq(y, z) && lteq(x, z)
|
||||
}
|
||||
}
|
||||
@ -98,11 +98,11 @@ class UTF8Spec extends WordSpec with Matchers {
|
||||
|
||||
val shuffledCodepoints = new Random(0).shuffle(validCodepoints)
|
||||
|
||||
// Sort according UTF16
|
||||
// Sort according Utf16
|
||||
val negativeCase = shuffledCodepoints.sorted
|
||||
|
||||
// Sort according our ad hoc ordering
|
||||
val positiveCase = shuffledCodepoints.sorted(UTF8.ordering)
|
||||
val positiveCase = shuffledCodepoints.sorted(Utf8.Ordering)
|
||||
|
||||
negativeCase shouldNot be(validCodepoints)
|
||||
positiveCase shouldBe validCodepoints
|
||||
@ -116,14 +116,14 @@ class UTF8Spec extends WordSpec with Matchers {
|
||||
Ordering.by((s: String) => s.codePoints().toArray.toIterable)
|
||||
|
||||
forAll(Gen.listOfN(20, strings)) { list =>
|
||||
list.sorted(naiveOrdering) == list.sorted(UTF8.ordering)
|
||||
list.sorted(naiveOrdering) == list.sorted(Utf8.Ordering)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"be strict on individual codepoints" in {
|
||||
(validCodepoints zip validCodepoints.tail).foreach {
|
||||
case (x, y) => UTF8.ordering.compare(x, y) should be < 0
|
||||
case (x, y) => Utf8.Ordering.compare(x, y) should be < 0
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import java.io.File
|
||||
|
||||
import com.digitalasset.daml.bazeltools.BazelRunfiles
|
||||
import com.digitalasset.daml.lf.data.Ref._
|
||||
import com.digitalasset.daml.lf.data.{FrontStack, ImmArray, Ref, Time}
|
||||
import com.digitalasset.daml.lf.data._
|
||||
import com.digitalasset.daml.lf.lfpackage.Ast._
|
||||
import com.digitalasset.daml.lf.lfpackage.Decode
|
||||
import com.digitalasset.daml.lf.lfpackage.Util._
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
package com.digitalasset.daml.lf.speedy
|
||||
|
||||
import java.security.MessageDigest
|
||||
import java.util
|
||||
|
||||
import com.digitalasset.daml.lf.data.Ref._
|
||||
@ -156,7 +155,7 @@ object SBuiltin {
|
||||
machine.ctrl = CtrlValue(
|
||||
args.get(0) match {
|
||||
case SText(t) =>
|
||||
SList(FrontStack(UTF8.explode(t).map(SText)))
|
||||
SList(FrontStack(Utf8.explode(t).map(SText)))
|
||||
case _ =>
|
||||
throw SErrorCrash(s"type mismatch explodeText: $args")
|
||||
}
|
||||
@ -174,7 +173,7 @@ object SBuiltin {
|
||||
case v =>
|
||||
throw SErrorCrash(s"type mismatch implodeText: expected SText, got $v")
|
||||
}
|
||||
SText(ts.iterator.mkString)
|
||||
SText(Utf8.implode(ts.toImmArray))
|
||||
case _ =>
|
||||
throw SErrorCrash(s"type mismatch implodeText: $args")
|
||||
}
|
||||
@ -187,7 +186,7 @@ object SBuiltin {
|
||||
machine.ctrl = CtrlValue(
|
||||
(args.get(0), args.get(1)) match {
|
||||
case (SText(head), SText(tail)) =>
|
||||
SText(head ++ tail)
|
||||
SText(head + tail)
|
||||
case _ =>
|
||||
throw SErrorCrash(s"type mismatch appendText: $args")
|
||||
}
|
||||
@ -237,17 +236,11 @@ object SBuiltin {
|
||||
final case object SBSHA256Text extends SBuiltin(1) {
|
||||
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
|
||||
machine.ctrl = CtrlValue(args.get(0) match {
|
||||
case SText(t) => SText(hash(t))
|
||||
case SText(t) => SText(Utf8.sha256(t))
|
||||
case _ =>
|
||||
throw SErrorCrash(s"type mismatch textSHA256: $args")
|
||||
})
|
||||
}
|
||||
|
||||
private def hash(t: String): String = {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
val array = digest.digest(UTF8.getBytes(t))
|
||||
array.map("%02x" format _).mkString
|
||||
}
|
||||
}
|
||||
|
||||
final case object SBMapEmpty extends SBuiltin(0) {
|
||||
@ -308,6 +301,8 @@ object SBuiltin {
|
||||
|
||||
final case object SBMapToList extends SBuiltin(1) {
|
||||
|
||||
// implicit val classTag: ClassTag[Ref.Name.T] = Ref.Name.classTag
|
||||
|
||||
private val entryFields =
|
||||
Name.Array(Ast.keyFieldName, Ast.valueFieldName)
|
||||
|
||||
@ -437,7 +432,7 @@ object SBuiltin {
|
||||
case (SInt64(a), SInt64(b)) => a < b
|
||||
case (SDecimal(a), SDecimal(b)) => a < b
|
||||
case (STimestamp(a), STimestamp(b)) => a < b
|
||||
case (SText(a), SText(b)) => UTF8.ordering.lt(a, b)
|
||||
case (SText(a), SText(b)) => Utf8.Ordering.lt(a, b)
|
||||
case (SDate(a), SDate(b)) => a < b
|
||||
case (SParty(a), SParty(b)) => a < b
|
||||
case _ =>
|
||||
@ -452,7 +447,7 @@ object SBuiltin {
|
||||
case (SInt64(a), SInt64(b)) => a <= b
|
||||
case (SDecimal(a), SDecimal(b)) => a <= b
|
||||
case (STimestamp(a), STimestamp(b)) => a <= b
|
||||
case (SText(a), SText(b)) => UTF8.ordering.lteq(a, b)
|
||||
case (SText(a), SText(b)) => Utf8.Ordering.lteq(a, b)
|
||||
case (SDate(a), SDate(b)) => a <= b
|
||||
case (SParty(a), SParty(b)) => a <= b
|
||||
case _ =>
|
||||
@ -467,7 +462,7 @@ object SBuiltin {
|
||||
case (SInt64(a), SInt64(b)) => a > b
|
||||
case (SDecimal(a), SDecimal(b)) => a > b
|
||||
case (STimestamp(a), STimestamp(b)) => a > b
|
||||
case (SText(a), SText(b)) => UTF8.ordering.gt(a, b)
|
||||
case (SText(a), SText(b)) => Utf8.Ordering.gt(a, b)
|
||||
case (SDate(a), SDate(b)) => a > b
|
||||
case (SParty(a), SParty(b)) => a > b
|
||||
case _ =>
|
||||
@ -482,7 +477,7 @@ object SBuiltin {
|
||||
case (SInt64(a), SInt64(b)) => a >= b
|
||||
case (SDecimal(a), SDecimal(b)) => a >= b
|
||||
case (STimestamp(a), STimestamp(b)) => a >= b
|
||||
case (SText(a), SText(b)) => UTF8.ordering.gteq(a, b)
|
||||
case (SText(a), SText(b)) => Utf8.Ordering.gteq(a, b)
|
||||
case (SDate(a), SDate(b)) => a >= b
|
||||
case (SParty(a), SParty(b)) => a >= b
|
||||
case _ =>
|
||||
@ -1111,9 +1106,8 @@ object SBuiltin {
|
||||
|
||||
/** $error :: Text -> a */
|
||||
final case object SBError extends SBuiltin(1) {
|
||||
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
|
||||
def execute(args: util.ArrayList[SValue], machine: Machine): Unit =
|
||||
throw DamlEUserError(args.get(0).asInstanceOf[SText].value)
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
@ -5,9 +5,8 @@ package com.digitalasset.daml.lf.speedy
|
||||
|
||||
import java.util
|
||||
|
||||
import com.digitalasset.daml.lf.data.Decimal.Decimal
|
||||
import com.digitalasset.daml.lf.data._
|
||||
import com.digitalasset.daml.lf.data.Ref._
|
||||
import com.digitalasset.daml.lf.data.{FrontStack, ImmArray, SortedLookupList, Time}
|
||||
import com.digitalasset.daml.lf.lfpackage.Ast._
|
||||
import com.digitalasset.daml.lf.speedy.SError.SErrorCrash
|
||||
import com.digitalasset.daml.lf.value.{Value => V}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.digitalasset.daml.lf.interp.testing
|
||||
|
||||
import com.digitalasset.daml.lf.data.{Decimal}
|
||||
import com.digitalasset.daml.lf.data.Decimal
|
||||
import com.digitalasset.daml.lf.speedy.{SBuiltin, SValue}
|
||||
import com.digitalasset.daml.lf.speedy.SValue._
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
@ -18,8 +18,8 @@ class ToTextTest extends WordSpec with Matchers {
|
||||
|
||||
"toString" should {
|
||||
"Decimal" in {
|
||||
litToText(SDecimal(Decimal.fromString("123.4560000").toOption.get)) shouldBe "123.456"
|
||||
litToText(SDecimal(Decimal.fromString("123.0000000").toOption.get)) shouldBe "123.0"
|
||||
litToText(SDecimal(Decimal.fromString("123.4560000").toOption.get)) shouldBe ("123.456")
|
||||
litToText(SDecimal(Decimal.fromString("123.0000000").toOption.get)) shouldBe ("123.0")
|
||||
}
|
||||
|
||||
"Text" in {
|
||||
|
@ -6,7 +6,7 @@ package com.digitalasset.daml.lf.speedy
|
||||
import java.util
|
||||
|
||||
import com.digitalasset.daml.lf.PureCompiledPackages
|
||||
import com.digitalasset.daml.lf.data.{Decimal, FrontStack, Ref, Time}
|
||||
import com.digitalasset.daml.lf.data._
|
||||
import com.digitalasset.daml.lf.lfpackage.Ast._
|
||||
import com.digitalasset.daml.lf.speedy.SError.SError
|
||||
import com.digitalasset.daml.lf.speedy.SResult.{SResultContinue, SResultError}
|
||||
@ -16,6 +16,7 @@ import org.scalatest.prop.TableDrivenPropertyChecks
|
||||
import org.scalatest.{FreeSpec, Matchers}
|
||||
|
||||
import scala.collection.immutable.HashMap
|
||||
import scala.language.implicitConversions
|
||||
|
||||
@SuppressWarnings(Array("org.wartremover.warts.Any"))
|
||||
class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks {
|
||||
@ -209,9 +210,9 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
|
||||
|
||||
"MUL_DECIMAL" - {
|
||||
"throws exception in case of overflow" in {
|
||||
eval(e"MUL_DECIMAL 1.1 2.2") shouldBe Right(SDecimal(2.42))
|
||||
eval(e"MUL_DECIMAL 1.1 2.2") shouldBe Right(SDecimal(decimal(2.42)))
|
||||
eval(e"MUL_DECIMAL $bigBigDecimal $bigBigDecimal") shouldBe 'left
|
||||
eval(e"MUL_DECIMAL ${1E13} ${1E14}") shouldBe Right(SDecimal(1E27))
|
||||
eval(e"MUL_DECIMAL ${1E13} ${1E14}") shouldBe Right(SDecimal(decimal(1E27)))
|
||||
eval(e"MUL_DECIMAL ${1E14} ${1E14}") shouldBe 'left
|
||||
}
|
||||
}
|
||||
@ -237,23 +238,22 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
|
||||
(10, d, d)
|
||||
)
|
||||
|
||||
forEvery(testCases) { (rounding, decimal, result) =>
|
||||
eval(e"ROUND_DECIMAL $rounding $decimal") shouldBe Right(SDecimal(BigDecimal(result)))
|
||||
forEvery(testCases) { (rounding, input, result) =>
|
||||
eval(e"ROUND_DECIMAL $rounding $input") shouldBe Right(SDecimal(BigDecimal(result)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"Decimal binary operations computes proper results" in {
|
||||
"Decimal binary operations compute proper results" in {
|
||||
|
||||
val testCases = Table[String, (BigDecimal, BigDecimal) => Either[Any, SValue]](
|
||||
def round(x: BigDecimal) = x.setScale(10, BigDecimal.RoundingMode.HALF_EVEN)
|
||||
|
||||
val testCases = Table[String, (Decimal, Decimal) => Either[Any, SValue]](
|
||||
("builtin", "reference"),
|
||||
("ADD_DECIMAL", (a, b) => Decimal.checkWithinBoundsAndRound(a + b).map(SDecimal)),
|
||||
("SUB_DECIMAL", (a, b) => Decimal.checkWithinBoundsAndRound(a - b).map(SDecimal)),
|
||||
("MUL_DECIMAL", (a, b) => Decimal.checkWithinBoundsAndRound(a * b).map(SDecimal)),
|
||||
(
|
||||
"DIV_DECIMAL",
|
||||
(a, b) =>
|
||||
if (b == 0) Left(()) else Decimal.checkWithinBoundsAndRound(a / b).map(SDecimal)),
|
||||
("ADD_DECIMAL", (a, b) => Right(SDecimal(a + b))),
|
||||
("SUB_DECIMAL", (a, b) => Right(SDecimal(a - b))),
|
||||
("MUL_DECIMAL", (a, b) => Right(SDecimal(round(a * b)))),
|
||||
("DIV_DECIMAL", (a, b) => Either.cond(b != 0, SDecimal(round(a / b)), ())),
|
||||
("LESS_EQ_DECIMAL", (a, b) => Right(SBool(a <= b))),
|
||||
("GREATER_EQ_DECIMAL", (a, b) => Right(SBool(a >= b))),
|
||||
("LESS_DECIMAL", (a, b) => Right(SBool(a < b))),
|
||||
@ -264,7 +264,8 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
|
||||
forEvery(testCases) { (builtin, ref) =>
|
||||
forEvery(decimals) { a =>
|
||||
forEvery(decimals) { b =>
|
||||
eval(e"$builtin $a $b").left.map(_ => ()) shouldBe ref(BigDecimal(a), BigDecimal(b))
|
||||
eval(e"$builtin $a $b").left
|
||||
.map(_ => ()) shouldBe ref(decimal(BigDecimal(a)), decimal(BigDecimal(b)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -650,7 +651,7 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
|
||||
val testCases = Table[Long]("Int64", 167, 11, 2, 1, 0, -1, -2, -13, -113)
|
||||
|
||||
forEvery(testCases) { int64 =>
|
||||
eval(e"INT64_TO_DECIMAL $int64") shouldBe Right(SDecimal(int64))
|
||||
eval(e"INT64_TO_DECIMAL $int64") shouldBe Right(SDecimal(decimal(int64)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -761,7 +762,7 @@ class SBuiltinTest extends FreeSpec with Matchers with TableDrivenPropertyChecks
|
||||
val testCases = Table[String, SValue](
|
||||
"expression" -> "result",
|
||||
"1" -> SInt64(1),
|
||||
"1.0" -> SDecimal(1),
|
||||
"1.0" -> SDecimal(decimal(1)),
|
||||
"True" -> SBool(true),
|
||||
"()" -> SUnit(()),
|
||||
""" "text" """ -> SText("text"),
|
||||
@ -827,4 +828,7 @@ object SBuiltinTest {
|
||||
args.add(v)
|
||||
STuple(entryFields, args)
|
||||
}
|
||||
|
||||
private implicit def decimal(x: BigDecimal): Decimal = Decimal.assertFromBigDecimal(x)
|
||||
|
||||
}
|
||||
|
@ -5,9 +5,8 @@ package com.digitalasset.daml.lf.lfpackage
|
||||
|
||||
import com.digitalasset.daml.lf.archive.LanguageVersion
|
||||
import com.digitalasset.daml.lf.archive.Reader.ParseError
|
||||
import com.digitalasset.daml.lf.data.Decimal.Decimal
|
||||
import com.digitalasset.daml.lf.data.Ref._
|
||||
import com.digitalasset.daml.lf.data.{ImmArray, Time}
|
||||
import com.digitalasset.daml.lf.data.{Decimal, ImmArray, Time}
|
||||
|
||||
object Ast {
|
||||
//
|
||||
@ -269,6 +268,7 @@ object Ast {
|
||||
|
||||
final case class PLInt64(override val value: Long) extends PrimLit
|
||||
final case class PLDecimal(override val value: Decimal) extends PrimLit
|
||||
// Text should be treated as Utf8, data.Utf8 provide emulation functions for that
|
||||
final case class PLText(override val value: String) extends PrimLit
|
||||
final case class PLTimestamp(override val value: Time.Timestamp) extends PrimLit
|
||||
final case class PLParty(override val value: Party) extends PrimLit
|
||||
|
@ -6,7 +6,6 @@ package com.digitalasset.daml.lf.lfpackage
|
||||
import com.digitalasset.daml.lf.archive.LanguageVersion
|
||||
import com.digitalasset.daml.lf.data.ImmArray
|
||||
import com.digitalasset.daml.lf.data.Ref.{ChoiceName, DottedName, Name}
|
||||
import com.digitalasset.daml.lf.data.Ref.Name.{assertFromString => id}
|
||||
import com.digitalasset.daml.lf.lfpackage.Ast._
|
||||
import com.digitalasset.daml.lf.lfpackage.Decode.ParseError
|
||||
import org.scalatest.prop.TableDrivenPropertyChecks
|
||||
@ -37,7 +36,7 @@ class AstSpec extends WordSpec with TableDrivenPropertyChecks with Matchers {
|
||||
"Module.apply" should {
|
||||
|
||||
val template = Template(
|
||||
param = id("x"),
|
||||
param = Name.assertFromString("x"),
|
||||
precond = eTrue,
|
||||
signatories = eParties,
|
||||
agreementText = eText,
|
||||
@ -154,7 +153,7 @@ class AstSpec extends WordSpec with TableDrivenPropertyChecks with Matchers {
|
||||
name = name,
|
||||
consuming = true,
|
||||
controllers = eParties,
|
||||
selfBinder = id("self"),
|
||||
selfBinder = Name.assertFromString("self"),
|
||||
argBinder = (None, tUnit),
|
||||
returnType = tUnit,
|
||||
update = EUpdate(UpdatePure(typ, expr)),
|
||||
@ -166,7 +165,7 @@ class AstSpec extends WordSpec with TableDrivenPropertyChecks with Matchers {
|
||||
"catch choice name collisions" in {
|
||||
|
||||
Template(
|
||||
param = id("x"),
|
||||
param = Name.assertFromString("x"),
|
||||
precond = eTrue,
|
||||
signatories = eParties,
|
||||
agreementText = eText,
|
||||
@ -181,7 +180,7 @@ class AstSpec extends WordSpec with TableDrivenPropertyChecks with Matchers {
|
||||
|
||||
an[ParseError] shouldBe thrownBy(
|
||||
Template(
|
||||
param = id("x"),
|
||||
param = Name.assertFromString("x"),
|
||||
precond = eTrue,
|
||||
signatories = eParties,
|
||||
agreementText = eText,
|
||||
|
@ -26,11 +26,14 @@ object Implicits {
|
||||
Parsers.parseAll(Parsers.phrase(p), sc.standardInterpolator(identity, args.map(prettyPrint)))
|
||||
}
|
||||
|
||||
private def toString(x: BigDecimal) =
|
||||
Decimal.toString(Decimal.assertFromBigDecimal(x))
|
||||
|
||||
private def prettyPrint(x: Any): String =
|
||||
x match {
|
||||
case d: BigDecimal => Decimal.toString(d.bigDecimal)
|
||||
case d: Float => Decimal.toString(d.toDouble)
|
||||
case d: Double => Decimal.toString(d)
|
||||
case d: BigDecimal => toString(d)
|
||||
case d: Float => toString(d.toDouble)
|
||||
case d: Double => toString(d)
|
||||
case other: Any => other.toString
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ private[parser] object Token {
|
||||
final case class ContractId(s: String) extends Token
|
||||
final case class Timestamp(value: data.Time.Timestamp) extends Token
|
||||
final case class Date(value: data.Time.Date) extends Token
|
||||
final case class Decimal(value: data.Decimal.Decimal) extends Token
|
||||
final case class Decimal(value: data.Decimal) extends Token
|
||||
final case class Number(value: Long) extends Token
|
||||
final case class SimpleString(s: String) extends Token
|
||||
final case class Text(s: String) extends Token
|
||||
|
@ -4,8 +4,7 @@
|
||||
package com.digitalasset.daml.lf.testing.parser
|
||||
|
||||
import com.digitalasset.daml.lf.data.Ref._
|
||||
import com.digitalasset.daml.lf.data.Time
|
||||
import com.digitalasset.daml.lf.data.ImmArray
|
||||
import com.digitalasset.daml.lf.data.{Decimal, ImmArray, Time}
|
||||
import com.digitalasset.daml.lf.lfpackage.Ast._
|
||||
import com.digitalasset.daml.lf.testing.parser.Implicits._
|
||||
import org.scalatest.prop.TableDrivenPropertyChecks
|
||||
@ -116,9 +115,9 @@ class ParsersSpec extends WordSpec with TableDrivenPropertyChecks with Matchers
|
||||
"string to parse" -> "expected literal",
|
||||
"1" -> PLInt64(1),
|
||||
"-2" -> PLInt64(-2),
|
||||
"1.0" -> PLDecimal(1),
|
||||
"1.0" -> PLDecimal(1),
|
||||
"-1.0" -> PLDecimal(-1),
|
||||
"1.0" -> PLDecimal(Decimal.assertFromBigDecimal(1)),
|
||||
"1.0" -> PLDecimal(Decimal.assertFromBigDecimal(1)),
|
||||
"-1.0" -> PLDecimal(Decimal.assertFromBigDecimal(-1)),
|
||||
""""some text"""" -> PLText("some text"),
|
||||
""" " \n\r\"\\ " """ -> PLText(" \n\r\"\\ "),
|
||||
""" "français" """ -> PLText("français"),
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.digitalasset.daml.lf.speedy
|
||||
|
||||
import com.digitalasset.daml.lf.data.{FrontStack, ImmArray, Ref}
|
||||
import com.digitalasset.daml.lf.data._
|
||||
import com.digitalasset.daml.lf.data.Ref._
|
||||
import com.digitalasset.daml.lf.lfpackage.Ast._
|
||||
import com.digitalasset.daml.lf.lfpackage.Decode
|
||||
@ -51,13 +51,12 @@ object Main extends App {
|
||||
|
||||
}
|
||||
|
||||
def defaultCommand(possibleFile: String): Unit = {
|
||||
def defaultCommand(possibleFile: String): Unit =
|
||||
if (!Paths.get(possibleFile).toFile.isFile) {
|
||||
usage()
|
||||
System.exit(1)
|
||||
} else
|
||||
Repl.repl(possibleFile)
|
||||
}
|
||||
|
||||
if (args.isEmpty) {
|
||||
usage()
|
||||
@ -556,7 +555,7 @@ object Repl {
|
||||
QualifiedName.assertFromString("Dummy:Dummy"))
|
||||
|
||||
def pDecimal: Parser[Value[Nothing]] = """\d+\.\d+""".r ^^ { s =>
|
||||
ValueDecimal(BigDecimal(s))
|
||||
ValueDecimal(Decimal.assertFromString(s))
|
||||
}
|
||||
def pInt64: Parser[Value[Nothing]] = """\d+""".r ^^ { s =>
|
||||
ValueInt64(s.toLong)
|
||||
|
@ -33,6 +33,7 @@ da_scala_test_suite(
|
||||
scalacopts = lf_scalacopts,
|
||||
deps = [
|
||||
":scenario-interpreter",
|
||||
"//3rdparty/jvm/org/scalaz:scalaz_core",
|
||||
"//daml-lf/data",
|
||||
"//daml-lf/interpreter",
|
||||
"//daml-lf/lfpackage",
|
||||
|
@ -14,9 +14,9 @@ class ScenarioRunnerTest extends AsyncWordSpec with Matchers with ScalaFutures {
|
||||
|
||||
"ScenarioRunner" can {
|
||||
"mangle party names correctly" in {
|
||||
val e = Ast.EScenario(ScenarioGetParty(Ast.EPrimLit(Ast.PLText("foo-bar"))))
|
||||
val e = Ast.EScenario(ScenarioGetParty(Ast.EPrimLit(Ast.PLText(("foo-bar")))))
|
||||
val m = Speedy.Machine.fromExpr(e, PureCompiledPackages(Map.empty).right.get, true)
|
||||
val sr = ScenarioRunner(m, (s) => s + "-XXX")
|
||||
val sr = ScenarioRunner(m, _ + "-XXX")
|
||||
sr.run()
|
||||
m.ctrl shouldBe Speedy.CtrlValue(SValue.SParty(Ref.Party.assertFromString("foo-bar-XXX")))
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import com.digitalasset.daml.lf.PureCompiledPackages
|
||||
import com.digitalasset.daml.lf.command._
|
||||
import com.digitalasset.daml.lf.data.Ref.{PackageId, Party, QualifiedName}
|
||||
import com.digitalasset.daml.lf.data.Relation.Relation
|
||||
import com.digitalasset.daml.lf.data.{FrontStack, FrontStackCons, ImmArray, Time}
|
||||
import com.digitalasset.daml.lf.data._
|
||||
import com.digitalasset.daml.lf.engine.Event.Events
|
||||
import com.digitalasset.daml.lf.engine._
|
||||
import com.digitalasset.daml.lf.lfpackage.Ast._
|
||||
|
@ -95,7 +95,7 @@ object ValueGenerators {
|
||||
(1, Gen.const(Decimal.min)),
|
||||
(5, bd)
|
||||
)
|
||||
.map(d => ValueDecimal(d))
|
||||
.map(d => ValueDecimal(Decimal.assertFromBigDecimal(d)))
|
||||
}
|
||||
|
||||
val moduleSegmentGen: Gen[String] = for {
|
||||
@ -191,8 +191,9 @@ object ValueGenerators {
|
||||
|
||||
private def valueMapGen(nesting: Int) =
|
||||
for {
|
||||
list <- Gen.listOf(
|
||||
for { k <- Gen.asciiPrintableStr; v <- Gen.lzy(valueGen(nesting)) } yield k -> v)
|
||||
list <- Gen.listOf(for {
|
||||
k <- Gen.asciiPrintableStr; v <- Gen.lzy(valueGen(nesting))
|
||||
} yield k -> v)
|
||||
} yield ValueMap[ContractId](SortedLookupList(Map(list: _*)))
|
||||
def valueMapGen: Gen[ValueMap[ContractId]] = valueMapGen(0)
|
||||
|
||||
@ -220,10 +221,10 @@ object ValueGenerators {
|
||||
)
|
||||
val flat = List(
|
||||
(sz + 1, dateGen.map(ValueDate)),
|
||||
(sz + 1, Gen.alphaStr.map(ValueText)),
|
||||
(sz + 1, Gen.alphaStr.map(x => ValueText(x))),
|
||||
(sz + 1, decimalGen),
|
||||
(sz + 1, Arbitrary.arbLong.arbitrary.map(ValueInt64)),
|
||||
(sz + 1, Gen.alphaStr.map(ValueText)),
|
||||
(sz + 1, Gen.alphaStr.map(x => ValueText(x))),
|
||||
(sz + 1, timestampGen.map(ValueTimestamp)),
|
||||
(sz + 1, coidValueGen),
|
||||
(sz + 1, party.map(ValueParty)),
|
||||
|
@ -63,7 +63,7 @@ object TransactionCoder {
|
||||
: Either[DecodeError, ContractInst[Val]] = {
|
||||
ValueCoder.decodeIdentifier(protoCoinst.getTemplateId).flatMap { id =>
|
||||
decodeVal(protoCoinst.getValue)
|
||||
.map(a => ContractInst(id, a, protoCoinst.getAgreement))
|
||||
.map(a => ContractInst(id, a, (protoCoinst.getAgreement)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,8 @@ object Value {
|
||||
*/
|
||||
final case class ValueList[+Cid](values: FrontStack[Value[Cid]]) extends Value[Cid]
|
||||
final case class ValueInt64(value: Long) extends Value[Nothing]
|
||||
final case class ValueDecimal(value: BigDecimal) extends Value[Nothing]
|
||||
final case class ValueDecimal(value: Decimal) extends Value[Nothing]
|
||||
// Note that Text are assume to be UTF8
|
||||
final case class ValueText(value: String) extends Value[Nothing]
|
||||
final case class ValueTimestamp(value: Time.Timestamp) extends Value[Nothing]
|
||||
final case class ValueDate(value: Time.Date) extends Value[Nothing]
|
||||
|
@ -358,7 +358,7 @@ object ValueCoder {
|
||||
|
||||
case proto.Value.SumCase.MAP =>
|
||||
val entries = ImmArray(protoValue.getMap.getEntriesList.asScala.map(entry =>
|
||||
entry.getKey -> go(newNesting, entry.getValue)))
|
||||
(entry.getKey) -> go(newNesting, entry.getValue)))
|
||||
|
||||
val map = SortedLookupList
|
||||
.fromImmArray(entries)
|
||||
|
@ -4,7 +4,7 @@
|
||||
package com.digitalasset.daml.lf.transaction
|
||||
|
||||
import com.digitalasset.daml.lf.EitherAssertions
|
||||
import com.digitalasset.daml.lf.data.ImmArray
|
||||
import com.digitalasset.daml.lf.data.{ImmArray}
|
||||
import com.digitalasset.daml.lf.data.Ref.{Identifier, PackageId, Party, QualifiedName}
|
||||
import com.digitalasset.daml.lf.transaction.Node.{GenNode, NodeCreate, NodeExercises, NodeFetch}
|
||||
import com.digitalasset.daml.lf.transaction.{Transaction => Tx, TransactionOuterClass => proto}
|
||||
@ -244,7 +244,7 @@ class TransactionCoderSpec
|
||||
VersionedValue(
|
||||
ValueVersions.acceptedVersions.last,
|
||||
ValueParty(Party.assertFromString("francesco"))),
|
||||
"agreement"
|
||||
("agreement")
|
||||
),
|
||||
None,
|
||||
Set(Party.assertFromString("alice")),
|
||||
|
@ -145,7 +145,8 @@ object TransactionSpec {
|
||||
PackageId.assertFromString("-dummyPkg-"),
|
||||
QualifiedName.assertFromString("DummyModule:dummyName")),
|
||||
V.ValueUnit,
|
||||
"dummyAgreement"),
|
||||
("dummyAgreement")
|
||||
),
|
||||
None,
|
||||
Set.empty,
|
||||
Set.empty,
|
||||
|
@ -5,7 +5,7 @@ package com.digitalasset.daml.lf.value
|
||||
|
||||
import com.digitalasset.daml.lf.EitherAssertions
|
||||
import com.digitalasset.daml.lf.data.Ref.Party
|
||||
import com.digitalasset.daml.lf.data.{Decimal, ImmArray, Ref, Time}
|
||||
import com.digitalasset.daml.lf.data._
|
||||
import com.digitalasset.daml.lf.value.Value._
|
||||
import com.digitalasset.daml.lf.value.ValueCoder.DecodeError
|
||||
import com.digitalasset.daml.lf.value.{ValueOuterClass => proto}
|
||||
@ -45,8 +45,8 @@ class ValueCoderSpec extends WordSpec with Matchers with EitherAssertions with P
|
||||
"do Decimal" in {
|
||||
forAll("Decimal (BigDecimal) invariant") { d: BigDecimal =>
|
||||
// we are filtering on decimals invariant under string conversion
|
||||
whenever(Decimal.fromString(Decimal.toString(d)).isRight) {
|
||||
val Right(dec) = Decimal.fromString(Decimal.toString(d))
|
||||
whenever(Decimal.fromBigDecimal(d).isRight) {
|
||||
val Right(dec) = Decimal.fromBigDecimal(d)
|
||||
val value = ValueDecimal(dec)
|
||||
val recoveredDecimal = ValueCoder.decodeValue[ContractId](
|
||||
defaultCidDecode,
|
||||
|
@ -67,7 +67,9 @@ object JsonConverters {
|
||||
implicit val mapEncoder: Encoder[SortedLookupList[LedgerValue]] = m =>
|
||||
JsonObject(
|
||||
"Map" ->
|
||||
JsonObject.fromIterable(m.mapValue(_.asJson).toImmArray.toSeq).asJson).asJson
|
||||
JsonObject
|
||||
.fromIterable(m.toImmArray.map { case (k, v) => k -> v.asJson }.toSeq)
|
||||
.asJson).asJson
|
||||
|
||||
implicit val idKeyEncoder: KeyEncoder[Identifier] = id => s"${id.packageId}@${id.name}"
|
||||
implicit val idKeyDecoder: KeyDecoder[Identifier] = {
|
||||
|
@ -8,7 +8,7 @@ import api.value.Value.Sum
|
||||
import RecordField._
|
||||
import scalaz.{Optional => _, _}
|
||||
import Scalaz._
|
||||
import com.digitalasset.daml.lf.data.{SortedLookupList, ImmArray}
|
||||
import com.digitalasset.daml.lf.data.{ImmArray, SortedLookupList}
|
||||
|
||||
sealed trait LedgerValue
|
||||
|
||||
|
@ -90,7 +90,8 @@ object LfEngineToApi {
|
||||
value0: LfValue[Lf.AbsoluteContractId]): Either[String, ApiValue] =
|
||||
value0 match {
|
||||
case Lf.ValueUnit => Right(ApiValue(ApiValue.Sum.Unit(Empty())))
|
||||
case Lf.ValueDecimal(d) => Right(ApiValue(ApiValue.Sum.Decimal(Decimal.toString(d))))
|
||||
case Lf.ValueDecimal(d) =>
|
||||
Right(ApiValue(ApiValue.Sum.Decimal(Decimal.toString(d))))
|
||||
case Lf.ValueContractId(c) => Right(ApiValue(ApiValue.Sum.ContractId(c.coid)))
|
||||
case Lf.ValueBool(b) => Right(ApiValue(ApiValue.Sum.Bool(b)))
|
||||
case Lf.ValueDate(d) => Right(ApiValue(ApiValue.Sum.Date(d.days)))
|
||||
|
@ -3,7 +3,8 @@
|
||||
|
||||
package com.digitalasset.ledger.api
|
||||
|
||||
import com.digitalasset.daml.lf.data.{Time, UTF8}
|
||||
import com.digitalasset.daml.lf.data.Time
|
||||
import com.digitalasset.daml.lf.testing.parser.Implicits._
|
||||
import com.digitalasset.ledger.api.v1.value.Value.Sum
|
||||
import com.digitalasset.ledger.api.v1.value.{
|
||||
List => ApiList,
|
||||
@ -20,7 +21,6 @@ import com.digitalasset.platform.server.api.validation.IdentifierResolver
|
||||
import com.google.protobuf.empty.Empty
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1}
|
||||
import com.digitalasset.daml.lf.testing.parser.Implicits._
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
@ -115,7 +115,7 @@ class ValueConversionRoundTripTest
|
||||
val entries = List("‱", "1", "😂", "😃", "a").zipWithIndex.map {
|
||||
case (k, v) => ApiMap.Entry(k, Some(Value(Sum.Int64(v.toLong))))
|
||||
}
|
||||
val sortedEntries = entries.sortBy(_.key)(UTF8.ordering)
|
||||
val sortedEntries = entries.sortBy(_.key)
|
||||
|
||||
// just to be sure we did not write the entries sorted
|
||||
assert(entries != sortedEntries)
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
package com.digitalasset.ledger.api.validation
|
||||
|
||||
import java.security.MessageDigest
|
||||
import java.time.Instant
|
||||
|
||||
import com.digitalasset.api.util.TimestampConversion
|
||||
@ -485,9 +484,11 @@ class CommandSubmissionRequestValidatorTest
|
||||
|
||||
"convert valid maps" in {
|
||||
val entries = ImmArray(1 until 5).map { x =>
|
||||
hash(x.toString) -> x.toLong
|
||||
Utf8.sha256(x.toString) -> x.toLong
|
||||
}
|
||||
val apiEntries = entries.map {
|
||||
case (k, v) => ApiMap.Entry(k, Some(Value(Sum.Int64(v))))
|
||||
}
|
||||
val apiEntries = entries.map { case (k, v) => ApiMap.Entry(k, Some(Value(Sum.Int64(v)))) }
|
||||
val input = Value(Sum.Map(ApiMap(apiEntries.toSeq)))
|
||||
val lfEntries = entries.map { case (k, v) => k -> Lf.ValueInt64(v) }
|
||||
val expected =
|
||||
@ -498,14 +499,16 @@ class CommandSubmissionRequestValidatorTest
|
||||
|
||||
"reject maps with repeated keys" in {
|
||||
val entries = ImmArray(1 +: (1 until 5)).map { x =>
|
||||
hash(x.toString) -> x.toLong
|
||||
Utf8.sha256(x.toString) -> x.toLong
|
||||
}
|
||||
val apiEntries = entries.map {
|
||||
case (k, v) => ApiMap.Entry(k, Some(Value(Sum.Int64(v))))
|
||||
}
|
||||
val apiEntries = entries.map { case (k, v) => ApiMap.Entry(k, Some(Value(Sum.Int64(v)))) }
|
||||
val input = Value(Sum.Map(ApiMap(apiEntries.toSeq)))
|
||||
requestMustFailWith(
|
||||
sut.validateValue(input),
|
||||
INVALID_ARGUMENT,
|
||||
s"Invalid argument: key ${hash(1.toString)} duplicated when trying to build map")
|
||||
s"Invalid argument: key ${Utf8.sha256("1")} duplicated when trying to build map")
|
||||
}
|
||||
|
||||
"reject maps containing invalid value" in {
|
||||
@ -523,9 +526,4 @@ class CommandSubmissionRequestValidatorTest
|
||||
|
||||
}
|
||||
|
||||
private def hash(t: String): String = {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
val array = digest.digest(UTF8.getBytes(t))
|
||||
array.map("%02x" format _).mkString
|
||||
}
|
||||
}
|
||||
|
@ -63,8 +63,8 @@ object LedgerApiTestTool {
|
||||
}
|
||||
var failed = false
|
||||
|
||||
val runSuffix = Random.alphanumeric.take(10).mkString
|
||||
val partyNameMangler = (partyText: String) => s"$partyText-$runSuffix"
|
||||
val runSuffix = "-" + Random.alphanumeric.take(10).mkString
|
||||
val partyNameMangler = (partyText: String) => partyText + runSuffix
|
||||
val commandIdMangler: ((QualifiedName, Int, L.NodeId) => String) = (scenario, stepId, nodeId) =>
|
||||
s"ledger-api-test-tool-$scenario-$stepId-$nodeId-$runSuffix"
|
||||
|
||||
|
@ -6,6 +6,7 @@ package com.digitalasset.platform.sandbox.stores.ledger.sql.serialisation
|
||||
import java.nio.ByteBuffer
|
||||
import java.security.MessageDigest
|
||||
|
||||
import com.digitalasset.daml.lf.data.Utf8
|
||||
import com.digitalasset.daml.lf.transaction.Node.GlobalKey
|
||||
import com.digitalasset.daml.lf.value.Value
|
||||
import com.digitalasset.daml.lf.value.Value.AbsoluteContractId
|
||||
@ -109,9 +110,10 @@ object KeyHasher extends KeyHasher {
|
||||
digest.update(ByteBuffer.allocate(8).putLong(value).array())
|
||||
|
||||
private[this] def putStringContent(digest: MessageDigest, value: String): Unit =
|
||||
digest.update(value.getBytes("UTF8"))
|
||||
digest.update(Utf8.getBytes(value))
|
||||
|
||||
private[this] def putString(digest: MessageDigest, value: String): Unit = {
|
||||
// FixMe we probably should not use UTF16 length.
|
||||
putInt(digest, value.length)
|
||||
putStringContent(digest, value)
|
||||
}
|
||||
@ -140,6 +142,7 @@ object KeyHasher extends KeyHasher {
|
||||
case HashTokenInt(v) => putInt(d, v)
|
||||
case HashTokenLong(v) => putLong(d, v)
|
||||
case HashTokenText(v) => putString(d, v)
|
||||
// FixMe we probably should use Decimal.toString
|
||||
// Java docs: "The toString() method provides a canonical representation of a BigDecimal."
|
||||
case HashTokenBigDecimal(v) => putString(d, v.toString)
|
||||
case HashTokenCollectionBegin(length) => putInt(d, length)
|
||||
|
@ -48,11 +48,12 @@ class EventConverterSpec
|
||||
with TestHelpers
|
||||
with AkkaBeforeAndAfterAll
|
||||
with Inside {
|
||||
|
||||
private implicit def qualifiedNameStr(s: String): QualifiedName =
|
||||
QualifiedName.assertFromString(s)
|
||||
private implicit def party(s: String): Ref.Party = Ref.Party.assertFromString(s)
|
||||
private implicit def pkgId(s: String): Ref.PackageId = Ref.PackageId.assertFromString(s)
|
||||
private implicit def id(s: String): Ref.Name = Ref.Name.assertFromString(s)
|
||||
private implicit def name(s: String): Ref.Name = Ref.Name.assertFromString(s)
|
||||
|
||||
type LfTx = com.digitalasset.daml.lf.transaction.Transaction.Transaction
|
||||
|
||||
|
@ -11,6 +11,8 @@ import com.digitalasset.daml.lf.value.{Value, ValueVersion}
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
import com.digitalasset.platform.sandbox.stores.ledger.sql.serialisation.KeyHasher
|
||||
|
||||
import scala.language.implicitConversions
|
||||
|
||||
class KeyHasherSpec extends WordSpec with Matchers {
|
||||
private[this] def templateId(module: String, name: String) = Identifier(
|
||||
PackageId.assertFromString("package"),
|
||||
@ -25,8 +27,8 @@ class KeyHasherSpec extends WordSpec with Matchers {
|
||||
builder += None -> ValueInt64(0)
|
||||
builder += None -> ValueInt64(123456)
|
||||
builder += None -> ValueInt64(-1)
|
||||
builder += None -> ValueDecimal(Decimal.assertFromString("0"))
|
||||
builder += None -> ValueDecimal(Decimal.assertFromString("0.3333333333"))
|
||||
builder += None -> ValueDecimal(decimal(0))
|
||||
builder += None -> ValueDecimal(decimal(BigDecimal("0.3333333333")))
|
||||
builder += None -> ValueBool(true)
|
||||
builder += None -> ValueBool(false)
|
||||
builder += None -> ValueDate(Time.Date.assertFromDaysSinceEpoch(0))
|
||||
@ -125,9 +127,13 @@ class KeyHasherSpec extends WordSpec with Matchers {
|
||||
"not produce collision in list of decimals" in {
|
||||
// Testing whether decimals are delimited: [10, 10] vs [101, 0]
|
||||
val value1 =
|
||||
VersionedValue(ValueVersion("4"), ValueList(FrontStack(ValueDecimal(10), ValueDecimal(10))))
|
||||
VersionedValue(
|
||||
ValueVersion("4"),
|
||||
ValueList(FrontStack(ValueDecimal(decimal(10)), ValueDecimal(decimal(10)))))
|
||||
val value2 =
|
||||
VersionedValue(ValueVersion("4"), ValueList(FrontStack(ValueDecimal(101), ValueDecimal(0))))
|
||||
VersionedValue(
|
||||
ValueVersion("4"),
|
||||
ValueList(FrontStack(ValueDecimal(decimal(101)), ValueDecimal(decimal(0)))))
|
||||
|
||||
val tid = templateId("module", "name")
|
||||
|
||||
@ -269,8 +275,8 @@ class KeyHasherSpec extends WordSpec with Matchers {
|
||||
}
|
||||
|
||||
"not produce collision in Decimal" in {
|
||||
val value1 = VersionedValue(ValueVersion("4"), ValueDecimal(0))
|
||||
val value2 = VersionedValue(ValueVersion("4"), ValueDecimal(1))
|
||||
val value1 = VersionedValue(ValueVersion("4"), ValueDecimal(decimal(0)))
|
||||
val value2 = VersionedValue(ValueVersion("4"), ValueDecimal(decimal(1)))
|
||||
|
||||
val tid = templateId("module", "name")
|
||||
|
||||
@ -346,4 +352,7 @@ class KeyHasherSpec extends WordSpec with Matchers {
|
||||
hash1.equals(hash2) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
private implicit def decimal(x: BigDecimal): Decimal = Decimal.assertFromBigDecimal(x)
|
||||
|
||||
}
|
||||
|
@ -69,6 +69,8 @@ class PostgresDaoSpec
|
||||
|
||||
private val alice = Party.assertFromString("Alice")
|
||||
private val bob = Party.assertFromString("Bob")
|
||||
private val someValueText = ValueText("some text")
|
||||
private val agreement = "agreement"
|
||||
|
||||
"Postgres Ledger DAO" should {
|
||||
|
||||
@ -82,8 +84,8 @@ class PostgresDaoSpec
|
||||
Ref.QualifiedName(
|
||||
Ref.ModuleName.assertFromString("moduleName"),
|
||||
Ref.DottedName.assertFromString("name"))),
|
||||
VersionedValue(ValueVersions.acceptedVersions.head, ValueText("some text")),
|
||||
"agreement"
|
||||
VersionedValue(ValueVersions.acceptedVersions.head, someValueText),
|
||||
agreement
|
||||
)
|
||||
val keyWithMaintainers = KeyWithMaintainers(
|
||||
VersionedValue(ValueVersions.acceptedVersions.head, ValueText("key")),
|
||||
@ -144,7 +146,7 @@ class PostgresDaoSpec
|
||||
entry <- ledgerDao.lookupLedgerEntry(offset)
|
||||
endingOffset <- ledgerDao.lookupLedgerEnd()
|
||||
} yield {
|
||||
entry shouldEqual (Some(checkpoint))
|
||||
entry shouldEqual Some(checkpoint)
|
||||
endingOffset shouldEqual (startingOffset + 1)
|
||||
}
|
||||
}
|
||||
@ -198,8 +200,8 @@ class PostgresDaoSpec
|
||||
Ref.QualifiedName(
|
||||
Ref.ModuleName.assertFromString("moduleName"),
|
||||
Ref.DottedName.assertFromString("name"))),
|
||||
VersionedValue(ValueVersions.acceptedVersions.head, ValueText("some text")),
|
||||
"agreement"
|
||||
VersionedValue(ValueVersions.acceptedVersions.head, someValueText),
|
||||
agreement
|
||||
)
|
||||
|
||||
val keyWithMaintainers = KeyWithMaintainers(
|
||||
@ -265,8 +267,8 @@ class PostgresDaoSpec
|
||||
val let = Instant.now
|
||||
val contractInstance = ContractInst(
|
||||
templateId,
|
||||
VersionedValue(ValueVersions.acceptedVersions.head, ValueText("some text")),
|
||||
"agreement"
|
||||
VersionedValue(ValueVersions.acceptedVersions.head, someValueText),
|
||||
agreement
|
||||
)
|
||||
val contract = Contract(
|
||||
absCid,
|
||||
|
@ -57,7 +57,11 @@ object ApiCodecCompressed {
|
||||
JsObject(value.fields.map(f => f.label -> apiValueToJsValue(f.value)).toMap)
|
||||
|
||||
def apiMapToJsValue(value: Model.ApiMap): JsValue =
|
||||
JsObject(value.value.mapValue(apiValueToJsValue).toHashMap)
|
||||
JsObject(
|
||||
value.value.toImmArray
|
||||
.map { case (k, v) => k -> apiValueToJsValue(v) }
|
||||
.toSeq
|
||||
.toMap)
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------
|
||||
// Decoding - this needs access to DAML-LF types
|
||||
@ -88,7 +92,9 @@ object ApiCodecCompressed {
|
||||
case None => deserializationError(s"Can't read ${value.prettyPrint} as Optional")
|
||||
}
|
||||
case (JsObject(a), Model.DamlLfPrimType.Map) =>
|
||||
Model.ApiMap(SortedLookupList(a).mapValue(jsValueToApiType(_, prim.typArgs.head, defs)))
|
||||
Model.ApiMap(SortedLookupList(a.map {
|
||||
case (k, v) => k -> jsValueToApiType(v, prim.typArgs.head, defs)
|
||||
}))
|
||||
case _ => deserializationError(s"Can't read ${value.prettyPrint} as $prim")
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.digitalasset.navigator.json
|
||||
|
||||
import com.digitalasset.daml.lf.data.{SortedLookupList, ImmArray}
|
||||
import com.digitalasset.daml.lf.data.{ImmArray, SortedLookupList}
|
||||
import com.digitalasset.navigator.{model => Model}
|
||||
import com.digitalasset.navigator.json.DamlLfCodec.JsonImplicits._
|
||||
import com.digitalasset.navigator.json.Util._
|
||||
@ -81,7 +81,8 @@ object ApiCodecVerbose {
|
||||
JsObject(
|
||||
propType -> JsString(tagMap),
|
||||
propValue -> JsArray(value.value.toImmArray.toSeq.toVector.map {
|
||||
case (k, v) => JsObject(fieldKey -> JsString(k), fieldValue -> apiValueToJsValue(v))
|
||||
case (k, v) =>
|
||||
JsObject(fieldKey -> JsString(k), fieldValue -> apiValueToJsValue(v))
|
||||
})
|
||||
)
|
||||
|
||||
|
@ -7,7 +7,7 @@ import java.time.{Instant, LocalDate}
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
import com.digitalasset.daml.lf.data.{SortedLookupList, ImmArray}
|
||||
import com.digitalasset.daml.lf.data.{ImmArray, SortedLookupList}
|
||||
import com.digitalasset.ledger.api.{v1 => V1}
|
||||
import com.digitalasset.ledger.api.refinements.ApiTypes
|
||||
import com.digitalasset.navigator.{model => Model}
|
||||
|
@ -3,10 +3,9 @@
|
||||
|
||||
package com.digitalasset.navigator
|
||||
|
||||
import com.digitalasset.daml.lf.data.SortedLookupList
|
||||
import com.digitalasset.daml.lf.data.{SortedLookupList, Ref => DamlLfRef}
|
||||
import com.digitalasset.navigator.model._
|
||||
import com.digitalasset.daml.lf.{iface => DamlLfIface}
|
||||
import com.digitalasset.daml.lf.data.{Ref => DamlLfRef}
|
||||
|
||||
import scala.language.implicitConversions
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user