All Downloads are FREE. Search and download functionalities are using the official Maven repository.

formidable.Macros.scala Maven / Gradle / Ivy

The newest version!
package formidable

import rx._

import scala.reflect.macros._
import scala.language.experimental.macros

object Macros {

  def getCompanion(c: blackbox.Context)(tpe: c.Type) = {
    import c.universe._
    val symTab = c.universe.asInstanceOf[reflect.internal.SymbolTable]
    val pre = tpe.asInstanceOf[symTab.Type].prefix.asInstanceOf[Type]
    c.universe.internal.gen.mkAttributedRef(pre, tpe.typeSymbol.companion)
  }

  def generate[Layout: c.WeakTypeTag, Target: c.WeakTypeTag](c: blackbox.Context): c.Expr[Layout with FormidableRx[Target]] = {
    import c.universe._
    val targetTpe = weakTypeTag[Target].tpe
    val layoutTpe = weakTypeTag[Layout].tpe

    val fields = targetTpe.decls.collectFirst {
      case m: MethodSymbol if m.isPrimaryConstructor => m
    }.get.paramLists.head

    val layoutAccessors = layoutTpe.decls.map(_.asTerm).filter(_.isAccessor).toList
    val layoutNames = layoutAccessors.map(_.name.toString).toSet
    val targetNames = fields.map(_.name.toString).toSet
    val missing = targetNames.diff(layoutNames)
    if(missing.nonEmpty) {
      c.abort(c.enclosingPosition,s"The layout is not fully defined: Missing fields are:\n--${missing.mkString("\n--")}")
    }

    //Get subset of layout accessors that are rx.core.Var types
    val VAR_SYMBOL = typeOf[rx.core.Var[_]].typeSymbol
    val rxVarAccessors = layoutAccessors.filter { a =>
      a.typeSignature match {
        case NullaryMethodType(TypeRef(_,VAR_SYMBOL, _ :: Nil)) => true
        case _ => false
      }
    }

    val companion = getCompanion(c)(targetTpe)

    val magic: List[c.Tree] = fields.zipWithIndex.map { case (field,idx) =>
      val term = TermName(s"a$idx")
      val accessor = layoutAccessors.find(_.name == field.name).get
      q"implicitly[BindRx[${accessor.info.dealias},${field.info.dealias}]].bind(this.$accessor,$term)"
    }

    val unmagic: List[c.Tree] = fields.zipWithIndex.map { case (field,i) =>
      val accessor = layoutAccessors.find(_.name == field.name).get
      fq"${TermName(s"a$i")} <- implicitly[BindRx[${accessor.info.dealias},${field.info.dealias}]].unbind(this.$accessor)()"
    }

    val resetMagic: List[c.Tree] = fields.map { case field =>
      val accessor = layoutAccessors.find(_.name == field.name).get
      q"implicitly[BindRx[${accessor.info.dealias},${field.info.dealias}]].reset(this.$accessor)"
    }

    def bindN(n: Int) = {
      if(n > 0 && n < 23) {
        val vars = (0 until n).map(i => pq"${TermName(s"a$i")}")
        q"""$companion.unapply(inp).map { case (..$vars) => $magic }"""
      }
      else {
        c.abort(c.enclosingPosition,"Unsupported Case Class Dimension")
      }
    }

    //Hack to make Var types resetable
    //Basic idea: Generate vals that store the default Var value when constructed
    //And on reset, use those defaults vals to reset each Var
    val varDefaultsMagic = rxVarAccessors.map { a =>
      val default = a.name.decodedName.toString + "Default"
      q"val ${TermName(default)} = this.$a.now"
    }

    val varResetMagic = rxVarAccessors.map { a =>
      val default = a.name.decodedName.toString + "Default"
      q"this.$a.update(${TermName(default)})"
    }

    c.Expr[Layout with FormidableRx[Target]](q"""
      new $layoutTpe with FormidableRx[$targetTpe] {

        private var isUpdating: Boolean = false

        ..$varDefaultsMagic

        val current: Rx[scala.util.Try[$targetTpe]] = Rx {
          if(isUpdating) {
            scala.util.Failure(formidable.FormidableProcessingFailure)
           }
          else {
            for(..$unmagic) yield {
              $companion.apply(..${fields.indices.map(i=>TermName("a"+i))})
            }
          }
        }

        private def startUpdate(): Unit = isUpdating = true

        private def stopUpdate(): Unit = {
          isUpdating = false
          current.recalc()
        }

        override def set(inp: $targetTpe): Unit = {
          startUpdate()
          ${bindN(fields.size)}
          stopUpdate()
        }

        def reset(): Unit = {
          startUpdate()
          ..$varResetMagic
          ..$resetMagic
          stopUpdate()
        }
      }
    """)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy