From e9dd2a5a1101d846081e27fec508d61c894d3707 Mon Sep 17 00:00:00 2001 From: alexandr Date: Wed, 9 May 2018 18:18:50 +0300 Subject: [PATCH 1/6] EvaluatorV1_1 twice faster --- .../wavesplatform/state/RealDbBenchmark.scala | 1 - .../lang/v1/ScriptEstimatorBenchmark.scala | 8 ++ build.sbt | 2 + .../wavesplatform/lang/EvaluatorV1Test.scala | 12 ++ .../com/wavesplatform/lang/v1/Terms.scala | 2 +- .../wavesplatform/lang/v1/ctx/Context.scala | 10 +- .../lang/v1/evaluation/CoevalRef.scala | 32 ++++++ .../lang/v1/evaluation/EvalM.scala | 75 ++++++++++++ .../lang/v1/evaluation/Evaluation.scala | 6 + .../lang/v1/evaluation/EvaluationEnv.scala | 18 +++ .../lang/v1/evaluation/EvaluationError.scala | 4 + .../lang/v1/evaluation/EvaluatorV1_1.scala | 107 ++++++++++++++++++ project/Dependencies.scala | 1 + 13 files changed, 275 insertions(+), 3 deletions(-) delete mode 100644 benchmark/src/main/scala/com/wavesplatform/state/RealDbBenchmark.scala create mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/CoevalRef.scala create mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala create mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/Evaluation.scala create mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationEnv.scala create mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationError.scala create mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1_1.scala diff --git a/benchmark/src/main/scala/com/wavesplatform/state/RealDbBenchmark.scala b/benchmark/src/main/scala/com/wavesplatform/state/RealDbBenchmark.scala deleted file mode 100644 index 8b137891791..00000000000 --- a/benchmark/src/main/scala/com/wavesplatform/state/RealDbBenchmark.scala +++ /dev/null @@ -1 +0,0 @@ - diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEstimatorBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEstimatorBenchmark.scala index 5a11f790473..495976aab72 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEstimatorBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEstimatorBenchmark.scala @@ -4,6 +4,8 @@ import java.util.concurrent.TimeUnit import com.wavesplatform.lang.v1.ScriptEstimatorBenchmark.St import com.wavesplatform.lang.v1.ctx.Context +import com.wavesplatform.lang.v1.ctx.impl.PureContext +import com.wavesplatform.lang.v1.evaluation.EvaluatorV1_1 import com.wavesplatform.utils import org.openjdk.jmh.annotations._ import org.openjdk.jmh.infra.Blackhole @@ -19,6 +21,12 @@ class ScriptEstimatorBenchmark { @Benchmark def apply_test(st: St, bh: Blackhole): Unit = bh.consume(ScriptEstimator(st.functionCosts, st.expr)) + @Benchmark + def v1_test(st: St, bh: Blackhole): Unit = bh.consume(EvaluatorV1[Long](PureContext.instance, st.expr)) + + @Benchmark + def v1_1_test(st: St, bh: Blackhole): Unit = bh.consume(EvaluatorV1_1[Long](PureContext.instance, st.expr)) + } object ScriptEstimatorBenchmark { diff --git a/build.sbt b/build.sbt index 33c45160066..a3fcbc07ac9 100644 --- a/build.sbt +++ b/build.sbt @@ -181,6 +181,7 @@ lazy val lang = .settings( version := "0.0.1", test in assembly := {}, + addCompilerPlugin(Dependencies.kindProjector), libraryDependencies ++= Dependencies.cats ++ Dependencies.scalacheck ++ @@ -209,6 +210,7 @@ lazy val langJVM = lang.jvm lazy val node = project .in(file(".")) .settings( + addCompilerPlugin(Dependencies.kindProjector), libraryDependencies ++= Dependencies.network ++ Dependencies.db ++ diff --git a/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala b/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala index 31da876fa32..01cddcd496f 100644 --- a/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala +++ b/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala @@ -11,6 +11,7 @@ import com.wavesplatform.lang.v1.ctx.Context._ import com.wavesplatform.lang.v1.ctx._ import com.wavesplatform.lang.v1.ctx.impl.PureContext._ import com.wavesplatform.lang.v1.ctx.impl.{CryptoContext, PureContext} +import com.wavesplatform.lang.v1.evaluation.EvaluatorV1_1 import com.wavesplatform.lang.v1.testing.ScriptGen import com.wavesplatform.lang.v1.{EvaluatorV1, FunctionHeader} import org.scalatest.prop.PropertyChecks @@ -25,8 +26,19 @@ class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with Sc EvaluatorV1[T](context, expr) private def simpleDeclarationAndUsage(i: Int) = BLOCK(LET("x", CONST_LONG(i)), REF("x", LONG), LONG) + private def logTime[A](in: => A): A = { + val start = System.currentTimeMillis() + in + println(System.currentTimeMillis() - start) + in + } + property("successful on very deep expressions (stack overflow check)") { val term = (1 to 100000).foldLeft[EXPR](CONST_LONG(0))((acc, _) => FUNCTION_CALL(sumLong.header, List(acc, CONST_LONG(1)), LONG)) + + val r1 = logTime(ev[Long](expr = term)) + val r2 = logTime(EvaluatorV1_1[Long](PureContext.instance, term)) + ev[Long](expr = term) shouldBe Right(100000) } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/Terms.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/Terms.scala index 2bdd5098191..0122751658e 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/Terms.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/Terms.scala @@ -13,7 +13,7 @@ object Terms { case class OPTIONTYPEPARAM(t: TYPEPLACEHOLDER) extends TYPEPLACEHOLDER sealed trait TYPE extends TYPEPLACEHOLDER { - type Underlying + type Underlying <: Any def typeInfo: TypeInfo[Underlying] } sealed abstract class AUTO_TAGGED_TYPE[T](implicit override val typeInfo: TypeInfo[T]) extends TYPE { diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/ctx/Context.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/ctx/Context.scala index 61c22079f15..fe5d47c0330 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/ctx/Context.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/ctx/Context.scala @@ -1,11 +1,19 @@ package com.wavesplatform.lang.v1.ctx import cats._ -import com.wavesplatform.lang.v1.FunctionHeader +import com.wavesplatform.lang.v1.{EvaluationContext, FunctionHeader} +import shapeless._ case class Context(typeDefs: Map[String, PredefType], letDefs: Map[String, LazyVal], functions: Map[FunctionHeader, PredefFunction]) object Context { + + object Lenses { + val types: Lens[Context, Map[String, PredefType]] = lens[Context] >> 'typeDefs + val lets: Lens[Context, Map[String, LazyVal]] = lens[Context] >> 'letDefs + val funcs: Lens[Context, Map[FunctionHeader, PredefFunction]] = lens[Context] >> 'functions + } + val empty = Context(Map.empty, Map.empty, Map.empty) implicit val monoid: Monoid[Context] = new Monoid[Context] { diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/CoevalRef.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/CoevalRef.scala new file mode 100644 index 00000000000..e1aa2112ff6 --- /dev/null +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/CoevalRef.scala @@ -0,0 +1,32 @@ +package com.wavesplatform.lang.v1.evaluation + +import java.util.concurrent.atomic.AtomicReference + +import monix.eval.Coeval + +sealed trait CoevalRef[A] { + def read: Coeval[A] + def write(a: A): Coeval[Unit] + def update(f: A => A): Coeval[Unit] +} + +object CoevalRef { + def of[A](a: A): CoevalRef[A] = { + new CoevalRef[A] { + + private val atom = new AtomicReference[A](a) + + override def read: Coeval[A] = Coeval.delay(atom.get()) + + override def write(a: A): Coeval[Unit] = Coeval.delay(atom.lazySet(a)) + + override def update(f: A => A): Coeval[Unit] = Coeval.delay { + for { + old <- read + upd = f(old) + _ <- write(upd) + } yield () + } + } + } +} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala new file mode 100644 index 00000000000..a2306995a7e --- /dev/null +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala @@ -0,0 +1,75 @@ +package com.wavesplatform.lang.v1.evaluation + +import cats.{Monad, ~>} +import cats.data.{EitherT, Kleisli} +import com.wavesplatform.lang.{ExecutionError, ExecutionLog, TrampolinedExecResult} +import com.wavesplatform.lang.v1.EvaluationContext +import com.wavesplatform.lang.v1.EvaluationContext._ +import com.wavesplatform.lang.v1.ctx.Context +import monix.eval.Coeval +import cats.implicits._ + +final case class EvalM[A](inner: Kleisli[Coeval, CoevalRef[EvaluationContext], A]) { + def ter(ctx: Context): TrampolinedExecResult[A] = { + val atom = CoevalRef.of(EvaluationContext(ctx)) + + EitherT[Coeval, ExecutionError, A] { + inner.run(atom) + .attempt + .map(_.leftMap(_.getMessage)) + } + } + + def run(ctx: Context): Either[(Context, ExecutionLog, ExecutionError), A] = { + val atom = CoevalRef.of(EvaluationContext(ctx)) + + val action = inner + .run(atom) + .attempt + + (for { + result <- action + lastCtx <- atom.read + } yield result.left.map(err => (lastCtx.context, lastCtx.getLog, err.getMessage))).value + } +} + +object EvalM { + type Inner[A] = Kleisli[Coeval, CoevalRef[EvaluationContext], A] + + private val innerMonad: Monad[Inner] = implicitly[Monad[Inner]] + + implicit val monadInstance: Monad[EvalM] = new Monad[EvalM] { + override def pure[A](x: A): EvalM[A] = + EvalM(Kleisli.pure[Coeval, CoevalRef[EvaluationContext], A](x)) + + override def flatMap[A, B](fa: EvalM[A])(f: A => EvalM[B]): EvalM[B] = + EvalM(fa.inner.flatMap(f andThen { _.inner })) + + override def tailRecM[A, B](a: A)(f: A => EvalM[Either[A, B]]): EvalM[B] = + EvalM(innerMonad.tailRecM(a)(f andThen { _.inner })) + } + + def getContext: EvalM[Context] = + EvalM(Kleisli(_.read.map(_.context))) + + def updateContext(f: Context => Context): EvalM[Unit] = + EvalM(Kleisli(_.update(context.modify(_)(f)))) + + def liftValue[A](a: A): EvalM[A] = + monadInstance.pure(a) + + def liftError[A](err: ExecutionError): EvalM[A] = + EvalM(Kleisli(_ => Coeval.raiseError[A](new Exception(err)))) + + def liftTER[A](ter: Coeval[Either[ExecutionError, A]]): EvalM[A] = + EvalM(Kleisli(_ => { + ter.flatMap({ + case Right(v) => Coeval.delay(v) + case Left(err) => Coeval.raiseError(new Exception(err)) + }) + })) + + def writeLog(l: String): EvalM[Unit] = + EvalM(Kleisli(_.update(_.logAppend(l)))) +} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/Evaluation.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/Evaluation.scala new file mode 100644 index 00000000000..616bd570253 --- /dev/null +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/Evaluation.scala @@ -0,0 +1,6 @@ +package com.wavesplatform.lang.v1.evaluation + +object Evaluation { + + +} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationEnv.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationEnv.scala new file mode 100644 index 00000000000..5f8bd90f333 --- /dev/null +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationEnv.scala @@ -0,0 +1,18 @@ +package com.wavesplatform.lang.v1.evaluation + +import com.wavesplatform.lang.v1.FunctionHeader +import com.wavesplatform.lang.v1.ctx.{Context, LazyVal, PredefFunction, PredefType} +import monix.eval.Coeval +import shapeless.{Lens, lens} + +final case class EvaluationEnv(context: Context, log: StringBuffer) { + def logAppend(l: String): Coeval[Unit] = Coeval.delay(log.append(l)) + def getLog: Coeval[String] = Coeval.delay(log.toString) +} + +object EvaluationEnv { + val context: Lens[EvaluationEnv, Context] = lens[EvaluationEnv] >> 'context + val types: Lens[EvaluationEnv, Map[String, PredefType]] = lens[EvaluationEnv] >> 'context >> 'typeDefs + val lets: Lens[EvaluationEnv, Map[String, LazyVal]] = lens[EvaluationEnv] >> 'context >> 'letDefs + val funcs: Lens[EvaluationEnv, Map[FunctionHeader, PredefFunction]] = lens[EvaluationEnv] >> 'context >> 'functions +} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationError.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationError.scala new file mode 100644 index 00000000000..297471fe3ef --- /dev/null +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationError.scala @@ -0,0 +1,4 @@ +package com.wavesplatform.lang.v1.evaluation + +sealed trait EvaluationError extends Throwable + diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1_1.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1_1.scala new file mode 100644 index 00000000000..7291b1dd7a2 --- /dev/null +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1_1.scala @@ -0,0 +1,107 @@ +package com.wavesplatform.lang.v1.evaluation + +import com.wavesplatform.lang.v1.Terms.{TYPE, Typed} +import com.wavesplatform.lang.v1.Terms.Typed._ +import com.wavesplatform.lang.v1.ctx.Context.Lenses._ +import cats.implicits._ +import com.wavesplatform.lang.{ExecutionError, ExecutionLog, TypeInfo} +import com.wavesplatform.lang.TypeInfo._ +import com.wavesplatform.lang.v1.FunctionHeader +import com.wavesplatform.lang.v1.ctx.{Context, LazyVal, Obj} + +object EvaluatorV1_1 { + + import EvalM._ + + private def evalBlock(let: LET, inner: EXPR, tpe: TYPE): EvalM[Any] = { + import let.{name, value} + for { + _ <- writeLog(s"LET: $LET, TYPE: $tpe") + ctx <- getContext + result <- { + if (lets.get(ctx).get(name).isDefined) + liftError(s"Value '$name' already defined in the scope") + else if (funcs.get(ctx).keys.exists(_.name == name)) + liftError(s"Value '$name' can't be defined because function with such name is predefined") + else { + val blockEvaluation = evalExpr(value)(value.tpe.typeInfo) + val lazyBlock = LazyVal(value.tpe)(blockEvaluation.ter(ctx)) + updateContext(lets.modify(_)(_.updated(name, lazyBlock))) *> evalExpr(inner)(tpe.typeInfo) + } + } + } yield result + } + + private def evalRef(key: String) = { + for { + _ <- writeLog(s"KEY: $key") + ctx <- getContext + result <- lets.get(ctx).get(key) match { + case Some(lzy) => liftTER[Any](lzy.value.value) + case None => liftError[Any](s"A definition of '$key' is not found") + } + } yield result + } + + private def evalIF(cond: EXPR, ifTrue: EXPR, ifFalse: EXPR, tpe: TYPE) = { + for { + _ <- writeLog("Evaluating IF") + ifc <- writeLog("Evaluating COND") *> evalExpr[Boolean](cond) + result <- ifc match { + case true => writeLog("Evaluating IF_TRUE") *> evalExpr(ifTrue)(tpe.typeInfo) + case false => writeLog("Evaluating IF_FALSE") *> evalExpr(ifFalse)(tpe.typeInfo) + } + } yield result + } + + private def evalGetter(expr: EXPR, field: String) = { + for { + obj <- evalExpr[Obj](expr) + result <- obj.fields.get(field) match { + case Some(lzy) => liftTER[Any](lzy.value.value) + case None => liftError[Any](s"field '$field' not found") + } + } yield result + } + + private def evalFunctionCall(header: FunctionHeader, args: List[EXPR]): EvalM[Any] = { + for { + _ <- writeLog(s"FUNCTION HEADER: $header") + ctx <- getContext + result <- funcs + .get(ctx) + .get(header) + .fold(liftError[Any](s"function '$header' not found")) { func => + args + .traverse[EvalM, Any](a => evalExpr(a)(a.tpe.typeInfo).map(_.asInstanceOf[Any])) + .map(func.eval) + .flatMap(r => liftTER[Any](r.value)) + } + } yield result + } + + private def evalExpr[T: TypeInfo](t: Typed.EXPR): EvalM[T] = { + (t match { + case Typed.BLOCK(let, inner, blockTpe) => writeLog("Evaluating BLOCK") *> evalBlock(let, inner, blockTpe) <* writeLog("FINISHED") + case Typed.REF(str, _) => writeLog("Evaluating REF") *> evalRef(str) <* writeLog("FINISHED") + case Typed.CONST_LONG(v) => liftValue(v) + case Typed.CONST_BYTEVECTOR(v) => liftValue(v) + case Typed.CONST_STRING(v) => liftValue(v) + case Typed.TRUE => liftValue(true) + case Typed.FALSE => liftValue(false) + case Typed.IF(cond, t1, t2, tpe) => writeLog("Evaluating IF") *> evalIF(cond, t1, t2, tpe) <* writeLog("FINISHED") + case Typed.GETTER(expr, field, _) => writeLog("Evaluating GETTER") *> evalGetter(expr, field) <* writeLog("FINISHED") + case Typed.FUNCTION_CALL(header, args, _) => writeLog("Evaluating FUNCTION_CALL") *> evalFunctionCall(header, args) <* writeLog("FINISHED") + }).flatMap(v => { + val ti = typeInfo[T] + if (t.tpe.typeInfo <:< ti) liftValue(v.asInstanceOf[T]) + else liftError(s"Bad type: expected: ${ti} actual: ${t.tpe.typeInfo}") + }) + + } + + def apply[A: TypeInfo](c: Context, expr: Typed.EXPR): Either[(Context, ExecutionLog, ExecutionError), A] = { + evalExpr[A](expr) + .run(c) + } +} diff --git a/project/Dependencies.scala b/project/Dependencies.scala index fc77e9feb33..551a12ccf5b 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -93,4 +93,5 @@ object Dependencies { "org.scalacheck" %% "scalacheck" % "1.13.5", "io.github.amrhassan" %% "scalacheck-cats" % "0.4.0" % Test ) + lazy val kindProjector = "org.spire-math" %% "kind-projector" % "0.9.6" } From dee27cf537893f50e7b6ae2279e5260389d712b2 Mon Sep 17 00:00:00 2001 From: alexandr Date: Wed, 9 May 2018 19:14:21 +0300 Subject: [PATCH 2/6] EvaluatorV1_1: Remove logging, EvaluationContext -> Context --- .../lang/v1/evaluation/EvalM.scala | 61 +++++++++---------- .../lang/v1/evaluation/Evaluation.scala | 6 -- .../lang/v1/evaluation/EvaluationEnv.scala | 18 ------ .../lang/v1/evaluation/EvaluationError.scala | 4 -- .../lang/v1/evaluation/EvaluatorV1_1.scala | 20 +++--- 5 files changed, 36 insertions(+), 73 deletions(-) delete mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/Evaluation.scala delete mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationEnv.scala delete mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationError.scala diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala index a2306995a7e..539d49e94b4 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala @@ -1,75 +1,70 @@ package com.wavesplatform.lang.v1.evaluation -import cats.{Monad, ~>} -import cats.data.{EitherT, Kleisli} +import cats.{Monad, StackSafeMonad, ~>} +import cats.data.{EitherT, Kleisli, WriterT} import com.wavesplatform.lang.{ExecutionError, ExecutionLog, TrampolinedExecResult} -import com.wavesplatform.lang.v1.EvaluationContext -import com.wavesplatform.lang.v1.EvaluationContext._ import com.wavesplatform.lang.v1.ctx.Context +import com.wavesplatform.lang.v1.ctx.Context.Lenses._ import monix.eval.Coeval import cats.implicits._ +import com.wavesplatform.lang.v1.evaluation.H.EC -final case class EvalM[A](inner: Kleisli[Coeval, CoevalRef[EvaluationContext], A]) { +object H { + type Env = (CoevalRef[Context], StringBuffer) + type EC[A] = EitherT[Coeval, ExecutionError, A] +} +final case class EvalM[A](inner: Kleisli[Coeval, CoevalRef[Context], Either[ExecutionError, A]]) { def ter(ctx: Context): TrampolinedExecResult[A] = { - val atom = CoevalRef.of(EvaluationContext(ctx)) + val atom = CoevalRef.of(ctx) - EitherT[Coeval, ExecutionError, A] { - inner.run(atom) - .attempt - .map(_.leftMap(_.getMessage)) - } + EitherT(inner.run(atom)) } def run(ctx: Context): Either[(Context, ExecutionLog, ExecutionError), A] = { - val atom = CoevalRef.of(EvaluationContext(ctx)) + val atom = CoevalRef.of(ctx) val action = inner .run(atom) - .attempt (for { result <- action lastCtx <- atom.read - } yield result.left.map(err => (lastCtx.context, lastCtx.getLog, err.getMessage))).value + } yield result.left.map(err => (lastCtx, "EMPTY", err))).value } } object EvalM { - type Inner[A] = Kleisli[Coeval, CoevalRef[EvaluationContext], A] + type Inner[A] = Kleisli[Coeval, CoevalRef[Context], A] private val innerMonad: Monad[Inner] = implicitly[Monad[Inner]] - implicit val monadInstance: Monad[EvalM] = new Monad[EvalM] { + implicit val monadInstance: Monad[EvalM] = new StackSafeMonad[EvalM] { override def pure[A](x: A): EvalM[A] = - EvalM(Kleisli.pure[Coeval, CoevalRef[EvaluationContext], A](x)) + EvalM(Kleisli.pure[Coeval, CoevalRef[Context], Either[ExecutionError, A]](x.asRight[ExecutionError])) override def flatMap[A, B](fa: EvalM[A])(f: A => EvalM[B]): EvalM[B] = - EvalM(fa.inner.flatMap(f andThen { _.inner })) - - override def tailRecM[A, B](a: A)(f: A => EvalM[Either[A, B]]): EvalM[B] = - EvalM(innerMonad.tailRecM(a)(f andThen { _.inner })) + EvalM(fa.inner.flatMap({ + case Right(v) => f(v).inner + case Left(err) => Kleisli.pure(err.asLeft[B]) + })) } def getContext: EvalM[Context] = - EvalM(Kleisli(_.read.map(_.context))) + EvalM(Kleisli[Coeval, CoevalRef[Context], Either[ExecutionError, Context]](ref => { + ref.read.map(_.asRight[ExecutionError]) + })) def updateContext(f: Context => Context): EvalM[Unit] = - EvalM(Kleisli(_.update(context.modify(_)(f)))) + EvalM(Kleisli[Coeval, CoevalRef[Context], Either[ExecutionError, Unit]](ref => { + ref.update(f).map(_.asRight[ExecutionError]) + })) def liftValue[A](a: A): EvalM[A] = monadInstance.pure(a) def liftError[A](err: ExecutionError): EvalM[A] = - EvalM(Kleisli(_ => Coeval.raiseError[A](new Exception(err)))) + EvalM(Kleisli[Coeval, CoevalRef[Context], Either[ExecutionError, A]](_ => Coeval.delay(err.asLeft[A]))) def liftTER[A](ter: Coeval[Either[ExecutionError, A]]): EvalM[A] = - EvalM(Kleisli(_ => { - ter.flatMap({ - case Right(v) => Coeval.delay(v) - case Left(err) => Coeval.raiseError(new Exception(err)) - }) - })) - - def writeLog(l: String): EvalM[Unit] = - EvalM(Kleisli(_.update(_.logAppend(l)))) + EvalM(Kleisli[Coeval, CoevalRef[Context], Either[ExecutionError, A]](_ => ter)) } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/Evaluation.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/Evaluation.scala deleted file mode 100644 index 616bd570253..00000000000 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/Evaluation.scala +++ /dev/null @@ -1,6 +0,0 @@ -package com.wavesplatform.lang.v1.evaluation - -object Evaluation { - - -} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationEnv.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationEnv.scala deleted file mode 100644 index 5f8bd90f333..00000000000 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationEnv.scala +++ /dev/null @@ -1,18 +0,0 @@ -package com.wavesplatform.lang.v1.evaluation - -import com.wavesplatform.lang.v1.FunctionHeader -import com.wavesplatform.lang.v1.ctx.{Context, LazyVal, PredefFunction, PredefType} -import monix.eval.Coeval -import shapeless.{Lens, lens} - -final case class EvaluationEnv(context: Context, log: StringBuffer) { - def logAppend(l: String): Coeval[Unit] = Coeval.delay(log.append(l)) - def getLog: Coeval[String] = Coeval.delay(log.toString) -} - -object EvaluationEnv { - val context: Lens[EvaluationEnv, Context] = lens[EvaluationEnv] >> 'context - val types: Lens[EvaluationEnv, Map[String, PredefType]] = lens[EvaluationEnv] >> 'context >> 'typeDefs - val lets: Lens[EvaluationEnv, Map[String, LazyVal]] = lens[EvaluationEnv] >> 'context >> 'letDefs - val funcs: Lens[EvaluationEnv, Map[FunctionHeader, PredefFunction]] = lens[EvaluationEnv] >> 'context >> 'functions -} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationError.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationError.scala deleted file mode 100644 index 297471fe3ef..00000000000 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluationError.scala +++ /dev/null @@ -1,4 +0,0 @@ -package com.wavesplatform.lang.v1.evaluation - -sealed trait EvaluationError extends Throwable - diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1_1.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1_1.scala index 7291b1dd7a2..f353d669b35 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1_1.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1_1.scala @@ -16,7 +16,6 @@ object EvaluatorV1_1 { private def evalBlock(let: LET, inner: EXPR, tpe: TYPE): EvalM[Any] = { import let.{name, value} for { - _ <- writeLog(s"LET: $LET, TYPE: $tpe") ctx <- getContext result <- { if (lets.get(ctx).get(name).isDefined) @@ -34,7 +33,6 @@ object EvaluatorV1_1 { private def evalRef(key: String) = { for { - _ <- writeLog(s"KEY: $key") ctx <- getContext result <- lets.get(ctx).get(key) match { case Some(lzy) => liftTER[Any](lzy.value.value) @@ -45,11 +43,10 @@ object EvaluatorV1_1 { private def evalIF(cond: EXPR, ifTrue: EXPR, ifFalse: EXPR, tpe: TYPE) = { for { - _ <- writeLog("Evaluating IF") - ifc <- writeLog("Evaluating COND") *> evalExpr[Boolean](cond) + ifc <- evalExpr[Boolean](cond) result <- ifc match { - case true => writeLog("Evaluating IF_TRUE") *> evalExpr(ifTrue)(tpe.typeInfo) - case false => writeLog("Evaluating IF_FALSE") *> evalExpr(ifFalse)(tpe.typeInfo) + case true => evalExpr(ifTrue)(tpe.typeInfo) + case false => evalExpr(ifFalse)(tpe.typeInfo) } } yield result } @@ -66,7 +63,6 @@ object EvaluatorV1_1 { private def evalFunctionCall(header: FunctionHeader, args: List[EXPR]): EvalM[Any] = { for { - _ <- writeLog(s"FUNCTION HEADER: $header") ctx <- getContext result <- funcs .get(ctx) @@ -82,16 +78,16 @@ object EvaluatorV1_1 { private def evalExpr[T: TypeInfo](t: Typed.EXPR): EvalM[T] = { (t match { - case Typed.BLOCK(let, inner, blockTpe) => writeLog("Evaluating BLOCK") *> evalBlock(let, inner, blockTpe) <* writeLog("FINISHED") - case Typed.REF(str, _) => writeLog("Evaluating REF") *> evalRef(str) <* writeLog("FINISHED") + case Typed.BLOCK(let, inner, blockTpe) => evalBlock(let, inner, blockTpe) + case Typed.REF(str, _) => evalRef(str) case Typed.CONST_LONG(v) => liftValue(v) case Typed.CONST_BYTEVECTOR(v) => liftValue(v) case Typed.CONST_STRING(v) => liftValue(v) case Typed.TRUE => liftValue(true) case Typed.FALSE => liftValue(false) - case Typed.IF(cond, t1, t2, tpe) => writeLog("Evaluating IF") *> evalIF(cond, t1, t2, tpe) <* writeLog("FINISHED") - case Typed.GETTER(expr, field, _) => writeLog("Evaluating GETTER") *> evalGetter(expr, field) <* writeLog("FINISHED") - case Typed.FUNCTION_CALL(header, args, _) => writeLog("Evaluating FUNCTION_CALL") *> evalFunctionCall(header, args) <* writeLog("FINISHED") + case Typed.IF(cond, t1, t2, tpe) => evalIF(cond, t1, t2, tpe) + case Typed.GETTER(expr, field, _) => evalGetter(expr, field) + case Typed.FUNCTION_CALL(header, args, _) => evalFunctionCall(header, args) }).flatMap(v => { val ti = typeInfo[T] if (t.tpe.typeInfo <:< ti) liftValue(v.asInstanceOf[T]) From b70c0234903f39f0c2b2b615e3d7ed794e2d0ef6 Mon Sep 17 00:00:00 2001 From: alexandr Date: Thu, 10 May 2018 11:07:08 +0300 Subject: [PATCH 3/6] Old evaluator removed --- .../lang/v1/ScriptEstimatorBenchmark.scala | 10 -- .../wavesplatform/lang/EvaluatorV1Test.scala | 35 +---- .../wavesplatform/lang/IntegrationTest.scala | 3 +- .../wavesplatform/lang/ExprEvaluator.scala | 2 +- .../lang/v1/EvaluationContext.scala | 16 -- .../wavesplatform/lang/v1/EvaluatorV1.scala | 141 ------------------ .../com/wavesplatform/lang/v1/Terms.scala | 2 +- .../wavesplatform/lang/v1/ctx/Context.scala | 2 +- .../lang/v1/evaluation/CoevalRef.scala | 24 +-- .../lang/v1/evaluation/EvalM.scala | 32 ++-- ...{EvaluatorV1_1.scala => EvaluatorV1.scala} | 21 +-- .../smart/script/ScriptRunner.scala | 4 +- .../state/diffs/smart/predef/package.scala | 7 +- .../AddressFromRecipientScenarioTest.scala | 5 +- .../scenarios/HackatonScenartioTest.scala | 3 +- 15 files changed, 56 insertions(+), 251 deletions(-) delete mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/EvaluationContext.scala delete mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/EvaluatorV1.scala rename lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/{EvaluatorV1_1.scala => EvaluatorV1.scala} (90%) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEstimatorBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEstimatorBenchmark.scala index 495976aab72..e7f1ef6723d 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEstimatorBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEstimatorBenchmark.scala @@ -4,8 +4,6 @@ import java.util.concurrent.TimeUnit import com.wavesplatform.lang.v1.ScriptEstimatorBenchmark.St import com.wavesplatform.lang.v1.ctx.Context -import com.wavesplatform.lang.v1.ctx.impl.PureContext -import com.wavesplatform.lang.v1.evaluation.EvaluatorV1_1 import com.wavesplatform.utils import org.openjdk.jmh.annotations._ import org.openjdk.jmh.infra.Blackhole @@ -17,16 +15,8 @@ import org.openjdk.jmh.infra.Blackhole @Warmup(iterations = 10) @Measurement(iterations = 10) class ScriptEstimatorBenchmark { - @Benchmark def apply_test(st: St, bh: Blackhole): Unit = bh.consume(ScriptEstimator(st.functionCosts, st.expr)) - - @Benchmark - def v1_test(st: St, bh: Blackhole): Unit = bh.consume(EvaluatorV1[Long](PureContext.instance, st.expr)) - - @Benchmark - def v1_1_test(st: St, bh: Blackhole): Unit = bh.consume(EvaluatorV1_1[Long](PureContext.instance, st.expr)) - } object ScriptEstimatorBenchmark { diff --git a/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala b/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala index 01cddcd496f..cfdddfe11a9 100644 --- a/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala +++ b/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala @@ -4,6 +4,7 @@ import cats.kernel.Monoid import cats.syntax.semigroup._ import com.wavesplatform.lang.Common._ import com.wavesplatform.lang.TypeInfo._ +import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.FunctionHeader.FunctionHeaderType import com.wavesplatform.lang.v1.Terms.Typed._ import com.wavesplatform.lang.v1.Terms._ @@ -11,39 +12,28 @@ import com.wavesplatform.lang.v1.ctx.Context._ import com.wavesplatform.lang.v1.ctx._ import com.wavesplatform.lang.v1.ctx.impl.PureContext._ import com.wavesplatform.lang.v1.ctx.impl.{CryptoContext, PureContext} -import com.wavesplatform.lang.v1.evaluation.EvaluatorV1_1 +import com.wavesplatform.lang.v1.evaluation.EvaluatorV1 import com.wavesplatform.lang.v1.testing.ScriptGen -import com.wavesplatform.lang.v1.{EvaluatorV1, FunctionHeader} import org.scalatest.prop.PropertyChecks import org.scalatest.{Matchers, PropSpec} import scodec.bits.ByteVector -import scorex.crypto.signatures.{Curve25519, PublicKey, Signature} import scorex.crypto.hash.{Blake2b256, Keccak256, Sha256} +import scorex.crypto.signatures.{Curve25519, PublicKey, Signature} class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with ScriptGen with NoShrink { - private def ev[T: TypeInfo](context: Context = PureContext.instance, expr: EXPR): Either[(Context, ExecutionLog, ExecutionError), T] = + private def ev[T: TypeInfo](context: Context = PureContext.instance, expr: EXPR): Either[(Context, ExecutionError), T] = EvaluatorV1[T](context, expr) private def simpleDeclarationAndUsage(i: Int) = BLOCK(LET("x", CONST_LONG(i)), REF("x", LONG), LONG) - private def logTime[A](in: => A): A = { - val start = System.currentTimeMillis() - in - println(System.currentTimeMillis() - start) - in - } - property("successful on very deep expressions (stack overflow check)") { val term = (1 to 100000).foldLeft[EXPR](CONST_LONG(0))((acc, _) => FUNCTION_CALL(sumLong.header, List(acc, CONST_LONG(1)), LONG)) - val r1 = logTime(ev[Long](expr = term)) - val r2 = logTime(EvaluatorV1_1[Long](PureContext.instance, term)) - ev[Long](expr = term) shouldBe Right(100000) } - property("return log and context of failed evaluation") { - val Left((ctx, log, err)) = ev[Long]( + property("return error and context of failed evaluation") { + val Left((ctx, err)) = ev[Long]( expr = BLOCK( LET("x", CONST_LONG(3)), BLOCK( @@ -55,16 +45,9 @@ class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with Sc ) ) - val expectedLog = - "Evaluating BLOCK\n" ++ - "LET: LET(x,CONST_LONG(3)); TYPE: LONG\n" ++ - "Evaluating BLOCK\n" ++ - "LET: LET(x,FUNCTION_CALL(FunctionHeader(+,List(LONG, LONG)),List(CONST_LONG(3), CONST_LONG(0)),LONG)); TYPE: LONG" - val expectedError = "Value 'x' already defined in the scope" - log shouldBe expectedLog err shouldBe expectedError ctx.letDefs.contains("x") shouldBe true } @@ -347,9 +330,7 @@ class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with Sc r.isLeft shouldBe false } - private def sigVerifyTest(bodyBytes: Array[Byte], - publicKey: PublicKey, - signature: Signature): Either[(Context, ExecutionLog, ExecutionError), Boolean] = { + private def sigVerifyTest(bodyBytes: Array[Byte], publicKey: PublicKey, signature: Signature): Either[(Context, ExecutionError), Boolean] = { val txType = PredefType( "Transaction", List( @@ -399,7 +380,7 @@ class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with Sc for ((funcName, funcClass) <- hashFunctions) hashFuncTest(bodyBytes, funcName) shouldBe Right(ByteVector(funcClass.hash(bodyText))) } - private def hashFuncTest(bodyBytes: Array[Byte], funcName: String): Either[(Context, ExecutionLog, ExecutionError), ByteVector] = { + private def hashFuncTest(bodyBytes: Array[Byte], funcName: String): Either[(Context, ExecutionError), ByteVector] = { val context = Monoid.combineAll( Seq( PureContext.instance, diff --git a/lang/jvm/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala b/lang/jvm/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala index 87c1ad1300e..99fcc815998 100644 --- a/lang/jvm/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala +++ b/lang/jvm/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala @@ -2,9 +2,10 @@ package com.wavesplatform.lang import com.wavesplatform.lang.Common._ import com.wavesplatform.lang.TypeInfo._ -import com.wavesplatform.lang.v1.{EvaluatorV1, Parser, TypeChecker} import com.wavesplatform.lang.v1.ctx.impl.PureContext +import com.wavesplatform.lang.v1.evaluation.EvaluatorV1 import com.wavesplatform.lang.v1.testing.ScriptGen +import com.wavesplatform.lang.v1.{Parser, TypeChecker} import org.scalatest.prop.PropertyChecks import org.scalatest.{Matchers, PropSpec} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/ExprEvaluator.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/ExprEvaluator.scala index 99c98312bcd..ee685879844 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/ExprEvaluator.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/ExprEvaluator.scala @@ -1,5 +1,5 @@ package com.wavesplatform.lang trait ExprEvaluator extends Versioned { - def apply[T: TypeInfo](ctx: version.CtxT, expr: version.ExprT): Either[(version.CtxT, ExecutionLog, ExecutionError), T] + def apply[T: TypeInfo](ctx: version.CtxT, expr: version.ExprT): Either[(version.CtxT, ExecutionError), T] } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/EvaluationContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/EvaluationContext.scala deleted file mode 100644 index dfee423f18b..00000000000 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/EvaluationContext.scala +++ /dev/null @@ -1,16 +0,0 @@ -package com.wavesplatform.lang.v1 - -import com.wavesplatform.lang.v1.ctx.{Context, LazyVal, PredefFunction, PredefType} -import shapeless._ - -final case class EvaluationContext(context: Context, log: List[String] = List.empty) { - def logAppend(l: String): EvaluationContext = copy(log = l :: log) - def getLog: String = log.map(_.trim).reverse mkString ("", "\n", "") -} - -object EvaluationContext { - val context: Lens[EvaluationContext, Context] = lens[EvaluationContext] >> 'context - val types: Lens[EvaluationContext, Map[String, PredefType]] = lens[EvaluationContext] >> 'context >> 'typeDefs - val lets: Lens[EvaluationContext, Map[String, LazyVal]] = lens[EvaluationContext] >> 'context >> 'letDefs - val funcs: Lens[EvaluationContext, Map[FunctionHeader, PredefFunction]] = lens[EvaluationContext] >> 'context >> 'functions -} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/EvaluatorV1.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/EvaluatorV1.scala deleted file mode 100644 index 246fc04816f..00000000000 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/EvaluatorV1.scala +++ /dev/null @@ -1,141 +0,0 @@ -package com.wavesplatform.lang.v1 - -import cats.data.{EitherT, StateT} -import cats.implicits._ -import com.wavesplatform.lang.ScriptVersion.Versions.V1 -import com.wavesplatform.lang.TypeInfo._ -import com.wavesplatform.lang.v1.Terms.Typed.{EXPR, LET} -import com.wavesplatform.lang.v1.Terms._ -import com.wavesplatform.lang.v1.ctx._ -import com.wavesplatform.lang._ -import monix.eval.Coeval -import EvaluationContext._ - -object EvaluatorV1 extends ExprEvaluator { - - override type V = V1.type - override val version: V = V1 - - private type F0[A] = StateT[Coeval, EvaluationContext, A] - private type FF[A] = EitherT[F0, ExecutionError, A] - - private def getContext: FF[EvaluationContext] = - EitherT.apply[F0, ExecutionError, EvaluationContext](StateT.get[Coeval, EvaluationContext].map(_.asRight[ExecutionError])) - private def updateContext(f: EvaluationContext => EvaluationContext): FF[Unit] = - EitherT.apply[F0, ExecutionError, Unit](StateT.modify[Coeval, EvaluationContext](f).map(_.asRight[ExecutionError])) - - private def writeLog(l: String): FF[Unit] = updateContext(_.logAppend(l)) - - private def liftR[A](x: A): FF[A] = EitherT.apply[F0, ExecutionError, A](StateT(s => Coeval.evalOnce((s, Right(x))))) - private def liftL[A](err: ExecutionError): FF[A] = EitherT.apply[F0, ExecutionError, A](StateT(s => Coeval.evalOnce((s, Left(err))))) - - private def liftCE[A](ei: Coeval[ExecutionError Either A]): FF[A] = EitherT.apply[F0, ExecutionError, A](StateT(s => ei.map(v => (s, v)))) - - private def toTER[A](ctx: EvaluationContext, fa: FF[A]): TrampolinedExecResult[A] = { - fa.value - .run(ctx) - .map(t => EitherT.fromEither[Coeval](t._2)) - .value - } - - private def evalBlock(let: LET, inner: EXPR, tpe: TYPE): FF[Any] = { - import let.{name, value} - for { - _ <- writeLog(s"LET: $let; TYPE: $tpe") - ctx <- getContext - result <- { - if (lets.get(ctx).get(name).isDefined) liftL(s"Value '$name' already defined in the scope") - else if (funcs.get(ctx).keys.exists(_.name == name)) - liftL(s"Value '$name' can't be defined because function with such name is predefined") - else { - val blockEvaluation = evalExpr(value)(value.tpe.typeInfo) - val lazyBlock = LazyVal(value.tpe)(toTER(ctx, blockEvaluation)) - updateContext(lets.modify(_)(_.updated(name, lazyBlock))) *> evalExpr(inner)(tpe.typeInfo) - } - } - } yield result - } - - private def evalRef(key: String) = { - for { - _ <- writeLog(s"KEY: $key") - ctx <- getContext - result <- lets.get(ctx).get(key) match { - case Some(lzy) => liftCE[Any](lzy.value.value) - case None => liftL[Any](s"A definition of '$key' is not found") - } - } yield result - } - - private def evalIF(cond: EXPR, ifTrue: EXPR, ifFalse: EXPR, tpe: TYPE) = { - for { - ifc <- writeLog("Evaluating COND") *> evalExpr[Boolean](cond) - result <- ifc match { - case true => writeLog("Evaluating IF_TRUE") *> evalExpr(ifTrue)(tpe.typeInfo) - case false => writeLog("Evaluating IF_FALSE") *> evalExpr(ifFalse)(tpe.typeInfo) - } - } yield result - } - - private def evalGetter(expr: EXPR, field: String) = { - for { - obj <- evalExpr[Obj](expr) - result <- obj.fields.get(field) match { - case Some(lzy) => liftCE[Any](lzy.value.value) - case None => liftL[Any](s"field '$field' not found") - } - } yield result - } - - private def evalFunctionCall(header: FunctionHeader, args: List[EXPR]): FF[Any] = { - for { - _ <- writeLog(s"FUNCTION HEADER: $header") - ctx <- getContext - result <- funcs - .get(ctx) - .get(header) - .fold(liftL[Any](s"function '$header' not found")) { func => - args - .traverse[FF, Any](a => evalExpr(a)(a.tpe.typeInfo).map(_.asInstanceOf[Any])) - .map(func.eval) - .flatMap(r => liftCE[Any](r.value)) - } - } yield result - } - - private def evalExpr[T: TypeInfo](t: Typed.EXPR): FF[T] = { - (t match { - case Typed.BLOCK(let, inner, blockTpe) => - writeLog("Evaluating BLOCK") *> evalBlock(let, inner, blockTpe) <* writeLog("FINISHED") - case Typed.REF(str, _) => - writeLog("Evaluating REF") *> evalRef(str) <* writeLog("FINISHED") - case Typed.CONST_LONG(v) => liftR(v) - case Typed.CONST_BYTEVECTOR(v) => liftR(v) - case Typed.CONST_STRING(v) => liftR(v) - case Typed.TRUE => liftR(true) - case Typed.FALSE => liftR(false) - case Typed.IF(cond, t1, t2, tpe) => - writeLog("Evaluating IF") *> evalIF(cond, t1, t2, tpe) <* writeLog("FINISHED") - case Typed.GETTER(expr, field, _) => - writeLog(s"Evaluating GETTER") *> evalGetter(expr, field) <* writeLog("FINISHED") - case Typed.FUNCTION_CALL(header, args, _) => - writeLog(s"Evaluating FUNCTION_CALL") *> evalFunctionCall(header, args) <* writeLog("FINISHED") - }).flatMap(v => { - val ti = typeInfo[T] - if (t.tpe.typeInfo <:< ti) liftR(v.asInstanceOf[T]) - else liftL(s"Bad type: expected: ${ti} actual: ${t.tpe.typeInfo}") - }) - - } - - def apply[A: TypeInfo](c: Context, expr: Typed.EXPR): Either[(Context, ExecutionLog, ExecutionError), A] = { - def evaluation = evalExpr[A](expr).value.run(EvaluationContext(c)) - - evaluation - .map({ - case (_, Right(v)) => Right(v) - case (ctx, Left(err)) => Left((ctx.context, ctx.getLog, err)) - }) - .apply() - } -} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/Terms.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/Terms.scala index 0122751658e..2bdd5098191 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/Terms.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/Terms.scala @@ -13,7 +13,7 @@ object Terms { case class OPTIONTYPEPARAM(t: TYPEPLACEHOLDER) extends TYPEPLACEHOLDER sealed trait TYPE extends TYPEPLACEHOLDER { - type Underlying <: Any + type Underlying def typeInfo: TypeInfo[Underlying] } sealed abstract class AUTO_TAGGED_TYPE[T](implicit override val typeInfo: TypeInfo[T]) extends TYPE { diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/ctx/Context.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/ctx/Context.scala index fe5d47c0330..bf96f2c8497 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/ctx/Context.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/ctx/Context.scala @@ -1,7 +1,7 @@ package com.wavesplatform.lang.v1.ctx import cats._ -import com.wavesplatform.lang.v1.{EvaluationContext, FunctionHeader} +import com.wavesplatform.lang.v1.FunctionHeader import shapeless._ case class Context(typeDefs: Map[String, PredefType], letDefs: Map[String, LazyVal], functions: Map[FunctionHeader, PredefFunction]) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/CoevalRef.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/CoevalRef.scala index e1aa2112ff6..2dc767431c8 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/CoevalRef.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/CoevalRef.scala @@ -1,32 +1,20 @@ package com.wavesplatform.lang.v1.evaluation -import java.util.concurrent.atomic.AtomicReference - import monix.eval.Coeval +import monix.execution.atomic._ +import monix.execution.atomic.Atomic sealed trait CoevalRef[A] { def read: Coeval[A] def write(a: A): Coeval[Unit] - def update(f: A => A): Coeval[Unit] } object CoevalRef { - def of[A](a: A): CoevalRef[A] = { + def of[A, R <: Atomic[A]](a: A)(implicit ab: AtomicBuilder[A, R]): CoevalRef[A] = { new CoevalRef[A] { - - private val atom = new AtomicReference[A](a) - - override def read: Coeval[A] = Coeval.delay(atom.get()) - - override def write(a: A): Coeval[Unit] = Coeval.delay(atom.lazySet(a)) - - override def update(f: A => A): Coeval[Unit] = Coeval.delay { - for { - old <- read - upd = f(old) - _ <- write(upd) - } yield () - } + private val atom: Atomic[A] = Atomic(a) + override def read: Coeval[A] = Coeval.delay(atom.get) + override def write(a: A): Coeval[Unit] = Coeval.delay(atom.set(a)) } } } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala index 539d49e94b4..879f9b8e694 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvalM.scala @@ -1,18 +1,12 @@ package com.wavesplatform.lang.v1.evaluation -import cats.{Monad, StackSafeMonad, ~>} -import cats.data.{EitherT, Kleisli, WriterT} -import com.wavesplatform.lang.{ExecutionError, ExecutionLog, TrampolinedExecResult} +import cats.data.{EitherT, Kleisli} +import cats.implicits._ +import cats.{Monad, StackSafeMonad} import com.wavesplatform.lang.v1.ctx.Context -import com.wavesplatform.lang.v1.ctx.Context.Lenses._ +import com.wavesplatform.lang.{ExecutionError, TrampolinedExecResult} import monix.eval.Coeval -import cats.implicits._ -import com.wavesplatform.lang.v1.evaluation.H.EC -object H { - type Env = (CoevalRef[Context], StringBuffer) - type EC[A] = EitherT[Coeval, ExecutionError, A] -} final case class EvalM[A](inner: Kleisli[Coeval, CoevalRef[Context], Either[ExecutionError, A]]) { def ter(ctx: Context): TrampolinedExecResult[A] = { val atom = CoevalRef.of(ctx) @@ -20,7 +14,7 @@ final case class EvalM[A](inner: Kleisli[Coeval, CoevalRef[Context], Either[Exec EitherT(inner.run(atom)) } - def run(ctx: Context): Either[(Context, ExecutionLog, ExecutionError), A] = { + def run(ctx: Context): Either[(Context, ExecutionError), A] = { val atom = CoevalRef.of(ctx) val action = inner @@ -29,24 +23,22 @@ final case class EvalM[A](inner: Kleisli[Coeval, CoevalRef[Context], Either[Exec (for { result <- action lastCtx <- atom.read - } yield result.left.map(err => (lastCtx, "EMPTY", err))).value + } yield result.left.map(err => (lastCtx, err))).value } } object EvalM { - type Inner[A] = Kleisli[Coeval, CoevalRef[Context], A] - - private val innerMonad: Monad[Inner] = implicitly[Monad[Inner]] implicit val monadInstance: Monad[EvalM] = new StackSafeMonad[EvalM] { override def pure[A](x: A): EvalM[A] = EvalM(Kleisli.pure[Coeval, CoevalRef[Context], Either[ExecutionError, A]](x.asRight[ExecutionError])) - override def flatMap[A, B](fa: EvalM[A])(f: A => EvalM[B]): EvalM[B] = + override def flatMap[A, B](fa: EvalM[A])(f: A => EvalM[B]): EvalM[B] = { EvalM(fa.inner.flatMap({ - case Right(v) => f(v).inner + case Right(v) => f(v).inner case Left(err) => Kleisli.pure(err.asLeft[B]) })) + } } def getContext: EvalM[Context] = @@ -54,11 +46,13 @@ object EvalM { ref.read.map(_.asRight[ExecutionError]) })) - def updateContext(f: Context => Context): EvalM[Unit] = + def setContext(ctx: Context): EvalM[Unit] = EvalM(Kleisli[Coeval, CoevalRef[Context], Either[ExecutionError, Unit]](ref => { - ref.update(f).map(_.asRight[ExecutionError]) + ref.write(ctx).map(_.asRight[ExecutionError]) })) + def updateContext(f: Context => Context): EvalM[Unit] = getContext >>= (f andThen setContext) + def liftValue[A](a: A): EvalM[A] = monadInstance.pure(a) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1_1.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1.scala similarity index 90% rename from lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1_1.scala rename to lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1.scala index f353d669b35..cce660d01c4 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1_1.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluation/EvaluatorV1.scala @@ -1,18 +1,22 @@ package com.wavesplatform.lang.v1.evaluation -import com.wavesplatform.lang.v1.Terms.{TYPE, Typed} -import com.wavesplatform.lang.v1.Terms.Typed._ -import com.wavesplatform.lang.v1.ctx.Context.Lenses._ import cats.implicits._ -import com.wavesplatform.lang.{ExecutionError, ExecutionLog, TypeInfo} +import com.wavesplatform.lang.ScriptVersion.Versions.V1 import com.wavesplatform.lang.TypeInfo._ import com.wavesplatform.lang.v1.FunctionHeader +import com.wavesplatform.lang.v1.Terms.Typed._ +import com.wavesplatform.lang.v1.Terms.{TYPE, Typed} +import com.wavesplatform.lang.v1.ctx.Context.Lenses._ import com.wavesplatform.lang.v1.ctx.{Context, LazyVal, Obj} +import com.wavesplatform.lang.{ExecutionError, ExprEvaluator, TypeInfo} -object EvaluatorV1_1 { +object EvaluatorV1 extends ExprEvaluator { import EvalM._ + override type V = V1.type + override val version: V = V1 + private def evalBlock(let: LET, inner: EXPR, tpe: TYPE): EvalM[Any] = { import let.{name, value} for { @@ -91,13 +95,12 @@ object EvaluatorV1_1 { }).flatMap(v => { val ti = typeInfo[T] if (t.tpe.typeInfo <:< ti) liftValue(v.asInstanceOf[T]) - else liftError(s"Bad type: expected: ${ti} actual: ${t.tpe.typeInfo}") + else liftError(s"Bad type: expected: $ti actual: ${t.tpe.typeInfo}") }) } - def apply[A: TypeInfo](c: Context, expr: Typed.EXPR): Either[(Context, ExecutionLog, ExecutionError), A] = { - evalExpr[A](expr) - .run(c) + def apply[A: TypeInfo](c: Context, expr: Typed.EXPR): Either[(Context, ExecutionError), A] = { + evalExpr[A](expr).run(c) } } diff --git a/src/main/scala/scorex/transaction/smart/script/ScriptRunner.scala b/src/main/scala/scorex/transaction/smart/script/ScriptRunner.scala index e903ed291c9..3701a893243 100644 --- a/src/main/scala/scorex/transaction/smart/script/ScriptRunner.scala +++ b/src/main/scala/scorex/transaction/smart/script/ScriptRunner.scala @@ -1,7 +1,7 @@ package scorex.transaction.smart.script import cats.implicits._ -import com.wavesplatform.lang.v1.EvaluatorV1 +import com.wavesplatform.lang.v1.evaluation.EvaluatorV1 import com.wavesplatform.lang.{ExecutionError, TypeInfo} import com.wavesplatform.state._ import monix.eval.Coeval @@ -20,7 +20,7 @@ object ScriptRunner { Coeval.evalOnce(height), blockchain ) - EvaluatorV1[A](ctx, expr).left.map(_._3) + EvaluatorV1[A](ctx, expr).left.map(_._2) case _ => "Unsupported script version".asLeft[A] } diff --git a/src/test/scala/com/wavesplatform/state/diffs/smart/predef/package.scala b/src/test/scala/com/wavesplatform/state/diffs/smart/predef/package.scala index 5be36eb9c0d..c4119aa6401 100644 --- a/src/test/scala/com/wavesplatform/state/diffs/smart/predef/package.scala +++ b/src/test/scala/com/wavesplatform/state/diffs/smart/predef/package.scala @@ -1,17 +1,20 @@ package com.wavesplatform.state.diffs.smart import com.wavesplatform.lang.TypeInfo -import com.wavesplatform.lang.v1.{EvaluatorV1, Parser, TypeChecker} +import com.wavesplatform.lang.v1.evaluation.EvaluatorV1 +import com.wavesplatform.lang.v1.{Parser, TypeChecker} import com.wavesplatform.utils.dummyTypeCheckerContext import fastparse.core.Parsed.Success import monix.eval.Coeval import scorex.transaction.Transaction import scorex.transaction.smart.BlockchainContext + package object predef { val networkByte: Byte = 'u' + def runScript[T: TypeInfo](script: String, tx: Transaction = null): Either[String, T] = { val Success(expr, _) = Parser(script) val Right(typedExpr) = TypeChecker(dummyTypeCheckerContext, expr) - EvaluatorV1[T](BlockchainContext.build(networkByte, Coeval(tx), Coeval(???), null), typedExpr).left.map(_._3) + EvaluatorV1[T](BlockchainContext.build(networkByte, Coeval(tx), Coeval(???), null), typedExpr).left.map(_._2) } } diff --git a/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/AddressFromRecipientScenarioTest.scala b/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/AddressFromRecipientScenarioTest.scala index a643cc74e30..75430af76aa 100644 --- a/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/AddressFromRecipientScenarioTest.scala +++ b/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/AddressFromRecipientScenarioTest.scala @@ -1,7 +1,8 @@ package com.wavesplatform.state.diffs.smart.scenarios import com.wavesplatform.lang.v1.ctx.Obj -import com.wavesplatform.lang.v1.{EvaluatorV1, Parser, TypeChecker} +import com.wavesplatform.lang.v1.evaluation.EvaluatorV1 +import com.wavesplatform.lang.v1.{Parser, TypeChecker} import com.wavesplatform.state._ import com.wavesplatform.state.diffs.{ENOUGH_AMT, assertDiffAndState, produce} import com.wavesplatform.{NoShrink, TransactionGen} @@ -38,7 +39,7 @@ class AddressFromRecipientScenarioTest extends PropSpec with PropertyChecks with val Parsed.Success(expr, _) = Parser("addressFromRecipient(tx.recipient)") val Right(typedExpr) = TypeChecker(TypeChecker.TypeCheckerContext.fromContext(context), expr) - EvaluatorV1[Obj](context, typedExpr).left.map(_._3) + EvaluatorV1[Obj](context, typedExpr).left.map(_._2) } property("Script can resolve AddressOrAlias") { diff --git a/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/HackatonScenartioTest.scala b/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/HackatonScenartioTest.scala index 0c6a11e6d0d..26ae492c1ff 100644 --- a/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/HackatonScenartioTest.scala +++ b/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/HackatonScenartioTest.scala @@ -4,7 +4,8 @@ import java.nio.charset.StandardCharsets import com.wavesplatform.lang.TypeInfo import com.wavesplatform.lang.TypeInfo._ -import com.wavesplatform.lang.v1.{EvaluatorV1, Parser, TypeChecker} +import com.wavesplatform.lang.v1.evaluation.EvaluatorV1 +import com.wavesplatform.lang.v1.{Parser, TypeChecker} import com.wavesplatform.state._ import com.wavesplatform.state.diffs._ import com.wavesplatform.state.diffs.smart._ From 32f45a1e842fc81da1c66978384536b16af4936d Mon Sep 17 00:00:00 2001 From: alexandr Date: Wed, 16 May 2018 14:29:41 +0300 Subject: [PATCH 4/6] tailRecM implemented, Unused LoggedEvaluationContext removed --- .../it/sync/SmartContractsTestSuite.scala | 13 ++++++------- .../com/wavesplatform/lang/EvaluatorV1Test.scala | 7 ++++--- .../wavesplatform/lang/v1/evaluator/EvalM.scala | 15 +++++++++++++-- .../evaluator/ctx/LoggedEvaluationContext.scala | 16 ---------------- 4 files changed, 23 insertions(+), 28 deletions(-) delete mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/LoggedEvaluationContext.scala diff --git a/it/src/test/scala/com/wavesplatform/it/sync/SmartContractsTestSuite.scala b/it/src/test/scala/com/wavesplatform/it/sync/SmartContractsTestSuite.scala index 3b8adf2d5dd..540f1f31717 100644 --- a/it/src/test/scala/com/wavesplatform/it/sync/SmartContractsTestSuite.scala +++ b/it/src/test/scala/com/wavesplatform/it/sync/SmartContractsTestSuite.scala @@ -1,21 +1,20 @@ package com.wavesplatform.it.sync import com.wavesplatform.crypto +import com.wavesplatform.it.api.SyncHttpApi._ import com.wavesplatform.it.transactions.BaseTransactionSuite -import org.scalatest.CancelAfterFailure -import scorex.account.{AddressScheme, PrivateKeyAccount} import com.wavesplatform.it.util._ -import com.wavesplatform.it.api.SyncHttpApi._ import com.wavesplatform.lang.v1.compiler.CompilerV1 -import play.api.libs.json.JsNumber import com.wavesplatform.lang.v1.parser.Parser -import com.wavesplatform.utils.dummyTypeCheckerContext import com.wavesplatform.state._ +import com.wavesplatform.utils.dummyTypeCheckerContext +import org.scalatest.CancelAfterFailure +import play.api.libs.json.JsNumber +import scorex.account.PrivateKeyAccount import scorex.transaction.Proofs -import scorex.transaction.transfer._ -import scorex.transaction.lease.LeaseCancelTransactionV2 import scorex.transaction.smart.SetScriptTransaction import scorex.transaction.smart.script.v1.ScriptV1 +import scorex.transaction.transfer._ class SmartContractsTestSuite extends BaseTransactionSuite with CancelAfterFailure { private def pkFromAddress(address: String) = PrivateKeyAccount.fromSeed(sender.seed(address)).right.get diff --git a/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala b/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala index 2ddad62e4b8..af4805eea19 100644 --- a/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala +++ b/lang/jvm/src/test/scala/com/wavesplatform/lang/EvaluatorV1Test.scala @@ -21,8 +21,7 @@ import scorex.crypto.signatures.{Curve25519, PublicKey, Signature} class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with ScriptGen with NoShrink { - private def ev[T: TypeInfo](context: EvaluationContext = PureContext.instance, - expr: EXPR): Either[(EvaluationContext, ExecutionError), T] = + private def ev[T: TypeInfo](context: EvaluationContext = PureContext.instance, expr: EXPR): Either[(EvaluationContext, ExecutionError), T] = EvaluatorV1[T](context, expr) private def simpleDeclarationAndUsage(i: Int) = BLOCK(LET("x", CONST_LONG(i)), REF("x", LONG), LONG) @@ -330,7 +329,9 @@ class EvaluatorV1Test extends PropSpec with PropertyChecks with Matchers with Sc r.isLeft shouldBe false } - private def sigVerifyTest(bodyBytes: Array[Byte], publicKey: PublicKey, signature: Signature): Either[(EvaluationContext, ExecutionError), Boolean] = { + private def sigVerifyTest(bodyBytes: Array[Byte], + publicKey: PublicKey, + signature: Signature): Either[(EvaluationContext, ExecutionError), Boolean] = { val txType = PredefType( "Transaction", List( diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvalM.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvalM.scala index 1d4d79ecdca..79627136827 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvalM.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvalM.scala @@ -1,8 +1,8 @@ package com.wavesplatform.lang.v1.evaluator +import cats.Monad import cats.data.{EitherT, Kleisli} import cats.implicits._ -import cats.{Monad, StackSafeMonad} import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext import com.wavesplatform.lang.{ExecutionError, TrampolinedExecResult} import monix.eval.Coeval @@ -29,7 +29,10 @@ final case class EvalM[A](inner: Kleisli[Coeval, CoevalRef[EvaluationContext], E object EvalM { - implicit val monadInstance: Monad[EvalM] = new StackSafeMonad[EvalM] { + private type MM[A] = Kleisli[Coeval, CoevalRef[EvaluationContext], A] + private val M: Monad[MM] = implicitly + + implicit val monadInstance: Monad[EvalM] = new Monad[EvalM] { override def pure[A](x: A): EvalM[A] = EvalM(Kleisli.pure[Coeval, CoevalRef[EvaluationContext], Either[ExecutionError, A]](x.asRight[ExecutionError])) @@ -39,6 +42,14 @@ object EvalM { case Left(err) => Kleisli.pure(err.asLeft[B]) })) } + + override def tailRecM[A, B](a: A)(f: A => EvalM[Either[A, B]]): EvalM[B] = { + EvalM(M.tailRecM(a)(f andThen (_.inner.map { + case Left(err) => Right(Left(err)) + case Right(Left(lv)) => Left(lv) + case Right(Right(rv)) => Right(Right(rv)) + }))) + } } def getContext: EvalM[EvaluationContext] = diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/LoggedEvaluationContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/LoggedEvaluationContext.scala deleted file mode 100644 index cb6ad465f50..00000000000 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/LoggedEvaluationContext.scala +++ /dev/null @@ -1,16 +0,0 @@ -package com.wavesplatform.lang.v1.evaluator.ctx - -import com.wavesplatform.lang.v1.FunctionHeader -import shapeless._ - -final case class LoggedEvaluationContext(context: EvaluationContext, log: List[String] = List.empty) { - def logAppend(l: String): LoggedEvaluationContext = copy(log = l :: log) - def getLog: String = log.map(_.trim).reverse mkString ("", "\n", "") -} - -object LoggedEvaluationContext { - val context: Lens[LoggedEvaluationContext, EvaluationContext] = lens[LoggedEvaluationContext] >> 'context - val types: Lens[LoggedEvaluationContext, Map[String, PredefType]] = lens[LoggedEvaluationContext] >> 'context >> 'typeDefs - val lets: Lens[LoggedEvaluationContext, Map[String, LazyVal]] = lens[LoggedEvaluationContext] >> 'context >> 'letDefs - val funcs: Lens[LoggedEvaluationContext, Map[FunctionHeader, PredefFunction]] = lens[LoggedEvaluationContext] >> 'context >> 'functions -} From ab7086891fbb267cdb6fcf2492cfa128fd914e47 Mon Sep 17 00:00:00 2001 From: alexandr Date: Wed, 16 May 2018 14:49:57 +0300 Subject: [PATCH 5/6] TypeInfo for AnyObj added, compilation fixed --- .../it/sync/SmartContractsTestSuite.scala | 1 + .../scala/com/wavesplatform/lang/Common.scala | 2 +- .../wavesplatform/lang/IntegrationTest.scala | 3 ++- .../com/wavesplatform/lang/TypeInfo.scala | 10 ++++---- .../lang/v1/evaluator/EvaluatorV1.scala | 23 +++++++------------ 5 files changed, 17 insertions(+), 22 deletions(-) diff --git a/it/src/test/scala/com/wavesplatform/it/sync/SmartContractsTestSuite.scala b/it/src/test/scala/com/wavesplatform/it/sync/SmartContractsTestSuite.scala index e69de29bb2d..8b137891791 100644 --- a/it/src/test/scala/com/wavesplatform/it/sync/SmartContractsTestSuite.scala +++ b/it/src/test/scala/com/wavesplatform/it/sync/SmartContractsTestSuite.scala @@ -0,0 +1 @@ + diff --git a/lang/jvm/src/test/scala/com/wavesplatform/lang/Common.scala b/lang/jvm/src/test/scala/com/wavesplatform/lang/Common.scala index d35ac635946..f0b4461df7a 100644 --- a/lang/jvm/src/test/scala/com/wavesplatform/lang/Common.scala +++ b/lang/jvm/src/test/scala/com/wavesplatform/lang/Common.scala @@ -13,7 +13,7 @@ import scala.util.{Left, Right, Try} object Common { - def ev[T: TypeInfo](context: EvaluationContext = PureContext.instance, expr: EXPR): Either[(EvaluationContext, ExecutionLog, ExecutionError), T] = + def ev[T: TypeInfo](context: EvaluationContext = PureContext.instance, expr: EXPR): Either[(EvaluationContext, ExecutionError), T] = EvaluatorV1[T](context, expr) trait NoShrink { diff --git a/lang/jvm/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala b/lang/jvm/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala index 0a38a539fe5..8b577f97c30 100644 --- a/lang/jvm/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala +++ b/lang/jvm/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala @@ -4,12 +4,13 @@ import cats.kernel.Monoid import com.wavesplatform.lang.Common._ import com.wavesplatform.lang.TypeInfo._ import com.wavesplatform.lang.v1.compiler.{CompilerContext, CompilerV1} +import com.wavesplatform.lang.v1.evaluator.EvaluatorV1 import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext +import com.wavesplatform.lang.v1.evaluator.ctx.{CaseObj, EvaluationContext} import com.wavesplatform.lang.v1.parser.Parser import com.wavesplatform.lang.v1.testing.ScriptGen import org.scalatest.prop.PropertyChecks import org.scalatest.{Matchers, PropSpec} -import com.wavesplatform.lang.v1.evaluator.ctx.{CaseObj, EvaluationContext} class IntegrationTest extends PropSpec with PropertyChecks with ScriptGen with Matchers with NoShrink { diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/TypeInfo.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/TypeInfo.scala index 32904d974dc..27cb46a4ce2 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/TypeInfo.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/TypeInfo.scala @@ -85,14 +85,14 @@ object TypeInfo { implicit val stringTypeInfo: TypeInfo[String] = fromAnyVal(interfaces = Set(serializableTypeInfo)) - implicit val objTypeInfo: TypeInfo[Obj] = + implicit val anyObjTypeInfo: TypeInfo[AnyObj] = fromAny(interfaces = Set(productTypeInfo, serializableTypeInfo)) - implicit val caseObjTypeInfo: TypeInfo[CaseObj] = - fromAny(interfaces = Set(productTypeInfo, serializableTypeInfo)) + implicit val objTypeInfo: TypeInfo[Obj] = + fromAny(interfaces = Set(productTypeInfo, serializableTypeInfo, anyObjTypeInfo)) - implicit val anyObjTypeInfo: TypeInfo[AnyObj] = - fromAny(interfaces = Set(productTypeInfo, serializableTypeInfo)) + implicit val caseObjTypeInfo: TypeInfo[CaseObj] = + fromAny(interfaces = Set(productTypeInfo, serializableTypeInfo, anyObjTypeInfo)) implicit def optionTypeInfo[A](implicit tia: TypeInfo[A]): TypeInfo[Option[A]] = fromAny(Set(tia), Set(productTypeInfo, serializableTypeInfo)) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV1.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV1.scala index 56067fda91b..ee937f8dfc6 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV1.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV1.scala @@ -4,16 +4,10 @@ import cats.implicits._ import com.wavesplatform.lang.ScriptVersion.Versions.V1 import com.wavesplatform.lang.TypeInfo._ import com.wavesplatform.lang.v1.FunctionHeader -import com.wavesplatform.lang.v1.compiler.Terms._ -import com.wavesplatform.lang.v1.evaluator.ctx.{EvaluationContext, LazyVal, Obj} -import com.wavesplatform.lang.{ExecutionError, ExprEvaluator, TypeInfo} -import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext.Lenses._ -import com.wavesplatform.lang._ -import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms.{EXPR, LET, _} -import com.wavesplatform.lang.v1.evaluator.ctx.LoggedEvaluationContext.{funcs, lets} -import com.wavesplatform.lang.v1.evaluator.ctx._ -import monix.eval.Coeval +import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext.Lenses._ +import com.wavesplatform.lang.v1.evaluator.ctx.{EvaluationContext, LazyVal, Obj, _} +import com.wavesplatform.lang.{ExecutionError, ExprEvaluator, TypeInfo} object EvaluatorV1 extends ExprEvaluator { @@ -71,8 +65,8 @@ object EvaluatorV1 extends ExprEvaluator { } case CaseObj(_, fields) => fields.get(field) match { - case Some(eager) => liftR[Any](eager.value) - case None => liftL[Any](s"field '$field' not found") + case Some(eager) => liftValue[Any](eager.value) + case None => liftError[Any](s"field '$field' not found") } } @@ -107,10 +101,9 @@ object EvaluatorV1 extends ExprEvaluator { case GETTER(expr, field, _) => evalGetter(expr, field) case FUNCTION_CALL(header, args, _) => evalFunctionCall(header, args) }).flatMap(v => { - liftR(v.asInstanceOf[T]) - // val ti = typeInfo[T] - // if (t.tpe.typeInfo <:< ti) liftValue(v.asInstanceOf[T]) - // else liftError(s"Bad type: expected: $ti actual: ${t.tpe.typeInfo}") + val ti = typeInfo[T] + if (t.tpe.typeInfo <:< ti) liftValue(v.asInstanceOf[T]) + else liftError(s"Bad type: expected: $ti actual: ${t.tpe.typeInfo}") }) } From 39e9290bcf0bd8962ac5a6606666eebb5402ed27 Mon Sep 17 00:00:00 2001 From: Alexandr M Date: Tue, 22 May 2018 12:55:32 +0300 Subject: [PATCH 6/6] EvalOpts: Simple Benchmark --- .../lang/v1/ScriptEvaluatorBenchmark.scala | 27 +++++++++++++++++++ .../lang/v1/evaluator/EvalM.scala | 5 ++-- 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala new file mode 100644 index 00000000000..0165937fd51 --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala @@ -0,0 +1,27 @@ +package com.wavesplatform.lang.v1 + +import java.util.concurrent.TimeUnit + +import com.wavesplatform.lang.TypeInfo._ +import com.wavesplatform.lang.v1.ScriptEvaluatorBenchmark.St +import com.wavesplatform.lang.v1.evaluator.EvaluatorV1 +import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext +import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.infra.Blackhole + +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(4) +@Fork(1) +@Warmup(iterations = 10) +@Measurement(iterations = 10) +class ScriptEvaluatorBenchmark { + @Benchmark + def apply_test(st: St, bh: Blackhole): Unit = bh.consume(EvaluatorV1[Boolean](st.context, st.expr)) +} + +object ScriptEvaluatorBenchmark { + class St extends BigSum { + val context = PureContext.instance + } +} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvalM.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvalM.scala index 79627136827..218e80e93a2 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvalM.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvalM.scala @@ -34,7 +34,7 @@ object EvalM { implicit val monadInstance: Monad[EvalM] = new Monad[EvalM] { override def pure[A](x: A): EvalM[A] = - EvalM(Kleisli.pure[Coeval, CoevalRef[EvaluationContext], Either[ExecutionError, A]](x.asRight[ExecutionError])) + EvalM(Kleisli.liftF[Coeval, CoevalRef[EvaluationContext], Either[ExecutionError, A]](Coeval.evalOnce(Right(x)))) override def flatMap[A, B](fa: EvalM[A])(f: A => EvalM[B]): EvalM[B] = { EvalM(fa.inner.flatMap({ @@ -64,8 +64,7 @@ object EvalM { def updateContext(f: EvaluationContext => EvaluationContext): EvalM[Unit] = getContext >>= (f andThen setContext) - def liftValue[A](a: A): EvalM[A] = - monadInstance.pure(a) + def liftValue[A](a: A): EvalM[A] = monadInstance.pure(a) def liftError[A](err: ExecutionError): EvalM[A] = EvalM(Kleisli[Coeval, CoevalRef[EvaluationContext], Either[ExecutionError, A]](_ => Coeval.delay(err.asLeft[A])))