-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
85e1e5c
commit 79cf9f3
Showing
8 changed files
with
1,044 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package proto | ||
|
||
import scala.quoted.* | ||
import scala.collection.immutable.ArraySeq | ||
import compiletime.asMatchable | ||
import scala.annotation.* | ||
|
||
trait Common: | ||
implicit val qctx: Quotes | ||
import qctx.reflect.{*, given} | ||
import qctx.reflect.defn.* | ||
import report.* | ||
|
||
val ArrayByteType: TypeRepr = TypeRepr.of[Array[Byte]] | ||
val ArraySeqByteType: TypeRepr = TypeRepr.of[ArraySeq[Byte]] | ||
val NTpe: TypeRepr = TypeRepr.of[N] | ||
val ItetableType: TypeRepr = TypeRepr.of[scala.collection.Iterable[?]] | ||
val ArrayType: TypeRepr = TypeRepr.of[Array[?]] | ||
|
||
extension (s: Symbol) | ||
def constructorParams: List[Symbol] = s.primaryConstructor.paramSymss.find(_.headOption.fold(false)( _.isTerm)).getOrElse(Nil) | ||
def tpe: TypeRepr = | ||
s.tree match | ||
case x: ClassDef => x.constructor.returnTpt.tpe | ||
case ValDef(_,tpt,_) => tpt.tpe | ||
case Bind(_, pattern: Term) => pattern.tpe | ||
|
||
def defaultMethodName(i: Int): String = s"$$lessinit$$greater$$default$$${i+1}" | ||
|
||
val commonTypes: List[TypeRepr] = | ||
TypeRepr.of[String] :: TypeRepr.of[Int] :: TypeRepr.of[Long] :: TypeRepr.of[Boolean] :: TypeRepr.of[Double] :: TypeRepr.of[Float] :: ArrayByteType :: ArraySeqByteType :: Nil | ||
|
||
val packedTypes: List[TypeRepr] = | ||
TypeRepr.of[Int] :: TypeRepr.of[Long] :: TypeRepr.of[Boolean] :: TypeRepr.of[Double] :: TypeRepr.of[Float] :: Nil | ||
|
||
extension (t: TypeRepr) | ||
def isNType: Boolean = t =:= NTpe | ||
def isCaseClass: Boolean = t.typeSymbol.flags.is(Flags.Case) | ||
def isCaseObject: Boolean = t.termSymbol.flags.is(Flags.Case) | ||
def isCaseType: Boolean = t.isCaseClass || t.isCaseObject | ||
def isSealedTrait: Boolean = t.typeSymbol.flags.is(Flags.Sealed) && t.typeSymbol.flags.is(Flags.Trait) | ||
def isIterable: Boolean = t <:< ItetableType && !t.isArraySeqByte | ||
def isArray: Boolean = t <:< ArrayType && !t.isArrayByte | ||
def isRepeated: Boolean = t.isIterable || t.isArray | ||
def isString: Boolean = t =:= TypeRepr.of[String] | ||
def isInt: Boolean = t =:= TypeRepr.of[Int] | ||
def isLong: Boolean = t =:= TypeRepr.of[Long] | ||
def isBoolean: Boolean = t =:= TypeRepr.of[Boolean] | ||
def isDouble: Boolean = t =:= TypeRepr.of[Double] | ||
def isFloat: Boolean = t =:= TypeRepr.of[Float] | ||
def isArrayByte: Boolean = t =:= ArrayByteType | ||
def isArraySeqByte: Boolean = t =:= ArraySeqByteType | ||
def isCommonType: Boolean = commonTypes.exists(_ =:= t) | ||
def isPackedType: Boolean = packedTypes.exists(_ =:= t) | ||
|
||
def isOption: Boolean = t.asMatchable match | ||
case AppliedType(t1, _) if t1.typeSymbol == OptionClass => true | ||
case _ => false | ||
|
||
def typeArgs: List[TypeRepr] = t.dealias.asMatchable match | ||
case AppliedType(t1, args) => args | ||
case _ => Nil | ||
|
||
def optionArgument: TypeRepr = t.asMatchable match | ||
case AppliedType(t1, args) if t1.typeSymbol == OptionClass => args.head | ||
case _ => errorAndAbort(s"It isn't Option type: ${t.typeSymbol.name}") | ||
|
||
def knownFinalSubclasses: List[Symbol] = | ||
@tailrec def loop(q: List[Symbol], acc: List[Symbol]): List[Symbol] = q match | ||
case Nil => acc | ||
case x :: xs if x.tpe.isSealedTrait => loop(x.tpe.typeSymbol.children ++ xs, acc) | ||
case x :: xs => loop(xs, x :: acc) | ||
loop(t.typeSymbol.children, Nil) | ||
|
||
def findN(x: Symbol): Option[Int] = { | ||
x.annotations.collect{ | ||
case Apply(Select(New(tpt), _), List(Literal(IntConstant(num)))) | ||
if tpt.tpe.asMatchable.isNType => num | ||
} match | ||
case List(x) => Some(x) | ||
case _ => None | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
package proto | ||
|
||
import scala.quoted.* | ||
import scala.annotation.* | ||
|
||
trait Ops extends Common: | ||
implicit val qctx: Quotes | ||
import qctx.reflect.{*, given} | ||
import report.* | ||
|
||
private[proto] case class ChildMeta(name: String, tpe: TypeRepr, n: Int, noargs: Boolean, rec: Boolean) | ||
|
||
sealed trait Tpe { val tpe: TypeRepr; val name: String } | ||
final case class TraitType(tpe: TypeRepr, name: String, children: Seq[ChildMeta], firstLevel: Boolean) extends Tpe | ||
final case class RegularType(tpe: TypeRepr, name: String) extends Tpe | ||
final case class RecursiveType(tpe: TypeRepr, name: String) extends Tpe | ||
final case class NoargsType(tpe: TypeRepr, name: String) extends Tpe | ||
final case class TupleType(tpe: TypeRepr, name: String, tpe_1: TypeRepr, tpe_2: TypeRepr) extends Tpe | ||
|
||
sealed trait DefVal | ||
sealed trait HasDefFun { val value: String } | ||
sealed trait FillDef { val value: String } | ||
|
||
case object NoDef extends DefVal | ||
|
||
case object NoneDef extends DefVal with HasDefFun { val value = "Nothing" } | ||
case object SeqDef extends DefVal with HasDefFun { val value = "[]" } | ||
|
||
final case class StrDef(v: String) extends DefVal with HasDefFun with FillDef { val value = s""""$v"""" } | ||
final case class OthDef(v: Any) extends DefVal with HasDefFun with FillDef { val value = v.toString } | ||
|
||
sealed trait IterablePurs | ||
final case class ArrayPurs(tpe: TypeRepr) extends IterablePurs | ||
final case class ArrayTuplePurs(tpe1: TypeRepr, tpe2: TypeRepr) extends IterablePurs | ||
|
||
def collectDefExpressions(tpe: TypeRepr): Seq[(Expr[String], Expr[Any])] = { | ||
val types = collectTypeRepr(tpe, tail=Nil, acc=Nil) | ||
types.flatMap{ tpe => | ||
val tpe_sym = tpe.typeSymbol | ||
|
||
tpe.typeSymbol.constructorParams.zipWithIndex.map{ case (sym, i) => | ||
if sym.flags.is(Flags.HasDefault) then | ||
val compMod = tpe_sym.companionModule | ||
val id = s"`${tpe_sym.name}_${sym.name}_default`" | ||
compMod.methodMember(defaultMethodName(i)) match | ||
case List(methodSym) => | ||
Some(Expr(id) -> Ref(compMod).select(methodSym).asExpr) | ||
case _ => errorAndAbort(s"${sym.fullName}`: default value method not found") | ||
else None | ||
} | ||
}.flatten | ||
} | ||
|
||
def collectTypeRepr(head: TypeRepr, tail: Seq[TypeRepr], acc: Seq[TypeRepr]): Seq[TypeRepr] = { | ||
val (tail1, acc1): (Seq[TypeRepr], Seq[TypeRepr]) = | ||
if acc.exists(_ =:= head) then | ||
(tail, acc) | ||
else if head.isSealedTrait then | ||
val children = head.knownFinalSubclasses.map(_.tpe) | ||
(children++tail, acc) | ||
else if head.isOption then | ||
val typeArgType = head.optionArgument | ||
if !typeArgType.isCommonType then (typeArgType+:tail, acc) | ||
else (tail, acc) | ||
else if head.isRepeated then | ||
head.typeArgs match | ||
case x :: Nil => | ||
val typeArgType = x | ||
if !typeArgType.isCommonType then (typeArgType+:tail, acc) | ||
else (tail, acc) | ||
case x :: y :: Nil => | ||
val zs = List(x, y).filter(!_.isCommonType) | ||
(zs++tail, acc) | ||
case _ => throw new Exception(s"too many type args for ${head}") | ||
else | ||
val ys = head.typeSymbol.constructorParams.map(_.tpe) | ||
(ys++tail, acc:+head) | ||
|
||
tail1 match { | ||
case h +: t => collectTypeRepr(h, tail=t, acc=acc1) | ||
case _ => acc1 | ||
} | ||
} | ||
|
||
// @tailrec | ||
def collectTpes(tpe: TypeRepr): Seq[Tpe] = { | ||
collectTpes(tpe, tail=Nil, acc=Nil, firstLevel=true) | ||
} | ||
|
||
def collectTpes(head: TypeRepr, tail: Seq[TypeRepr], acc: Seq[Tpe], firstLevel: Boolean): Seq[Tpe] = { | ||
val (tail1, acc1): (Seq[TypeRepr], Seq[Tpe]) = | ||
if acc.exists(_.tpe =:= head) then | ||
(tail, acc) | ||
else if head.isSealedTrait then | ||
val children = findChildren(head) | ||
(children.map(_.tpe)++tail, acc:+TraitType(head, head.typeSymbol.name, children, firstLevel)) | ||
else if head.isOption then | ||
val typeArgType = head.optionArgument | ||
if !typeArgType.isCommonType then (typeArgType+:tail, acc) | ||
else (tail, acc) | ||
else if head.isRepeated then | ||
head.typeArgs match | ||
case x :: Nil => | ||
val typeArgType = x | ||
if !typeArgType.isCommonType then (typeArgType+:tail, acc) | ||
else (tail, acc) | ||
case x :: y :: Nil => | ||
val zs = List(x, y).filter(!_.isCommonType) | ||
(zs++tail, acc:+TupleType(TypeRepr.of[Tuple2[Unit, Unit]].appliedTo(x::y::Nil), tupleFunName(x, y), x, y)) //todo check | ||
case _ => throw new Exception(s"too many type args for ${head}") | ||
else | ||
val (ys, z) = type_to_tpe(head) | ||
(ys++tail, acc:+z) | ||
|
||
tail1 match { | ||
case h +: t => collectTpes(h, tail=t, acc=acc1, firstLevel=false) | ||
case _ => acc1 | ||
} | ||
} | ||
|
||
def fields(tpe: TypeRepr): List[(String, TypeRepr, Int, DefVal)] = { | ||
val tpe_Sym = tpe.typeSymbol | ||
val tpe_CompanionSym = tpe_Sym.companionModule | ||
val compClass = tpe_Sym.companionClass | ||
val compModRef = Ref(tpe_CompanionSym) | ||
|
||
tpe.typeSymbol.constructorParams.zipWithIndex.map{ case (sym, i) => | ||
val tpe1 = sym.tpe | ||
val defval = | ||
if sym.flags.is(Flags.HasDefault) then | ||
val id = s"`${tpe_Sym.name}_${sym.name}_default`" | ||
tpe_CompanionSym.methodMember(defaultMethodName(i)) match | ||
case List(x) if x.isDefDef => | ||
val y = x.tree.asInstanceOf[DefDef] | ||
if (y.returnTpt.tpe.isString) StrDef(id) | ||
else OthDef(id) | ||
case _ => errorAndAbort(s"${sym.fullName}`: default value method not found") | ||
else if tpe1.isOption then | ||
NoneDef | ||
else if tpe1.isRepeated then | ||
SeqDef | ||
else | ||
NoDef | ||
(sym.name, tpe1, findN(sym), defval) | ||
|
||
}.collect{ case (a, b, Some(n), dv) => (a, b, n, dv) }.sortBy(_._3) | ||
} | ||
|
||
def findChildren(tpe: TypeRepr): Seq[ChildMeta] = | ||
tpe.knownFinalSubclasses.map(x => x -> findN(x)).collect{ case (x, Some(n)) => x -> n }.sortBy(_._2).map{ | ||
case (x: Symbol, n) => | ||
val tpe1: TypeRepr = x.tpe | ||
ChildMeta(name=x.name, tpe1, n, noargs=fields(tpe1).isEmpty, rec=isRecursive(tpe1)) | ||
} | ||
|
||
def isRecursive(base: TypeRepr): Boolean = { | ||
@tailrec def loop(compareTo: List[TypeRepr]): Boolean = compareTo match | ||
case Nil => false | ||
case x :: _ if x =:= base => true | ||
case x :: xs => loop(x.typeArgs ++ xs) | ||
loop(fields(base).map(_._2).filter(tpe => !tpe.isCommonType)) | ||
} | ||
|
||
def tupleFunName(tpe_1: TypeRepr, tpe_2: TypeRepr): String = { | ||
(pursType(tpe_1)._1.filter(_.isLetter) + "_" + pursType(tpe_2)._1).filter(_.isLetter) | ||
} | ||
|
||
def type_to_tpe(head: TypeRepr): (Seq[TypeRepr], Tpe) = { | ||
val xs = fields(head).map(_._2) | ||
val ys = xs.filter(x => !x.isCommonType) | ||
val z = | ||
if (xs.isEmpty) NoargsType(head, head.typeSymbol.name.stripSuffix("$")) //strip $ for case objects | ||
else if (isRecursive(head)) RecursiveType(head, head.typeSymbol.name) | ||
else RegularType(head, head.typeSymbol.name) | ||
(ys, z) | ||
} | ||
|
||
def pursType(tpe: TypeRepr): (String, String) = { | ||
def trim(x: String): String = x.stripPrefix("(").stripSuffix(")") | ||
val (a, b) = pursTypePars(tpe) | ||
trim(a) -> trim(b) | ||
} | ||
|
||
def pursTypePars(tpe: TypeRepr): (String, String) = { | ||
if tpe.isString then | ||
"String" -> "(Maybe String)" | ||
else if tpe.isInt then | ||
"Int" -> "(Maybe Int)" | ||
else if tpe.isLong then | ||
"BigInt" -> "(Maybe BigInt)" | ||
else if tpe.isBoolean then | ||
"Boolean" -> "(Maybe Boolean)" | ||
else if tpe.isDouble then | ||
"Number" -> "(Maybe Number)" | ||
else if tpe.isArrayByte then | ||
"Uint8Array" -> "(Maybe Uint8Array)" | ||
else if tpe.isOption then | ||
val typeArg = tpe.optionArgument | ||
if typeArg.isLong then | ||
"(Maybe BigInt)" -> "(Maybe BigInt)" | ||
else if typeArg.isDouble then | ||
"(Maybe Number)" -> "(Maybe Number)" | ||
else | ||
val name = typeArg.typeSymbol.name | ||
s"(Maybe $name)" -> s"Maybe $name)" | ||
else if tpe.isRepeated then | ||
iterablePurs(tpe) match | ||
case ArrayPurs(tpe) => | ||
val name = tpe.typeSymbol.name | ||
s"(Array $name)" -> s"(Array $name)" | ||
case ArrayTuplePurs(tpe1, tpe2) => | ||
val name1 = pursTypePars(tpe1)._1 | ||
val name2 = pursTypePars(tpe2)._1 | ||
s"(Array (Tuple $name1 $name2))" -> s"(Array (Tuple $name1 $name2))" | ||
else | ||
val name = tpe.typeSymbol.name | ||
name -> s"(Maybe $name)" | ||
} | ||
|
||
def iterablePurs(tpe: TypeRepr): IterablePurs = | ||
tpe.typeArgs match | ||
case x :: Nil => ArrayPurs(x) | ||
case x :: y :: Nil => ArrayTuplePurs(x, y) | ||
case _ => throw new Exception(s"too many type args for $tpe") |
Oops, something went wrong.