Skip to content

Commit

Permalink
Merge pull request #977 from ScorexFoundation/i966-template-compiler
Browse files Browse the repository at this point in the history
Correct environment for Template Compiler
  • Loading branch information
aslesarenko authored May 12, 2024
2 parents acecce0 + 28f7e15 commit 8af5260
Show file tree
Hide file tree
Showing 15 changed files with 258 additions and 50 deletions.
4 changes: 2 additions & 2 deletions data/shared/src/main/scala/sigma/ast/ErgoTree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ case class UnparsedErgoTree(bytes: mutable.WrappedArray[Byte], error: Validation
* ErgoTreeSerializer defines top-level serialization format of the scripts.
* The interpretation of the byte array depend on the first `header` byte, which uses VLQ encoding up to 30 bits.
* Currently we define meaning for only first byte, which may be extended in future versions.
* 7 6 5 4 3 2 1 0
* 7 6 5 4 3 2 1 0
* -------------------------
* | | | | | | | | |
* -------------------------
* Bit 7 == 1 if the header contains more than 1 byte (default == 0)
* Bit 6 - reserved for GZIP compression (should be 0)
* Bit 5 == 1 - reserved for context dependent costing (should be = 0)
* Bit 5 == 1 - reserved (should be = 0)
* Bit 4 == 1 if constant segregation is used for this ErgoTree (default = 0)
* (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/264)
* Bit 3 == 1 if size of the whole tree is serialized after the header byte (default = 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package sigmastate.lang
import fastparse._
import fastparse.NoWhitespace._
import SigmaParser._
import sigma.ast.SType
import sigma.ast.{Constant, SType}
import sigma.ast.syntax.SValue
import sigmastate.lang.parsers.Basic

Expand Down Expand Up @@ -187,9 +187,9 @@ object ContractParser {

def annotation[_: P] = P("@contract")

def paramDefault[_: P] = P(WL.? ~ `=` ~ WL.? ~ ExprLiteral).map(s => s.asWrappedType)
def paramDefault[_: P] = P(WL.? ~ `=` ~ WL.? ~ ExprLiteral)

def param[_: P] = P(WL.? ~ Id.! ~ ":" ~ Type ~ paramDefault.?).map(s => ContractParam(s._1, s._2, s._3))
def param[_: P] = P(WL.? ~ Id.! ~ ":" ~ Type ~ paramDefault.?).map(s => ContractParam(s._1, s._2, s._3.map(_.value)))

def params[_: P] = P("(" ~ param.rep(1, ",").? ~ ")")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sigmastate.lang
import org.scalatest.matchers.should.Matchers
import org.scalatest.propspec.AnyPropSpec
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import sigma.ast.SType.AnyOps
import sigma.ast._

class ContractParserSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers {
Expand Down Expand Up @@ -34,8 +35,8 @@ class ContractParserSpec extends AnyPropSpec with ScalaCheckPropertyChecks with

parsed.name shouldBe "contractName"
parsed.params should contain theSameElementsInOrderAs Seq(
ContractParam("p1", SInt, Some(IntConstant(5).asWrappedType)),
ContractParam("p2", SString, Some(StringConstant("default string").asWrappedType)),
ContractParam("p1", SInt, Some(5.asWrappedType)),
ContractParam("p2", SString, Some("default string".asWrappedType)),
ContractParam("param3", SLong, None)
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package sigmastate.lang

import org.scalatest.matchers.should.Matchers
import sigma.{Coll, _}
import sigma.ast.SCollection.SByteArray
import sigma.ast.syntax.{SValue, ValueOps}
import sigma.ast._
import sigma.crypto.CryptoConstants
import sigma.data.{CAnyValue, CSigmaDslBuilder, ProveDHTuple, ProveDlog, SigmaBoolean}
import sigma.util.Extensions.BigIntegerOps
import sigma._
import sigmastate.helpers.NegativeTesting
import sigmastate.interpreter.Interpreter.ScriptEnv
import sigma.ast.{Ident, MethodCallLike}

import java.math.BigInteger

Expand Down
14 changes: 14 additions & 0 deletions sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,18 @@ trait GraphBuilding extends SigmaLibrary { IR: IRContext =>
def error(msg: String) = throw new GraphBuildingException(msg, None)
def error(msg: String, srcCtx: Option[SourceContext]) = throw new GraphBuildingException(msg, srcCtx)

/** Graph node to represent a placeholder of a constant in ErgoTree.
* @param id Zero based index in ErgoTree.constants array.
* @param resultType type descriptor of the constant value.
*/
case class ConstantPlaceholder[T](id: Int, resultType: Elem[T]) extends Def[T]

/** Smart constructor method for [[ConstantPlaceholder]], should be used instead of the
* class constructor.
*/
@inline def constantPlaceholder[T](id: Int, eT: Elem[T]): Ref[T] = ConstantPlaceholder(id, eT)


/** Translates the given typed expression to IR graph representing a function from
* Context to some type T.
* @param env contains values for each named constant used
Expand Down Expand Up @@ -465,6 +477,8 @@ trait GraphBuilding extends SigmaLibrary { IR: IRContext =>
val resV = toRep(v)(e)
resV
}
case sigma.ast.ConstantPlaceholder(id, tpe) =>
constantPlaceholder(id, stypeToElem(tpe))
case sigma.ast.Context => ctx
case Global => sigmaDslBuilder
case Height => ctx.HEIGHT
Expand Down
4 changes: 4 additions & 0 deletions sc/shared/src/main/scala/sigmastate/eval/TreeBuilding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ trait TreeBuilding extends SigmaLibrary { IR: IRContext =>
case None =>
mkConstant[tpe.type](x.asInstanceOf[tpe.WrappedType], tpe)
}
case Def(IR.ConstantPlaceholder(id, elem)) =>
val tpe = elemToSType(elem)
mkConstantPlaceholder[tpe.type](id, tpe)

case Def(wc: LiftedConst[a,_]) =>
val tpe = elemToSType(s.elem)
mkConstant[tpe.type](wc.constValue.asInstanceOf[tpe.WrappedType], tpe)
Expand Down
10 changes: 8 additions & 2 deletions sc/shared/src/main/scala/sigmastate/lang/SigmaCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ class SigmaCompiler private(settings: CompilerSettings) {
val predefinedFuncRegistry = new PredefinedFuncRegistry(builder)
val binder = new SigmaBinder(env, builder, networkPrefix, predefinedFuncRegistry)
val bound = binder.bind(parsed)
val typer = new SigmaTyper(builder, predefinedFuncRegistry, settings.lowerMethodCalls)
val typeEnv = env.collect { case (k, v: SType) => k -> v }
val typer = new SigmaTyper(builder, predefinedFuncRegistry, typeEnv, settings.lowerMethodCalls)
val typed = typer.typecheck(bound)
typed
}
Expand All @@ -91,7 +92,12 @@ class SigmaCompiler private(settings: CompilerSettings) {

/** Compiles the given typed expression. */
def compileTyped(env: ScriptEnv, typedExpr: SValue)(implicit IR: IRContext): CompilerResult[IR.type] = {
val compiledGraph = IR.buildGraph(env, typedExpr)
val placeholdersEnv = env
.collect { case (name, t: SType) => name -> t }
.zipWithIndex
.map { case ((name, t), index) => name -> ConstantPlaceholder(index, t) }
.toMap
val compiledGraph = IR.buildGraph(env ++ placeholdersEnv, typedExpr)
val compiledTree = IR.buildTree(compiledGraph)
CompilerResult(env, "<no source code>", compiledGraph, compiledTree)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package sigmastate.lang

import fastparse.Parsed
import org.ergoplatform.sdk.ContractTemplate
import sigmastate.eval.CompiletimeIRContext
import org.ergoplatform.sdk.Parameter
import org.ergoplatform.sdk.{ContractTemplate, Parameter}
import sigma.ast.SourceContext
import sigma.ast.syntax.SValue
import sigmastate.interpreter.Interpreter.ScriptEnv
import sigmastate.eval.CompiletimeIRContext
import sigmastate.lang.parsers.ParserException

/** Compiler which compiles Ergo contract templates into a [[ContractTemplate]]. */
Expand All @@ -19,13 +17,14 @@ class SigmaTemplateCompiler(networkPrefix: Byte) {
* @param source The ErgoScript contract source code.
* @return The contract template.
*/
def compile(env: ScriptEnv, source: String): ContractTemplate = {
def compile(source: String): ContractTemplate = {
ContractParser.parse(source) match {
case Parsed.Success(template, _) => {
case Parsed.Success(parsedTemplate, _) =>
implicit val ir = new CompiletimeIRContext
val result = sigmaCompiler.compileParsed(env, template.body)
assemble(template, result.buildTree)
}
val parEnv = parsedTemplate.signature.params.map { p => p.name -> p.tpe }.toMap
val result = sigmaCompiler.compileParsed(parEnv, parsedTemplate.body)
assemble(parsedTemplate, result.buildTree)

case f: Parsed.Failure =>
throw new ParserException(s"Contract template syntax error: $f", Some(SourceContext.fromParserFailure(f)))
}
Expand Down
15 changes: 10 additions & 5 deletions sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package sigmastate.lang

import org.ergoplatform._
import sigma.ast.SCollection.{SBooleanArray, SByteArray}
import sigma.ast._
import sigma.ast.syntax.SValue
Expand All @@ -14,11 +13,15 @@ import sigma.serialization.OpCodes

import scala.collection.mutable.ArrayBuffer

/**
* Type inference and analysis for Sigma expressions.
/** Type inference and analysis for Sigma expressions.
* @param builder SigmaBuilder instance to create new nodes
* @param predefFuncRegistry predefined functions registry used to resolve names
* @param typeEnv environment with types of variables/names
* @param lowerMethodCalls if true, then MethodCall nodes are lowered to the corresponding ErgoTree nodes
*/
class SigmaTyper(val builder: SigmaBuilder,
predefFuncRegistry: PredefinedFuncRegistry,
typeEnv: Map[String, SType],
lowerMethodCalls: Boolean) {
import SigmaTyper._
import builder._
Expand All @@ -28,8 +31,10 @@ class SigmaTyper(val builder: SigmaBuilder,

import SType.tT

private val predefinedEnv: Map[String, SType] =
predefFuncRegistry.funcs.map { case (k, f) => k -> f.declaration.tpe }.toMap
private val predefinedEnv: Map[String, SType] = {
val predefFuncs = predefFuncRegistry.funcs.map { case (k, f) => k -> f.declaration.tpe }.toMap
predefFuncs ++ typeEnv
}

private def processGlobalMethod(srcCtx: Nullable[SourceContext],
method: SMethod,
Expand Down
11 changes: 10 additions & 1 deletion sc/shared/src/test/scala/sigmastate/CompilerTestsBase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import sigma.ast.syntax.{SValue, SigmaPropValue}
import sigma.serialization.ValueSerializer
import sigmastate.eval.IRContext
import sigma.ast.syntax.ValueOps
import sigmastate.helpers.{NegativeTesting, SigmaPPrint}

trait CompilerTestsBase extends TestsBase {
trait CompilerTestsBase extends TestsBase with NegativeTesting {
protected val _lowerMethodCalls = new DynamicVariable[Boolean](true)

/** Returns true if MethodCall nodes should be lowered by TypeChecker to the
Expand Down Expand Up @@ -63,4 +64,12 @@ trait CompilerTestsBase extends TestsBase {
val tree = mkTestErgoTree(prop)
(tree, prop)
}

/** Checks expectation pretty printing the actual value if there is a difference. */
def checkEquals[T](actual: T, expected: T): Unit = {
if (expected != actual) {
SigmaPPrint.pprintln(actual, width = 100)
}
actual shouldBe expected
}
}
Loading

0 comments on commit 8af5260

Please sign in to comment.