Created
April 12, 2017 21:09
-
-
Save DylanLukes/85a084ee815eb1973146101e55b7b10a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.emg.slidedeck.js.macros | |
import scala.collection.immutable.Seq | |
import scala.meta._ | |
import scala.meta.dialects.Paradise211 | |
class ScalaJSDefinedApply extends scala.annotation.StaticAnnotation { | |
inline def apply(defn: Any): Any = meta { | |
def extract(trt: Defn.Trait): Seq[(Term.Name, Option[String], Type, Term)] = { | |
trt.collect { | |
case dv @ Defn.Val(mods, Seq(Pat.Var.Term(name)), Some(tpe), rhs @ q"js.undefined") => | |
val jsName = mods.collectFirst { | |
case mod"@JSName(${lit: Lit})" => | |
lit.value match { | |
case s: String => s | |
case _ => abort(s"@ScalaJSDefinedApply: @JSName must be called with a string literal.") | |
} | |
// todo: add case of js.Symbol | |
} | |
(name, jsName, tpe, rhs) | |
case dv @ Defn.Val(_, Seq(_, _, _*), None, _) => | |
abort(s"@ScalaJSDefinedApply: $dv must only declare one left-hand side.") | |
case dv @ Defn.Val(_, _, None, _) => | |
abort(s"@ScalaJSDefinedApply: $dv must have an explicit type declaration.") | |
case dv @ Defn.Val(_, _, _, _) => | |
abort(s"@ScalaJSDefinedApply: $dv must be initialized to js.undefined.") | |
} | |
} | |
def generate(tname: Type.Name, schema: Seq[(Term.Name, Option[String], Type, Term)]): Defn.Def = { | |
val (params, args) = schema.map { | |
case (name, jsName, tpe, default) => | |
val namestr = Lit.String(jsName.getOrElse(name.value)) | |
param"$name: $tpe = $default" -> arg"($namestr, $name.asInstanceOf[js.Any])" | |
}.unzip | |
q"def apply(..$params): $tname = js.Dynamic.literal(..$args).asInstanceOf[$tname]" | |
} | |
defn match { | |
// A companion Object already exists in scope. Modify it. | |
case Term.Block(Seq( | |
dt @ Defn.Trait(_, tname, _, _, _), | |
companion: Defn.Object | |
)) => | |
val fields = extract(dt) | |
val applyMethod = generate(tname, fields) | |
Term.Block(Seq(dt, companion.copy( | |
templ = companion.templ.copy( | |
stats = companion.templ.stats.map { applyMethod +: _ } | |
)) | |
)) | |
// A companion Object does not yet exist. | |
case dt @ Defn.Trait(_, tname, _, _, _) => | |
val schema = extract(dt) | |
val apply = generate(tname, schema) | |
val companion = q"object ${Term.Name(tname.value)} { $apply }" | |
Term.Block(Seq(dt, companion)) | |
case _ => | |
abort("@ScalaJSDefinedApply must annotate a trait.") | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment