Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
scala.scalanative.native.cobj.CObj.scala Maven / Gradle / Ivy
package scala.scalanative.native.cobj
import de.surfice.smacrotools.MacroAnnotationHandler
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.{TypecheckException, whitebox}
import scala.scalanative.native._
import scala.scalanative.native.cobj.runtime.CObjObject
@compileTimeOnly("enable macro paradise to expand macro annotations")
class CObj(prefix: String = null, newSuffix: String = null, namingConvention: NamingConvention.Value = NamingConvention.SnakeCase) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro CObj.Macro.impl
}
object CObj {
class CObjWrapper extends StaticAnnotation
private[native] class Macro(val c: whitebox.Context) extends MacroBase {
def isMutable: Boolean = false
override def annotationName = "scala.scalanative.native.cobj.CObj"
override def supportsClasses: Boolean = true
override def supportsTraits: Boolean = true
override def supportsObjects: Boolean = true
override def createCompanion: Boolean = true
}
private[native] abstract class MacroBase extends MacroAnnotationHandler {
val c: whitebox.Context
import c.universe._
def isMutable: Boolean
private val tPtrByte = weakTypeOf[Ptr[Byte]]
private val tAnyRef = weakTypeOf[AnyRef]
private val tCObjObject = weakTypeOf[CObjObject]
private val tCObjWrapperAnnotation = weakTypeOf[CObjWrapper]
private val expExtern = q"scalanative.native.extern"
private val cobjWrapperAnnotation = q"new scalanative.native.cobj.CObj.CObjWrapper"
private val annotationParamNames = Seq("prefix","newSuffix","namingConvention","semantics")
implicit class MacroData(data: Map[String,Any]) {
type Data = Map[String, Any]
type Externals = Map[String, (String, Tree)]
type Statements = Seq[Tree]
def externalPrefix: String = data.getOrElse("externalPrefix", "").asInstanceOf[String]
def withExternalPrefix(prefix: String): Data = data.updated("externalPrefix",prefix)
def newSuffix: String = data.getOrElse("newSuffix","").asInstanceOf[String]
def withNewSuffix(suffix: String): Data = data.updated("newSuffix",suffix)
def namingConvention: NamingConvention.Value = data.getOrElse("namingConvention",NamingConvention.SnakeCase).asInstanceOf[NamingConvention.Value]
def withNamingConvention(nc: NamingConvention.Value): Data = data.updated("namingConvention",nc)
def isAbstract: Boolean = data.getOrElse("isAbstract",false).asInstanceOf[Boolean]
def withIsAbstract(flag: Boolean): Data = data.updated("isAbstract",flag)
def constructors: Seq[(String,Seq[ValDef])] = data.getOrElse("constructors",Nil).asInstanceOf[Seq[(String,Seq[ValDef])]]
def withConstructors(ctors: Seq[(String,Seq[Tree])]): Data = data.updated("constructors",ctors)
def externals: Externals = data.getOrElse("externals", Map()).asInstanceOf[Externals]
def withExternals(externals: Externals): Data = data.updated("externals",externals)
def currentType: String = data.getOrElse("currentType","").asInstanceOf[String]
def withCurrentType(tpe: String): Data = data.updated("currentType",tpe)
def parentIsCObj: Boolean = data.getOrElse("parentIsCObj",false).asInstanceOf[Boolean]
def withParentIsCObj(flag: Boolean): Data = data.updated("parentIsCObj",flag)
def additionalCompanionStmts: Statements = data.getOrElse("compStmts", Nil).asInstanceOf[Statements]
def withAdditionalCompanionStmts(stmts: Statements): Data = data.updated("compStmts",stmts)
}
override def analyze: Analysis = super.analyze andThen {
case (cls: ClassParts, data) =>
val updData = (
analyzeMainAnnotation(cls) _
andThen analyzeTypes(cls) _
andThen analyzeConstructor(cls) _
andThen analyzeBody(cls) _
)(data)
(cls, updData)
case (trt: TraitParts, data) =>
val updData = (
analyzeMainAnnotation(trt) _
andThen analyzeTypes(trt) _
andThen analyzeBody(trt) _
)(data)
(trt, updData)
case (obj: ObjectParts, data) =>
val updData = (
analyzeMainAnnotation(obj) _
andThen analyzeBody(obj) _
)(data)
(obj, updData)
case default => default
}
override def transform: Transformation = super.transform andThen {
/* transform class */
case cls: ClassTransformData =>
val transformedBody = genTransformedTypeBody(cls)
cls
.updBody(transformedBody)
.addAnnotations(cobjWrapperAnnotation)
.updCtorParams(genTransformedCtorParams(cls))
.updParents(genTransformedParents(cls))
/* transform trait */
case trt: TraitTransformData =>
val transformedBody = genTransformedTypeBody(trt)
trt
.updBody(transformedBody)
.addAnnotations(cobjWrapperAnnotation)
.updParents(genTransformedParents(trt))
/* transform companion object */
case obj: ObjectTransformData =>
val transformedBody = genTransformedCompanionBody(obj) ++ obj.data.additionalCompanionStmts :+ genBindingsObject(obj.data)
obj.updBody(transformedBody)
case default => default
}
private def analyzeMainAnnotation(tpe: CommonParts)(data: Data): Data = {
val annotParams = extractAnnotationParameters(c.prefix.tree, annotationParamNames)
val externalPrefix = annotParams("prefix") match {
case Some(prefix) => extractStringConstant(prefix).get
case None => genPrefixName(tpe.nameString)
}
val newSuffix = annotParams("newSuffix") match {
case Some(suffix) => extractStringConstant(suffix).get
case None => "new"
}
val namingConvention = annotParams("namingConvention") match {
case Some(Select(_,name)) => NamingConvention.withName(name.toString)
case None => NamingConvention.SnakeCase
}
data.withExternalPrefix(externalPrefix).withNewSuffix(newSuffix).withNamingConvention(namingConvention)
}
// TODO: move all checks (isCRef, isAbstract, ... in here and store the results in data)
private def analyzeTypes(tpe: TypeParts)(data: Data): Data = {
val isAbstract = tpe.modifiers.hasFlag(Flag.ABSTRACT)
// val crefType = tpe.parents.map(getType(_)).filter( _ <:< tCRef ) match {
// case Nil => tByte
// case List(tpe@TypeRef(_,sym,args)) if sym.toString == "trait CRef" =>
// args.head
// case List(tpe) =>
// c.error(c.enclosingPosition,"CObj types can only directly extend CRef[T]")
// ???
// case types =>
// c.error(c.enclosingPosition,s"CObj types can't extend more than one instance of CRef (found: $types)")
// ???
// }
val parentIsCObj = isExternalObject(tpe.parents.head,data)
// val parentIsAbstract = getType(tpe.parents.head).typeSymbol.isAbstract
data
.withIsAbstract(isAbstract)
.withCurrentType(tpe.fullName)
.withParentIsCObj(parentIsCObj)
// .withCRefType(crefType)
// .withParentIsCRef(parentIsCRef)
// .withParentIsAbstract(parentIsAbstract)
// .withFullName(tpe.fullName)
}
private def analyzeConstructor(cls: ClassParts)(data: Data): Data = {
val companionStmts =
if (cls.isClass && !cls.modifiers.hasFlag(Flag.ABSTRACT))
List(genWrapperImplicit(cls.name, cls.tparams))
else
Nil
data
// .withConstructors( Seq( (genExternalName(data.externalPrefix,data.newSuffix,data.namingConvention),cls.params.asInstanceOf[List[ValDef]]) ) )
.withAdditionalCompanionStmts(data.additionalCompanionStmts ++ companionStmts)
}
private def analyzeBody(tpe: CommonParts)(data: Data): Data = {
val prefix = data.externalPrefix
val typeExternals = tpe.body.collect {
case t@DefDef(mods, name, types, args, rettype, rhs) if isExtern(rhs) =>
genExternalBinding(prefix,t,!tpe.isObject,data)
}
val companionExternals = tpe match {
case t: TypeParts => t.companion.map(_.body.collect {
case t@DefDef(mods, name, types, args, rettype, rhs) if isExtern(rhs) =>
genExternalBinding(prefix,t,false,data)
}).getOrElse(Map())
case _ => Nil
}
data.withExternals( (typeExternals ++ companionExternals).toMap )
}
private def genTransformedParents(cls: TypeTransformData[TypeParts]): Seq[Tree] = {
cls.modParts.parents map (p => (p,getType(p,true))) map {
case (tree,tpe) if tpe =:= tAnyRef => tq"$tCObjObject"
case (tree,tpe) if tpe =:= tCObjObject || tpe.typeSymbol.isAbstract => tree
case (tree,tpe) if tpe <:< tCObjObject => q"$tree(__ptr)"
case (tree,_) => tree
}
}
private def genTransformedCtorParams(cls: ClassTransformData): Seq[Tree] =
// if (cls.data.isAbstract || isMutable)
// cls.modParts.params
// else if(cls.data.parentIsCRef)
// Seq(q"override val __ref: scalanative.native.cobj.Ref[${cls.data.crefType}]")
// else
if(cls.data.parentIsCObj)
Seq(q"override val __ptr: $tPtrByte")
else
Seq(q"val __ptr: $tPtrByte")
private def genTransformedTypeBody(t: TypeTransformData[TypeParts]): Seq[Tree] = {
val companion = t.modParts.companion.get.name
val imports = Seq(q"import $companion.__ext")
val ctors = genSecondaryConstructor(t)
// if(isMutable)
// Nil
// else if(isAbstract(t))
// Seq(q"def __ref: scalanative.native.cobj.Ref[${t.data.crefType}]")
// else
// genSecondaryConstructor(t)
// val ctors = Seq(s"val __ref: ${t.data.crefType} = ")
imports ++ ctors ++ (t.modParts.body map {
case tree @ DefDef(mods, name, types, args, rettype, rhs) if isExtern(rhs) =>
val externalName = t.data.externals(name.toString)._1
genExternalCall(externalName,tree,false,t.data)
case default => default
})
}
private def genTransformedCompanionBody(t: TransformData[CommonParts]): Seq[Tree] = {
t.modParts.body map {
case tree @ DefDef(mods, name, types, args, rettype, rhs) if isExtern(rhs) =>
val externalName = t.data.externals(name.toString)._1
genExternalCall(externalName,tree,t.modParts.isObject,t.data)
case default => default
}
}
private def genBindingsObject(data: MacroData): Tree = {
val ctors = data.constructors.map{
case (externalName,args) => q"def ${TermName(externalName)}(..$args): $tPtrByte = $expExtern"
}
val defs = data.externals.values.map(_._2)
q"""@scalanative.native.extern object __ext {..${ctors++defs}}"""
}
private def genWrapperImplicit(tpe: TypeName, tparams: Seq[Tree]): Tree =
tparams.size match {
case 0 =>
q"""implicit object __wrapper extends scalanative.native.cobj.CObjWrapper[$tpe] {
def wrap(ptr: scalanative.native.Ptr[Byte]) = new $tpe(ptr)
def unwrap(value: $tpe): scalanative.native.Ptr[Byte] = value.__ptr
}
"""
case 1 =>
q"""implicit object __wrapper extends scalanative.native.cobj.CObjWrapper[$tpe[_]] {
def wrap(ptr: scalanative.native.Ptr[Byte]) = new $tpe(ptr)
def unwrap(value: $tpe[_]): scalanative.native.Ptr[Byte] = value.__ptr
}
"""
case 2 =>
q"""implicit object __wrapper extends scalanative.native.cobj.CObjWrapper[$tpe[_,_]] {
def wrap(ptr: scalanative.native.Ptr[Byte]) = new $tpe(ptr)
def unwrap(value: $tpe[_,_]): scalanative.native.Ptr[Byte] = value.__ptr
}
"""
}
private def genSecondaryConstructor(t: TypeTransformData[TypeParts]): Seq[Tree] = {
val companion = t.modParts.companion.get.name
t.data.constructors.map { p =>
val args = transformExternalCallArgs(p._2,t.data)
DefDef(Modifiers(),
termNames.CONSTRUCTOR,
List(),
List(p._2.toList),
TypeTree(),
Block(
Nil,
Apply(Ident(termNames.CONSTRUCTOR), List(q"$companion.__ext.${TermName(p._1)}(..$args)"))
))
}
}
private def genPrefixName(clsName: String): String =
clsName.replaceAll("(.)([A-Z])","$1_$2").toLowerCase + "_"
private def genExternalName(prefix: String, scalaName: String, nc: NamingConvention.Value): String = nc match {
case NamingConvention.SnakeCase =>
prefix + scalaName.replaceAll("([A-Z])","_$1").toLowerCase
case NamingConvention.PascalCase =>
prefix + scalaName.head.toUpper + scalaName.tail
case _ => prefix + scalaName
}
private def genExternalBinding(prefix: String, scalaDef: DefDef, isInstanceMethod: Boolean, data: Data): (String,(String,Tree)) = {
val scalaName = scalaDef.name.toString
val externalName = genExternalName(prefix,scalaName,data.namingConvention)
val externalParams =
if(isInstanceMethod) scalaDef.vparamss match {
case Nil => List(List(q"val self: $tPtrByte"))
case List(params) => List(q"val self: $tPtrByte" +: transformExternalBindingParams(params,data) )
case List(inparams,outparams) =>
List( q"val self: $tPtrByte" +:
(transformExternalBindingParams(inparams,data) ++ transformExternalBindingParams(outparams,data,true)) )
case _ =>
c.error(c.enclosingPosition,"extern methods with more than two parameter lists are not supported for @CObj classes")
???
}
else scalaDef.vparamss match {
case Nil => List(Nil)
case List(params) => List(transformExternalBindingParams(params,data))
case List(inparams,outparams) => List(transformExternalBindingParams(inparams++outparams,data))
case x =>
c.error(c.enclosingPosition,"extern methods with more than two parameter lists are not supported for @CObj classes")
???
}
val tpt =
if(isExternalObject(scalaDef.tpt,data)) tq"scalanative.native.Ptr[Byte]"
else scalaDef.tpt
val mods = Modifiers(NoFlags,scalaDef.mods.privateWithin,scalaDef.mods.annotations) // remove flags (e.g. 'override')
val externalDef = DefDef(mods,TermName(externalName),scalaDef.tparams,externalParams,tpt,scalaDef.rhs)
(scalaName,(externalName,externalDef))
}
private def transformExternalBindingParams(params: List[ValDef], data: Data, outParams: Boolean = false): List[ValDef] = {
params map {
case ValDef(mods,name,tpt,rhs) if isExternalObject(tpt,data) =>
ValDef(mods,name,q"$tPtrByte",rhs)
case default => default
}
}
private def genExternalCall(externalName: String, scalaDef: DefDef, isClassMethod: Boolean, data: Data): DefDef = {
import scalaDef._
val args = vparamss match {
case Nil => None
case List(args) => Some(transformExternalCallArgs(args,data))
case List(inargs,outargs) =>
Some( transformExternalCallArgs(inargs,data) ++ transformExternalCallArgs(outargs,data) )
case _ =>
c.error(c.enclosingPosition,"extern methods with more than two parameter lists are not supported for @CObj classes")
???
}
val external = TermName(externalName)
val call = args match {
case Some(as) if isClassMethod => q"__ext.$external(..$as)"
case Some(as) => q"__ext.$external(__ptr,..$as)"
case None if isClassMethod => q"__ext.$external"
case None => q"__ext.$external(__ptr)"
}
val rhs =
// if(returnsThis(scalaDef))
// q"$call;this"
// else if(updatesThis(scalaDef))
// q"this.__ref = $call.cast[$tRefNothing];this"
// else
wrapExternalCallResult(call,tpt,data,nullable(scalaDef))
DefDef(mods,name,tparams,vparamss,tpt,rhs)
}
private def transformExternalCallArgs(args: Seq[ValDef], data: Data, outArgs: Boolean = false): Seq[Tree] = {
args map {
// TODO: currently crashes due to https://github.com/scala-native/scala-native/issues/1142
case ValDef(_,name,tpt,_) if isExternalObject(tpt,data) || outArgs =>
//q"$name.__ref.cast[$tPtrByte]"
q"""if($name==null) null else $name.__ptr"""
case ValDef(_,name,AppliedTypeTree(tpe,_),_) if tpe.toString == "_root_.scala." => q"$name:_*"
case ValDef(_,name,tpt,_) => q"$name"
}
}
private def wrapExternalCallResult(tree: Tree, tpt: Tree, data: Data, isNullable: Boolean): Tree = {
if (isExternalObject(tpt,data)) {
if(isNullable)
q"""val res = $tree; if(res == null) null else new $tpt(res)"""
else
q"""new $tpt($tree)"""
}
else tree
}
private def isExternalObject(tpt: Tree, data: Data): Boolean =
try {
val typed = getType(tpt,true)
// TODO: do we still need the check for tCRef (or can we only check for tCObjWrapper)
typed.baseClasses.map(_.asType.toType).exists( t => t <:< tCObjObject) ||
this.findAnnotation(typed.typeSymbol,"scalanative.native.cobj.CObj.CObjWrapper").isDefined ||
data.currentType == getQualifiedTypeName(tpt,true)
} catch {
case ex: TypecheckException => false
}
private def isExtern(rhs: Tree): Boolean = rhs match {
case Ident(TermName(id)) =>
id == "extern"
case Select(_,name) =>
name.toString == "extern"
case x =>
false
}
private def isAbstract(t: TypeTransformData[TypeParts]): Boolean = t match {
case cls : ClassTransformData => t.modParts.modifiers.hasFlag(Flag.ABSTRACT)
case _ => false
}
private def nullable(m: DefDef): Boolean =
findAnnotation(m.mods.annotations,"scala.scalanative.native.cobj.nullable").isDefined
}
}