Skip to content

Instantly share code, notes, and snippets.

@clhodapp
Last active December 15, 2015 17:59
Show Gist options
  • Save clhodapp/5300785 to your computer and use it in GitHub Desktop.
Save clhodapp/5300785 to your computer and use it in GitHub Desktop.
package tuple
import language.experimental.macros
import language.dynamics
import scala.reflect.macros.Context
sealed trait ~[+A, +B]
private[tuple] object TupleMacros {
def apply(c: Context)(exprs: c.Expr[Any]*): c.Expr[Tuple[_]] = {
import c.universe._
val termList = exprs.map(_.tree).toList
val typeList = termList.map(_.tpe match {
case t if t <:< typeOf[AnyRef] => t
case t => t.erasure
})
val tildedList = {
val tildeType = typeOf[~[_,_]].typeSymbol
val tildeIdent = Ident(tildeType)
val trees = typeList.map(t => TypeTree(t))
def tildeTogether(t1: Tree, t2: Tree): Tree = AppliedTypeTree(tildeIdent, List(t1, t2))
trees.tail.foldLeft[Tree](trees.head)((t1: Tree, t2: Tree) => tildeTogether(t1, t2))
}
def extractType[T: TypeTag](l: List[Tree]) = l.partition(_.tpe <:< typeOf[T])
val arrayIdent = Ident(weakTypeOf[Array[_]].typeSymbol)
val arrayCompanionIdent = Ident(typeOf[Array[_]].typeSymbol.companionSymbol)
def typeIdent[T: TypeTag] = Ident(typeOf[T].typeSymbol)
def arrayType[T: TypeTag] = AppliedTypeTree(arrayIdent, List(typeIdent[T]))
def cast(casted: Tree, targetType: Tree) = TypeApply(Select(casted, newTermName("asInstanceOf")), List(targetType))
def applyToArray(args: List[Tree]) = Apply(Select(arrayCompanionIdent, newTermName("apply")), args)
def nullLiteral = Literal(Constant(null))
def arrayOrNull[T: TypeTag](args: List[Tree]) =
if (args.isEmpty) cast(nullLiteral, arrayType[T])
else applyToArray(args)
def buildArgument[T: TypeTag](input: List[Tree]): (Tree, List[Tree]) = {
val (matching, nonMatching) = extractType[T](input)
(arrayOrNull[T](matching), nonMatching)
}
val (unitArrayTree, r1) = buildArgument[Unit](termList)
val (booleanArrayTree, r2) = buildArgument[Boolean](r1)
val (byteArrayTree, r3) = buildArgument[Byte](r2)
val (charArrayTree, r4) = buildArgument[Char](r3)
val (shortArrayTree, r5) = buildArgument[Short](r4)
val (intArrayTree, r6) = buildArgument[Int](r5)
val (longArrayTree, r7) = buildArgument[Long](r6)
val (floatArrayTree, r8) = buildArgument[Float](r7)
val (doubleArrayTree, r9) = buildArgument[Double](r8)
val refArrayTree = arrayOrNull[AnyRef](r9)
val tupleIdent = Ident(weakTypeOf[Tuple[_]].typeSymbol)
val objectCreationTree = Apply(
Select(New(AppliedTypeTree(tupleIdent, List(tildedList))), nme.CONSTRUCTOR),
List(
unitArrayTree,
booleanArrayTree,
byteArrayTree,
charArrayTree,
shortArrayTree,
intArrayTree,
longArrayTree,
floatArrayTree,
doubleArrayTree,
refArrayTree
)
)
c.Expr[Tuple[_]](objectCreationTree)
}
def get[T: c.WeakTypeTag](c: Context { type PrefixType <: Tuple[_] })(nameExpr: c.Expr[String]) = {
import c.universe._
val Literal(Constant(name: String)) = nameExpr.tree
val tildeType = weakTypeOf[~[_, _]].typeConstructor
def isTildeType(tpe: Type) = tpe.erasure =:= tildeType.erasure
def listify(tpe: Type, current: List[Type]): List[Type] = tpe match {
case TypeRef(_, _, List(first, second)) if isTildeType(tpe) => listify(first, second :: current)
case end => end :: current
}
val typeList = listify(weakTypeOf[T], Nil)
if (name.head != '_') throw new IllegalArgumentException()
val index = name.tail.toInt - 1
if (index >= typeList.size) throw new IllegalArgumentException()
if (index < 0) throw new IllegalArgumentException()
val tpe = typeList(index)
val indexInBin =
if (tpe <:< typeOf[AnyRef]) typeList.take(index).count(_ <:< typeOf[AnyRef])
else typeList.take(index).count(_ =:= tpe)
val indexExpr = c.Expr[Int](Literal(Constant(indexInBin)))
def cast(casted: Tree, targetType: Tree) = TypeApply(Select(casted, newTermName("asInstanceOf")), List(targetType))
if (tpe <:< typeOf[Unit]) reify { c.prefix.splice.units(indexExpr.splice) }
else if (tpe <:< typeOf[Boolean]) reify { c.prefix.splice.booleans(indexExpr.splice) }
else if (tpe <:< typeOf[Byte]) reify { c.prefix.splice.bytes(indexExpr.splice) }
else if (tpe <:< typeOf[Char]) reify { c.prefix.splice.chars(indexExpr.splice) }
else if (tpe <:< typeOf[Short]) reify { c.prefix.splice.shorts(indexExpr.splice) }
else if (tpe <:< typeOf[Int]) reify { c.prefix.splice.ints(indexExpr.splice) }
else if (tpe <:< typeOf[Float]) reify { c.prefix.splice.floats(indexExpr.splice) }
else if (tpe <:< typeOf[Double]) reify { c.prefix.splice.doubles(indexExpr.splice) }
else c.Expr[AnyRef](cast(reify(c.prefix.splice.refs(indexExpr.splice)).tree, TypeTree(tpe)))
}
}
class Tuple[+T](
val units: Array[Unit],
val booleans: Array[Boolean],
val bytes: Array[Byte],
val chars: Array[Char],
val shorts: Array[Short],
val ints: Array[Int],
val longs: Array[Long],
val floats: Array[Float],
val doubles: Array[Double],
val refs: Array[AnyRef]
) extends Dynamic {
def selectDynamic(nameExpr: String) = macro TupleMacros.get[T]
}
object Tuple {
def apply(exprs: Any *) = macro TupleMacros.apply
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment