diff --git a/core/shared/src/main/scala/sigma/Colls.scala b/core/shared/src/main/scala/sigma/Colls.scala index 625120deac..d10026066b 100644 --- a/core/shared/src/main/scala/sigma/Colls.scala +++ b/core/shared/src/main/scala/sigma/Colls.scala @@ -45,6 +45,19 @@ trait Coll[@specialized A] { */ def apply(i: Int): A + /** The element at given index or None if there is no such element. Indices start at `0`. + * + * @param i the index + * @return the element at the given index, or None if there is no such element + */ + def get(i: Int): Option[A] = { + if (isDefinedAt(i)) { + Some(apply(i)) + } else { + None + } + } + /** Tests whether this $coll contains given index. * * The implementations of methods `apply` and `isDefinedAt` turn a `Coll[A]` into @@ -76,6 +89,18 @@ trait Coll[@specialized A] { * produces a collection ((x0, y0), ..., (xK, yK)) where K = min(N, M) */ def zip[@specialized B](ys: Coll[B]): Coll[(A, B)] + /** + * @return true if first elements of this collection form given `ys` collection, false otherwise. + * E.g. [1,2,3] starts with [1,2] + */ + def startsWith(ys: Coll[A]): Boolean + + /** + * @return true if last elements of this collection form given `ys` collection, false otherwise. + * E.g. [1,2,3] ends with [2,3] + */ + def endsWith(ys: Coll[A]): Boolean + /** Tests whether a predicate holds for at least one element of this collection. * @param p the predicate used to test elements. * @return `true` if the given predicate `p` is satisfied by at least one element of this collection, otherwise `false` diff --git a/core/shared/src/main/scala/sigma/data/CollsOverArrays.scala b/core/shared/src/main/scala/sigma/data/CollsOverArrays.scala index 2413f7f427..eb4c1e4931 100644 --- a/core/shared/src/main/scala/sigma/data/CollsOverArrays.scala +++ b/core/shared/src/main/scala/sigma/data/CollsOverArrays.scala @@ -39,6 +39,10 @@ class CollOverArray[@specialized A](val toArray: Array[A], val builder: CollBuil @inline def zip[@specialized B](ys: Coll[B]): PairColl[A, B] = builder.pairColl(this, ys) + @inline def startsWith(ys: Coll[A]): Boolean = toArray.startsWith(ys.toArray) + + @inline def endsWith(ys: Coll[A]): Boolean = toArray.endsWith(ys.toArray) + def append(other: Coll[A]): Coll[A] = { if (toArray.length <= 0) return other val result = if (VersionContext.current.isJitActivated) { @@ -350,6 +354,16 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R def zip[@specialized B](ys: Coll[B]): PairColl[(L,R), B] = builder.pairColl(this, ys) + def startsWith(ys: Coll[(L, R)]): Boolean = ys match { + case yp: PairOfCols[L, R] => ls.startsWith(yp.ls) && rs.startsWith(yp.rs) + case _ => toArray.startsWith(ys.toArray) + } + + def endsWith(ys: Coll[(L, R)]): Boolean = ys match { + case yp: PairOfCols[L, R] => ls.endsWith(yp.ls) && rs.endsWith(yp.rs) + case _ => toArray.endsWith(ys.toArray) + } + override def indices: Coll[Int] = if (ls.length <= rs.length) ls.indices else rs.indices override def flatMap[B: RType](f: ((L, R)) => Coll[B]): Coll[B] = diff --git a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala index a5b3c1e787..3351c5a2c6 100644 --- a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala +++ b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala @@ -162,6 +162,9 @@ object ReflectionData { mkMethod(clazz, "apply", Array[Class[_]](classOf[Int])) { (obj, args) => obj.asInstanceOf[Coll[_]].apply(args(0).asInstanceOf[Int]) }, + mkMethod(clazz, "get", Array[Class[_]](classOf[Int])) { (obj, args) => + obj.asInstanceOf[Coll[_]].get(args(0).asInstanceOf[Int]) + }, mkMethod(clazz, "append", Array[Class[_]](classOf[Coll[_]])) { (obj, args) => obj.asInstanceOf[Coll[Any]].append(args(0).asInstanceOf[Coll[Any]]) }, @@ -170,6 +173,18 @@ object ReflectionData { }, mkMethod(clazz, "map", Array[Class[_]](classOf[Function1[_, _]], classOf[RType[_]])) { (obj, args) => obj.asInstanceOf[Coll[Any]].map(args(0).asInstanceOf[Any => Any])(args(1).asInstanceOf[RType[Any]]) + }, + mkMethod(clazz, "reverse", Array[Class[_]]()) { (obj, args) => + obj.asInstanceOf[Coll[Any]].reverse + }, + mkMethod(clazz, "distinct", Array[Class[_]]()) { (obj, args) => + obj.asInstanceOf[Coll[Any]].distinct + }, + mkMethod(clazz, "startsWith", Array[Class[_]](classOf[Coll[_]])) { (obj, args) => + obj.asInstanceOf[Coll[Any]].startsWith(args(0).asInstanceOf[Coll[Any]]) + }, + mkMethod(clazz, "endsWith", Array[Class[_]](classOf[Coll[_]])) { (obj, args) => + obj.asInstanceOf[Coll[Any]].endsWith(args(0).asInstanceOf[Coll[Any]]) } ) ) diff --git a/core/shared/src/test/scala/sigma/CollsTests.scala b/core/shared/src/test/scala/sigma/CollsTests.scala index 4886112742..da427ba576 100644 --- a/core/shared/src/test/scala/sigma/CollsTests.scala +++ b/core/shared/src/test/scala/sigma/CollsTests.scala @@ -386,6 +386,32 @@ class CollsTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers } } + property("Coll.startsWith") { + val minSuccess = minSuccessful(50) + forAll(collGen, minSuccess) { col => + val n = col.length / 2 + val prefix = col.take(n) + val pairs = col.zip(col) + pairs.startsWith(prefix.zip(prefix)) shouldBe true + col.startsWith(prefix) shouldBe true + val pairOfCols = new PairOfCols[Int, Int](col, col) + pairOfCols.startsWith(pairOfCols.take(n)) shouldBe true + } + } + + property("Coll.endsWith") { + val minSuccess = minSuccessful(50) + forAll(collGen, minSuccess) { col => + val n = col.length / 2 + val suffix = col.slice(n, col.length) + col.endsWith(suffix) shouldBe true + val pairs = col.zip(col) + pairs.endsWith(suffix.zip(suffix)) shouldBe true + val pairOfCols = new PairOfCols[Int, Int](col, col) + pairOfCols.endsWith(pairOfCols.slice(n, col.length)) shouldBe true + } + } + property("Coll.equals") { def checkColls(repl: Coll[_], coll: Coll[_]) = { assert(coll == repl) diff --git a/data/shared/src/main/scala/sigma/SigmaDataReflection.scala b/data/shared/src/main/scala/sigma/SigmaDataReflection.scala index c64bdee877..341ee647b3 100644 --- a/data/shared/src/main/scala/sigma/SigmaDataReflection.scala +++ b/data/shared/src/main/scala/sigma/SigmaDataReflection.scala @@ -317,6 +317,22 @@ object SigmaDataReflection { mkMethod(clazz, "flatMap_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[Function1[_,_]], classOf[ErgoTreeEvaluator])) { (obj, args) => obj.asInstanceOf[SCollectionMethods.type].flatMap_eval(args(0).asInstanceOf[MethodCall], args(1).asInstanceOf[Coll[Any]], args(2).asInstanceOf[Any => Coll[Any]])(args(3).asInstanceOf[ErgoTreeEvaluator]) + }, + mkMethod(clazz, "reverse_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) => + obj.asInstanceOf[SCollectionMethods.type].reverse_eval(args(0).asInstanceOf[MethodCall], + args(1).asInstanceOf[Coll[Any]])(args(2).asInstanceOf[ErgoTreeEvaluator]) + }, + mkMethod(clazz, "distinct_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) => + obj.asInstanceOf[SCollectionMethods.type].distinct_eval(args(0).asInstanceOf[MethodCall], + args(1).asInstanceOf[Coll[Any]])(args(2).asInstanceOf[ErgoTreeEvaluator]) + }, + mkMethod(clazz, "startsWith_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) => + obj.asInstanceOf[SCollectionMethods.type].startsWith_eval(args(0).asInstanceOf[MethodCall], + args(1).asInstanceOf[Coll[Any]], args(2).asInstanceOf[Coll[Any]])(args(3).asInstanceOf[ErgoTreeEvaluator]) + }, + mkMethod(clazz, "endsWith_eval", Array[Class[_]](classOf[MethodCall], classOf[Coll[_]], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) => + obj.asInstanceOf[SCollectionMethods.type].endsWith_eval(args(0).asInstanceOf[MethodCall], + args(1).asInstanceOf[Coll[Any]], args(2).asInstanceOf[Coll[Any]])(args(3).asInstanceOf[ErgoTreeEvaluator]) } ) ) diff --git a/data/shared/src/main/scala/sigma/ast/methods.scala b/data/shared/src/main/scala/sigma/ast/methods.scala index 2ba314ba7d..4cba72e28d 100644 --- a/data/shared/src/main/scala/sigma/ast/methods.scala +++ b/data/shared/src/main/scala/sigma/ast/methods.scala @@ -1020,7 +1020,7 @@ object SCollectionMethods extends MethodsContainer with MethodByNameUnapply { SFunc(Array(ThisType, tIV, SInt), SInt, paramIVSeq), 26, PerItemCost(baseCost = JitCost(20), perChunkCost = JitCost(10), chunkSize = 2)) .withIRInfo(MethodCallIrBuilder, javaMethodOf[Coll[_], Any, Int]("indexOf")) - .withInfo(MethodCall, "") + .withInfo(MethodCall, "Returns index of a collection element, or -1 if not found") /** Implements evaluation of Coll.indexOf method call ErgoTree node. * Called via reflection based on naming convention. @@ -1052,8 +1052,7 @@ object SCollectionMethods extends MethodsContainer with MethodByNameUnapply { baseCost = JitCost(10), perChunkCost = JitCost(1), chunkSize = 10) val ZipMethod = SMethod(this, "zip", - SFunc(Array(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Array[STypeParam](tIV, tOV)), - 29, Zip_CostKind) + SFunc(Array(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Array[STypeParam](tIV, tOV)), 29, Zip_CostKind) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, "") @@ -1069,29 +1068,133 @@ object SCollectionMethods extends MethodsContainer with MethodByNameUnapply { } } + // ======== 6.0 methods below =========== + + private val reverseCostKind = Append.costKind + + val ReverseMethod = SMethod(this, "reverse", + SFunc(Array(ThisType), ThisType, paramIVSeq), 30, reverseCostKind) + .withIRInfo(MethodCallIrBuilder) + .withInfo(MethodCall, "") + + /** Implements evaluation of Coll.reverse method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ + def reverse_eval[A](mc: MethodCall, xs: Coll[A]) + (implicit E: ErgoTreeEvaluator): Coll[A] = { + val m = mc.method + E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () => + xs.reverse + } + } + + private val distinctCostKind = PerItemCost(baseCost = JitCost(60), perChunkCost = JitCost(5), chunkSize = 100) + + val DistinctMethod = SMethod(this, "distinct", + SFunc(Array(ThisType), ThisType, paramIVSeq), 31, distinctCostKind) + .withIRInfo(MethodCallIrBuilder) + .withInfo(MethodCall, "Returns inversed collection.") + + /** Implements evaluation of Coll.reverse method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ + def distinct_eval[A](mc: MethodCall, xs: Coll[A]) + (implicit E: ErgoTreeEvaluator): Coll[A] = { + val m = mc.method + E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () => + xs.distinct + } + } + + private val startsWithCostKind = Zip_CostKind + + val StartsWithMethod = SMethod(this, "startsWith", + SFunc(Array(ThisType, ThisType), SBoolean, paramIVSeq), 32, startsWithCostKind) + .withIRInfo(MethodCallIrBuilder) + .withInfo(MethodCall, "Returns true if this collection starts with given one, false otherwise.", + ArgInfo("prefix", "Collection to be checked for being a prefix of this collection.")) + + /** Implements evaluation of Coll.zip method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ + def startsWith_eval[A](mc: MethodCall, xs: Coll[A], ys: Coll[A]) + (implicit E: ErgoTreeEvaluator): Boolean = { + val m = mc.method + E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () => + xs.startsWith(ys) + } + } + + private val endsWithCostKind = Zip_CostKind + + val EndsWithMethod = SMethod(this, "endsWith", + SFunc(Array(ThisType, ThisType), SBoolean, paramIVSeq), 33, endsWithCostKind) + .withIRInfo(MethodCallIrBuilder) + .withInfo(MethodCall, "Returns true if this collection ends with given one, false otherwise.", + ArgInfo("suffix", "Collection to be checked for being a suffix of this collection.")) + + /** Implements evaluation of Coll.zip method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ + def endsWith_eval[A](mc: MethodCall, xs: Coll[A], ys: Coll[A]) + (implicit E: ErgoTreeEvaluator): Boolean = { + val m = mc.method + E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () => + xs.endsWith(ys) + } + } + + val GetMethod = SMethod(this, "get", + SFunc(Array(ThisType, SInt), SOption(tIV), Array[STypeParam](tIV)), 34, ByIndex.costKind) + .withIRInfo(MethodCallIrBuilder) + .withInfo(MethodCall, + "Returns Some(element) if there is an element at given index, None otherwise.", + ArgInfo("index", "Index of an element (starting from 0).") + ) + + private val v5Methods = super.getMethods() ++ Seq( + SizeMethod, + GetOrElseMethod, + MapMethod, + ExistsMethod, + FoldMethod, + ForallMethod, + SliceMethod, + FilterMethod, + AppendMethod, + ApplyMethod, + IndicesMethod, + FlatMapMethod, + PatchMethod, + UpdatedMethod, + UpdateManyMethod, + IndexOfMethod, + ZipMethod + ) + + private val v6Methods = v5Methods ++ Seq( + ReverseMethod, + DistinctMethod, + StartsWithMethod, + EndsWithMethod, + GetMethod + ) + /** This method should be overriden in derived classes to add new methods in addition to inherited. * Typical override: `super.getMethods() ++ Seq(m1, m2, m3)` */ - override protected def getMethods(): Seq[SMethod] = super.getMethods() ++ - Seq( - SizeMethod, - GetOrElseMethod, - MapMethod, - ExistsMethod, - FoldMethod, - ForallMethod, - SliceMethod, - FilterMethod, - AppendMethod, - ApplyMethod, - IndicesMethod, - FlatMapMethod, - PatchMethod, - UpdatedMethod, - UpdateManyMethod, - IndexOfMethod, - ZipMethod - ) + override protected def getMethods(): Seq[SMethod] = { + if (VersionContext.current.isV6SoftForkActivated) { + v6Methods + } else { + v5Methods + } + } + } object STupleMethods extends MethodsContainer { diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala index 9aecb24a89..37550e3430 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala @@ -988,6 +988,19 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => val i = asRep[Int](argsV(0)) val d = asRep[t](argsV(1)) xs.getOrElse(i, d) + case SCollectionMethods.ReverseMethod.name => + xs.reverse + case SCollectionMethods.DistinctMethod.name => + xs.distinct + case SCollectionMethods.StartsWithMethod.name => + val ys = asRep[Coll[t]](argsV(0)) + xs.startsWith(ys) + case SCollectionMethods.EndsWithMethod.name => + val ys = asRep[Coll[t]](argsV(0)) + xs.endsWith(ys) + case SCollectionMethods.GetMethod.name => + val idx = asRep[Int](argsV(0)) + xs.get(idx) case _ => throwError() } case (opt: ROption[t]@unchecked, SOptionMethods) => method.name match { diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala b/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala index c8399099d6..8f461e502e 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala @@ -203,6 +203,22 @@ object GraphIRReflection { }, mkMethod(clazz, "exists", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) => obj.asInstanceOf[ctx.Coll[Any]].exists(args(0).asInstanceOf[ctx.Ref[Any => Boolean]]) + }, + // V6 methods + mkMethod(clazz, "reverse", Array[Class[_]]()) { (obj, _) => + obj.asInstanceOf[ctx.Coll[Any]].reverse + }, + mkMethod(clazz, "distinct", Array[Class[_]]()) { (obj, _) => + obj.asInstanceOf[ctx.Coll[Any]].distinct + }, + mkMethod(clazz, "startsWith", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) => + obj.asInstanceOf[ctx.Coll[Any]].startsWith(args(0).asInstanceOf[ctx.Ref[ctx.Coll[Any]]]) + }, + mkMethod(clazz, "endsWith", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) => + obj.asInstanceOf[ctx.Coll[Any]].endsWith(args(0).asInstanceOf[ctx.Ref[ctx.Coll[Any]]]) + }, + mkMethod(clazz, "get", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) => + obj.asInstanceOf[ctx.Coll[_]].apply(args(0).asInstanceOf[ctx.Ref[Int]]) } ) ) diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/CollsUnit.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/CollsUnit.scala index 9b4a002a14..fdc9fadcba 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/CollsUnit.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/CollsUnit.scala @@ -14,6 +14,7 @@ import sigma.compiler.ir.{Base, IRContext} implicit def eA: Elem[A]; def length: Ref[Int]; def apply(i: Ref[Int]): Ref[A]; + def get(index: Ref[Int]): Ref[WOption[A]]; def getOrElse(index: Ref[Int], default: Ref[A]): Ref[A]; def map[B](f: Ref[scala.Function1[A, B]]): Ref[Coll[B]]; def zip[B](ys: Ref[Coll[B]]): Ref[Coll[scala.Tuple2[A, B]]]; @@ -29,6 +30,10 @@ import sigma.compiler.ir.{Base, IRContext} def updateMany(indexes: Ref[Coll[Int]], values: Ref[Coll[A]]): Ref[Coll[A]]; def slice(from: Ref[Int], until: Ref[Int]): Ref[Coll[A]]; def append(other: Ref[Coll[A]]): Ref[Coll[A]]; + def reverse: Ref[Coll[A]] + def distinct: Ref[Coll[A]] + def startsWith(ys: Ref[Coll[A]]): Ref[Boolean]; + def endsWith(ys: Ref[Coll[A]]): Ref[Boolean]; }; trait CollBuilder extends Def[CollBuilder] { def fromItems[T](items: Ref[T]*)(implicit cT: Elem[T]): Ref[Coll[T]]; diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/CollsImpl.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/CollsImpl.scala index 0a18ea586a..04a7070acf 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/CollsImpl.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/CollsImpl.scala @@ -63,6 +63,14 @@ class CollCls extends EntityObject("Coll") { true, false, element[A])) } + override def get(index: Ref[Int]): Ref[WOption[A]] = { + asRep[WOption[A]](mkMethodCall(self, + CollClass.getMethod("get", classOf[Sym]), + Array[AnyRef](index), + true, false, element[WOption[A]])) + } + + override def map[B](f: Ref[A => B]): Ref[Coll[B]] = { implicit val eB = f.elem.eRange asRep[Coll[B]](mkMethodCall(self, @@ -164,6 +172,34 @@ class CollCls extends EntityObject("Coll") { Array[AnyRef](other), true, false, element[Coll[A]])) } + + override def reverse: Ref[Coll[A]] = { + asRep[Coll[A]](mkMethodCall(self, + CollClass.getMethod("reverse"), + Array[AnyRef](), + true, false, element[Coll[A]])) + } + + override def distinct: Ref[Coll[A]] = { + asRep[Coll[A]](mkMethodCall(self, + CollClass.getMethod("distinct"), + Array[AnyRef](), + true, false, element[Coll[A]])) + } + + def startsWith(ys: Ref[Coll[A]]): Ref[Boolean] = { + asRep[Boolean](mkMethodCall(self, + CollClass.getMethod("startsWith", classOf[Sym]), + Array[AnyRef](ys), + true, false, element[Boolean])) + } + + def endsWith(ys: Ref[Coll[A]]): Ref[Boolean] = { + asRep[Boolean](mkMethodCall(self, + CollClass.getMethod("endsWith", classOf[Sym]), + Array[AnyRef](ys), + true, false, element[Boolean])) + } } case class LiftableColl[SA, A](lA: Liftable[SA, A]) @@ -210,6 +246,13 @@ class CollCls extends EntityObject("Coll") { true, true, element[A])) } + def get(index: Ref[Int]): Ref[WOption[A]] = { + asRep[WOption[A]](mkMethodCall(source, + CollClass.getMethod("get", classOf[Sym]), + Array[AnyRef](index), + true, true, element[WOption[A]])) + } + def map[B](f: Ref[A => B]): Ref[Coll[B]] = { implicit val eB = f.elem.eRange asRep[Coll[B]](mkMethodCall(source, @@ -311,6 +354,34 @@ class CollCls extends EntityObject("Coll") { Array[AnyRef](other), true, true, element[Coll[A]])) } + + def reverse: Ref[Coll[A]] = { + asRep[Coll[A]](mkMethodCall(source, + CollClass.getMethod("reverse"), + Array[AnyRef](), + true, true, element[Coll[A]])) + } + + def distinct: Ref[Coll[A]] = { + asRep[Coll[A]](mkMethodCall(source, + CollClass.getMethod("distinct"), + Array[AnyRef](), + true, true, element[Coll[A]])) + } + + def startsWith(ys: Ref[Coll[A]]): Ref[Boolean] = { + asRep[Boolean](mkMethodCall(source, + CollClass.getMethod("startsWith", classOf[Sym]), + Array[AnyRef](ys), + true, true, element[Boolean])) + } + + def endsWith(ys: Ref[Coll[A]]): Ref[Boolean] = { + asRep[Boolean](mkMethodCall(source, + CollClass.getMethod("endsWith", classOf[Sym]), + Array[AnyRef](ys), + true, true, element[Boolean])) + } } // entityUnref: single unref method for each type family diff --git a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala index a365d60ad8..607e480c31 100644 --- a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala +++ b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala @@ -12,7 +12,7 @@ import sigma.ast.SCollection.SByteArray import sigma.ast.syntax.TrueSigmaProp import sigma.ast.{SInt, _} import sigma.data.{AvlTreeData, AvlTreeFlags, CAnyValue, CAvlTree, CBigInt, CBox, CHeader, CSigmaProp, ExactNumeric, ProveDHTuple, RType} -import sigma.data.{CBigInt, CBox, CHeader, CSigmaDslBuilder, ExactNumeric, RType} +import sigma.data.{CBigInt, CBox, CHeader, CSigmaDslBuilder, ExactNumeric, PairOfCols, RType} import sigma.eval.{CostDetails, SigmaDsl, TracedCost} import sigma.serialization.ValueCodes.OpCode import sigma.util.Extensions.{BooleanOps, IntOps} @@ -1758,4 +1758,98 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => } + property("Coll.reverse") { + val f = newFeature[Coll[Int], Coll[Int]]( + { (xs: Coll[Int]) => xs.reverse }, + """{(xs: Coll[Int]) => xs.reverse }""".stripMargin, + sinceVersion = VersionContext.V6SoftForkVersion + ) + + verifyCases( + Seq( + Coll(1, 2) -> Expected(ExpectedResult(Success(Coll(2, 1)), None)), + Coll[Int]() -> Expected(ExpectedResult(Success(Coll[Int]()), None)) + ), + f + ) + } + + property("Coll.distinct") { + val f = newFeature[Coll[Int], Coll[Int]]( + { (xs: Coll[Int]) => xs.distinct }, + """{(xs: Coll[Int]) => xs.distinct }""".stripMargin, + sinceVersion = VersionContext.V6SoftForkVersion + ) + + verifyCases( + Seq( + Coll(1, 2) -> Expected(ExpectedResult(Success(Coll(1, 2)), None)), + Coll(1, 1, 2) -> Expected(ExpectedResult(Success(Coll(1, 2)), None)), + Coll(1, 2, 2) -> Expected(ExpectedResult(Success(Coll(1, 2)), None)), + Coll(2, 2, 2) -> Expected(ExpectedResult(Success(Coll(2)), None)), + Coll(3, 1, 2, 2, 2, 4, 4, 1) -> Expected(ExpectedResult(Success(Coll(3, 1, 2, 4)), None)), + Coll[Int]() -> Expected(ExpectedResult(Success(Coll[Int]()), None)) + ), + f + ) + } + + property("Coll.startsWith") { + val f = newFeature[(Coll[Int], Coll[Int]), Boolean]( + { (xs: (Coll[Int], Coll[Int])) => xs._1.startsWith(xs._2) }, + """{(xs: (Coll[Int], Coll[Int])) => xs._1.startsWith(xs._2) }""".stripMargin, + sinceVersion = VersionContext.V6SoftForkVersion + ) + + verifyCases( + Seq( + (Coll(1, 2, 3), Coll(1, 2)) -> Expected(ExpectedResult(Success(true), None)), + (Coll(1, 2, 3), Coll(1, 2, 3)) -> Expected(ExpectedResult(Success(true), None)), + (Coll(1, 2, 3), Coll(1, 2, 4)) -> Expected(ExpectedResult(Success(false), None)), + (Coll(1, 2, 3), Coll(1, 2, 3, 4)) -> Expected(ExpectedResult(Success(false), None)), + (Coll[Int](), Coll[Int]()) -> Expected(ExpectedResult(Success(true), None)), + (Coll[Int](1, 2), Coll[Int]()) -> Expected(ExpectedResult(Success(true), None)) + ), + f + ) + } + + property("Coll.endsWith") { + val f = newFeature[(Coll[Int], Coll[Int]), Boolean]( + { (xs: (Coll[Int], Coll[Int])) => xs._1.endsWith(xs._2) }, + """{(xs: (Coll[Int], Coll[Int])) => xs._1.endsWith(xs._2) }""".stripMargin, + sinceVersion = VersionContext.V6SoftForkVersion + ) + + verifyCases( + Seq( + (Coll(1, 2, 3), Coll(1, 2)) -> Expected(ExpectedResult(Success(false), None)), + (Coll(1, 2, 3), Coll(2, 3)) -> Expected(ExpectedResult(Success(true), None)), + (Coll(1, 2, 3), Coll(2, 3, 4)) -> Expected(ExpectedResult(Success(false), None)), + (Coll(1, 2, 3), Coll(1, 2, 3)) -> Expected(ExpectedResult(Success(true), None)), + (Coll[Int](), Coll[Int]()) -> Expected(ExpectedResult(Success(true), None)) + ), + f + ) + } + + property("Coll.get") { + val f = newFeature[(Coll[Int], Int), Option[Int]]( + { (xs: (Coll[Int], Int)) => xs._1.get(xs._2) }, + """{(xs: (Coll[Int], Int)) => xs._1.get(xs._2) }""".stripMargin, + sinceVersion = VersionContext.V6SoftForkVersion + ) + + verifyCases( + Seq( + (Coll(1, 2), 0) -> Expected(ExpectedResult(Success(Some(1)), None)), + (Coll(1, 2), 1) -> Expected(ExpectedResult(Success(Some(2)), None)), + (Coll(1, 2), -1) -> Expected(ExpectedResult(Success(None), None)), + (Coll(1, 2), 2) -> Expected(ExpectedResult(Success(None), None)), + (Coll[Int](), 0) -> Expected(ExpectedResult(Success(None), None)) + ), + f + ) + } + } diff --git a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala index 86624436cb..1c38ca45a0 100644 --- a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala @@ -565,7 +565,9 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit with C EndsWithMethod, MapReduceMethod, */ - ), true) + ) ++ (if (isV6Activated) { + Seq(MInfo(30, ReverseMethod), MInfo(31, DistinctMethod), MInfo(32, StartsWithMethod), MInfo(33, EndsWithMethod), MInfo(34, GetMethod)) + } else Seq.empty), true) }, { import SOptionMethods._ (SOption.typeId, Seq( diff --git a/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala b/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala index cc41c2bcdf..3c93cbdf06 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala @@ -627,6 +627,162 @@ class BasicOpsSpecification extends CompilerTestingCommons } } + property("Coll.reverse"){ + def reverseTest() = test("reverse", env, ext, + """{ + | val c1 = Coll(1, 2, 3) + | val c2 = Coll(3, 2, 1) + | + | val b1 = Coll(INPUTS(0), OUTPUTS(0)) + | val b2 = Coll(OUTPUTS(0), INPUTS(0)) + | + | c1.reverse == c2 && b1.reverse == b2 + | }""".stripMargin, + null + ) + + if(VersionContext.current.isV6SoftForkActivated) { + reverseTest() + } else { + an[Exception] shouldBe thrownBy(reverseTest()) + } + } + + property("Coll.distinct"){ + def reverseTest() = test("distinct", env, ext, + """{ + | val c1 = Coll(1, 2, 3, 3, 2) + | val c2 = Coll(3, 2, 1) + | + | val h1 = Coll(INPUTS(0), INPUTS(0)) + | val h2 = Coll(INPUTS(0)) + | + | c1.distinct.reverse == c2 && h1.distinct == h2 + | }""".stripMargin, + null + ) + + if(VersionContext.current.isV6SoftForkActivated) { + reverseTest() + } else { + an[Exception] shouldBe thrownBy(reverseTest()) + } + } + + property("Coll.startsWith"){ + def reverseTest() = test("distinct", env, ext, + """{ + | val c1 = Coll(1, 2, 3) + | val c2 = Coll(1, 2) + | val c3 = Coll(1, 3) + | val c4 = Coll[Int]() + | val c5 = Coll(1, 2, 3, 4) + | + | val b1 = c1.startsWith(c3) + | val b2 = c1.startsWith(c5) + | + | c1.startsWith(c2) && c1.startsWith(c4) && c1.startsWith(c1) && !b1 && !b2 + | }""".stripMargin, + null + ) + + if(VersionContext.current.isV6SoftForkActivated) { + reverseTest() + } else { + an[Exception] shouldBe thrownBy(reverseTest()) + } + } + + property("Coll.startsWith - tuples"){ + def reverseTest() = test("distinct", env, ext, + """{ + | val c1 = Coll((1, 2), (3, 4), (5, 6)) + | val c2 = Coll((1, 2), (3, 4)) + | val c3 = Coll((1, 3)) + | val c4 = Coll[(Int, Int)]() + | val c5 = Coll((1, 2), (3, 4), (5, 6), (7, 8)) + | + | val b1 = c1.startsWith(c3) + | val b2 = c1.startsWith(c5) + | + | c1.startsWith(c2) && c1.startsWith(c4) && c1.startsWith(c1) && !b1 && !b2 + | }""".stripMargin, + null + ) + + if(VersionContext.current.isV6SoftForkActivated) { + reverseTest() + } else { + an[Exception] shouldBe thrownBy(reverseTest()) + } + } + + property("Coll.endsWith"){ + def reverseTest() = test("distinct", env, ext, + """{ + | val c1 = Coll(1, 2, 3) + | val c2 = Coll(2, 3) + | val c3 = Coll(2, 2) + | val c4 = Coll[Int]() + | val c5 = Coll(1, 2, 3, 4) + | + | val b1 = c1.endsWith(c3) + | val b2 = c1.endsWith(c5) + | + | c1.endsWith(c2) && c1.endsWith(c4) && c1.endsWith(c1) && !b1 && !b2 + | }""".stripMargin, + null + ) + + if(VersionContext.current.isV6SoftForkActivated) { + reverseTest() + } else { + an[Exception] shouldBe thrownBy(reverseTest()) + } + } + + property("Coll.endsWith - tuples"){ + def reverseTest() = test("endsWith tuples", env, ext, + """{ + | val c1 = Coll((1, 2), (2, 3)) + | val c2 = Coll((2, 3)) + | val c3 = Coll((2, 2)) + | val c4 = Coll[(Int, Int)]() + | val c5 = Coll((0, 2), (2, 3)) + | + | val b1 = c1.endsWith(c3) + | val b2 = c1.endsWith(c5) + | + | c1.endsWith(c2) && c1.endsWith(c4) && c1.endsWith(c1) && !b1 && !b2 + | }""".stripMargin, + null + ) + + if(VersionContext.current.isV6SoftForkActivated) { + reverseTest() + } else { + an[Exception] shouldBe thrownBy(reverseTest()) + } + } + + property("Coll.get"){ + def getTest() = test("get", env, ext, + """{ + | val c1 = Coll(1) + | val c2 = Coll[Int]() + | + | c2.get(0).getOrElse(c1.get(0).get) == c1.get(0).get + | }""".stripMargin, + null + ) + + if(VersionContext.current.isV6SoftForkActivated) { + getTest() + } else { + an[Exception] shouldBe thrownBy(getTest()) + } + } + property("Global.fromBigEndianBytes - byte") { def fromTest() = test("fromBigEndianBytes - byte", env, ext, s"""{