diff --git a/build.sbt b/build.sbt index a83befd..36d4953 100644 --- a/build.sbt +++ b/build.sbt @@ -30,11 +30,17 @@ lazy val core = project.settings( "org.typelevel" %% "cats-core" % "1.2.0", "org.typelevel" %% "cats-free" % "1.2.0", "com.chuusai" %% "shapeless" % "2.3.3", - "org.scalatest" %% "scalatest" % "3.0.5" % Test - ) + "org.scalatest" %% "scalatest" % "3.0.5" % Test, + scalaVersion("org.scala-lang" % "scala-reflect" % _).value + ), + addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full) ) lazy val root = project .in(file(".")) .dependsOn(core) .aggregate(core) + .settings( + libraryDependencies ++= Seq("com.chuusai" %% "shapeless" % "2.3.3"), + addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full) + ) diff --git a/core/src/main/scala/fluence/hackethberlin/ToVyper.scala b/core/src/main/scala/fluence/hackethberlin/ToVyper.scala new file mode 100644 index 0000000..8e0ea85 --- /dev/null +++ b/core/src/main/scala/fluence/hackethberlin/ToVyper.scala @@ -0,0 +1,132 @@ +package fluence.hackethberlin + +import fluence.hackethberlin.types.{PrimitiveType, _} +import shapeless.labelled.FieldType +import shapeless.syntax +import shapeless.syntax.SingletonOps +import syntax.singleton._ + +import scala.annotation.{compileTimeOnly, StaticAnnotation} +import scala.language.experimental.macros +import scala.reflect.macros.blackbox.Context +import scala.reflect.runtime.universe.{Tree, ValDef} + +@compileTimeOnly("ToVyper is compileTimeOnly") +class ToVyper extends StaticAnnotation { + + def macroTransform(annottees: Any*) = macro ToVyper.impl +} + +object ToVyper { + + def mapParam(p: ValDef): (String, PrimitiveType) = { + p.name.toString -> mapType(p.tpt) + } + + def mapType(t: Tree) = { + t.toString match { + case "String" => fluence.hackethberlin.types.address + case "Int" => fluence.hackethberlin.types.int128 + } + } + + def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + import c.universe.{Quasiquote, Tree => CTree} + + annottees.headOption.map(_.tree) match { + case Some(q"$mods class $tpname $ctorMods(..$paramss) { ..$stats }") => + println(s"QUASI MATCHED \nmods $mods \ntpname $tpname \nctorMods $ctorMods \nparamss $paramss \nstats$stats") + + implicit val liftPrimitive: c.universe.Liftable[PrimitiveType] = c.universe.Liftable[PrimitiveType] { + case `address` => q"_root_.fluence.hackethberlin.types.address" + case `bool` => q"_root_.fluence.hackethberlin.types.bool" + case `int128` => q"_root_.fluence.hackethberlin.types.int128" + case `uint256` => q"_root_.fluence.hackethberlin.types.uint256" + case `decimal` => q"_root_.fluence.hackethberlin.types.decimal" + case `string` => q"_root_.fluence.hackethberlin.types.string" + } + + // fluence.hackethberlin.types.PrimitiveType with shapeless.labelled.KeyTag[l.type,fluence.hackethberlin.types.PrimitiveType] + + implicit val lift: c.universe.Liftable[(Symbol, PrimitiveType)] = c.universe.Liftable[(Symbol, PrimitiveType)] { + case (l, r) => + type wtf = fluence.hackethberlin.types.PrimitiveType with shapeless.labelled.KeyTag[ + l.type, + fluence.hackethberlin.types.PrimitiveType + ] + implicit val liftWtf: c.universe.Liftable[wtf] = c.universe.Liftable[wtf] { + case _ => q"shapeless.labelled.field.apply(_root_.fluence.hackethberlin.types.address)" + } + +// q"$l -> $r" +// q"('abc ->> $r)" + q"(${shapeless.syntax.singleton.mkSingletonOps(l).->>(r)})" + } + + val params = paramss + .map(p => mapParam(p.asInstanceOf[ValDef])) + .foldRight[CTree](q"shapeless.HNil")( + (elem, acc) => q"$elem :: $acc" + ) + c.Expr[Any](q""" + class $tpname(..$paramss) { + def toAST = { + fluence.hackethberlin.FuncDef.apply( + "__init__", $params + )(fluence.hackethberlin.types.EmptyBody.get($params)) + } + } + """) + + case _ => c.abort(c.enclosingPosition, "Invalid annottee") + } + } +} + +// .${params.map(mapParam)} :: +// @inline def apply(defn: Any): Any = meta { +// defn match { +// case cls @ Defn.Class(_, name, _, ctor, template) => +// val params = ctor.paramss.head +// val tree: Tree = +// q""" +// FuncDef("__init__", HNil, uint256) +// """ +// tree +// +// case _ => throw new Exception() +// } +// } +// + +// +// def mapParam(p: Term.Param) = { +// s"${p.name}" -> s"${mapType(p.decltpe.get)}" +// } + +/* + +// Scala + +class MyContract(owner: String) { + val _owner: String = owner + + def isOwner(addr: String): Boolean = { + _owner == owner + } +} + + +// Vyper + +_owner: public(string) + +@public +__init__(owner: string): + self._owner = owner + +@public +def isOwner(addr: string): + return self._owner == addr + + */ diff --git a/core/src/main/scala/fluence/hackethberlin/types/PrimitiveType.scala b/core/src/main/scala/fluence/hackethberlin/types/PrimitiveType.scala index 124ae40..da5d9c1 100644 --- a/core/src/main/scala/fluence/hackethberlin/types/PrimitiveType.scala +++ b/core/src/main/scala/fluence/hackethberlin/types/PrimitiveType.scala @@ -16,5 +16,6 @@ object PrimitiveType { case object int128 extends PrimitiveType("int128") case object uint256 extends PrimitiveType("uint256") case object decimal extends PrimitiveType("decimal") + case object string extends PrimitiveType("string") } } diff --git a/core/src/main/scala/fluence/hackethberlin/types/Void.scala b/core/src/main/scala/fluence/hackethberlin/types/Void.scala index 3b616e6..dc7ec51 100644 --- a/core/src/main/scala/fluence/hackethberlin/types/Void.scala +++ b/core/src/main/scala/fluence/hackethberlin/types/Void.scala @@ -1,4 +1,7 @@ package fluence.hackethberlin.types +import cats.free.Free +import fluence.hackethberlin.Expr +import shapeless.HList sealed trait Void extends Type @@ -6,3 +9,10 @@ sealed trait Void extends Type case object Void extends Void { override def toVyper: String = "" } + +case object EmptyBody { + + def get[Args <: HList: DataVyper](args: Args): ProductType[Args] ⇒ Free[Expr, Unit] = { _ => + Free.pure(Void) + } +} diff --git a/src/main/scala/fluence/MakeVyperApp.scala b/src/main/scala/fluence/MakeVyperApp.scala index 392451d..c2a6275 100644 --- a/src/main/scala/fluence/MakeVyperApp.scala +++ b/src/main/scala/fluence/MakeVyperApp.scala @@ -65,4 +65,5 @@ object MakeVyperApp extends App { func(recordStruct.ref('record_address) :: HNil).toVyper ) + println(s"MMMMMACRO\n\n ${new MyContract("abc", 123).toAST.toVyper}") } diff --git a/src/main/scala/fluence/MyContract.scala b/src/main/scala/fluence/MyContract.scala new file mode 100644 index 0000000..a26aa6c --- /dev/null +++ b/src/main/scala/fluence/MyContract.scala @@ -0,0 +1,5 @@ +package fluence +import fluence.hackethberlin.ToVyper + +@ToVyper +class MyContract(owner: String, friend: Int) {}