-
Notifications
You must be signed in to change notification settings - Fork 56
Open
Description
//> using jvm system
//> using scala 3.7.1
import scala.quoted.*
enum BinOperator {
case Eq
}
object BinOperator {
given FromExpr[BinOperator] with {
def unapply(x: Expr[BinOperator])(using Quotes): Option[BinOperator] =
x match {
case '{ BinOperator.Eq } => Some(BinOperator.Eq)
}
}
}
enum Ast {
case Ident(name: String)
case Prop(base: Ast, name: String)
case Entity(table: String)
case Map(query: Ast, ident: Ident, body: Ast)
case Filter(query: Ast, ident: Ident, body: Ast)
case Lift(name: String)
case BinOp(op: BinOperator, left: Ast, right: Ast)
case Constant(value: Long)
}
object Ast {
given FromExpr[Constant] with {
def unapply(x: Expr[Constant])(using Quotes) = x match {
case '{ Ast.Constant(${ Expr(v) }) } => Some(Ast.Constant(v))
}
}
given FromExpr[BinOp] with {
def unapply(x: Expr[BinOp])(using Quotes) = x match {
case '{ BinOp(${ Expr(op) }, ${ Expr(left) }, ${ Expr(right) }) } =>
Some(BinOp(op, left, right))
}
}
given FromExpr[Ast.Ident] with {
def unapply(expr: Expr[Ast.Ident])(using Quotes): Option[Ast.Ident] =
expr match {
case '{ Ast.Ident(${ Expr(name) }) } => Some(Ast.Ident(name))
case _ => None
}
}
given FromExpr[Ast.Prop] with {
def unapply(expr: Expr[Ast.Prop])(using Quotes): Option[Ast.Prop] =
expr match {
case '{ Ast.Prop(${ Expr(base) }, ${ Expr(name) }) } =>
Some(Ast.Prop(base, name))
case _ => None
}
}
given FromExpr[Ast.Entity] with {
def unapply(expr: Expr[Ast.Entity])(using Quotes): Option[Ast.Entity] =
expr match {
case '{ Ast.Entity(${ Expr(table) }) } => Some(Ast.Entity(table))
case _ => None
}
}
given FromExpr[Ast.Map] with {
def unapply(expr: Expr[Ast.Map])(using Quotes): Option[Ast.Map] =
expr match {
// query, ident, and body are Ast types, so we extract their Exprs and unapply explicitly
case '{ Ast.Map(${ Expr(q) }, ${ Expr(id) }, ${ Expr(body) }) } =>
Some(Ast.Map(q, id, body))
case _ => None
}
}
given FromExpr[Ast.Filter] with {
def unapply(expr: Expr[Ast.Filter])(using Quotes): Option[Ast.Filter] =
expr match {
// query, ident, and body are Ast types, so we extract their Exprs and unapply explicitly
case '{ Ast.Filter(${ Expr(q) }, ${ Expr(id) }, ${ Expr(body) }) } =>
Some(Ast.Filter(q, id, body))
case _ => None
}
}
// FromExpr for Ast.Lift
given FromExpr[Ast.Lift] with {
def unapply(expr: Expr[Ast.Lift])(using Quotes): Option[Ast.Lift] =
expr match {
case '{ Ast.Lift(${ Expr(name) }) } => Some(Ast.Lift(name))
case _ => None
}
}
// Composite FromExpr for the Ast enum
given FromExpr[Ast] with {
def unapply(expr: Expr[Ast])(using Quotes): Option[Ast] = {
import quotes.reflect.*
extractTerm(expr.asTerm).asExpr match {
case '{
val p: EntityRef[t] = $pb
$e: Ast
} =>
e.value
case '{ $e: Ast.Ident } => e.value
case '{ $e: Ast.Prop } => e.value
case '{ $e: Ast.Entity } => e.value
case '{ $e: Ast.Map } => e.value
case '{ $e: Ast.Filter } => e.value
case '{ $e: Ast.Lift } => e.value
case '{ $e: Ast.BinOp } => e.value
case '{ $e: Ast.Constant } => e.value
case e =>
println(e.show)
None
}
}
}
}
private def extractTerm(using Quotes)(x: quotes.reflect.Term) = {
import quotes.reflect.*
def unwrapTerm(t: Term): Term = t match {
case Inlined(_, _, o) => unwrapTerm(o)
case Block(Nil, last) => last
case Typed(t, _) =>
unwrapTerm(t)
case Select(t, "$asInstanceOf$") =>
unwrapTerm(t)
case TypeApply(t, _) =>
unwrapTerm(t)
case o => o
}
val o = unwrapTerm(x)
o
}
opaque type Quoted <: Ast = Ast
opaque type QuotedExpr[V] <: Quoted = Ast
object Quoted {
inline def apply(inline x: Ast): Ast = x
inline def const(inline v: Long): QuotedExpr[Long] = Ast.Constant(v)
}
opaque type Col[E] <: Quoted = Ast
object Col {
extension [E](inline e: Col[E]) {
inline def ===(inline e1: QuotedExpr[E]): QuotedExpr[Boolean] =
Ast.BinOp(BinOperator.Eq, e, e1)
}
}
opaque type Query[A] <: Quoted & Ast = Ast
object Query {
inline def apply[A](inline x: Ast): Query[A] = x
extension [A](inline qa: Query[A]) {
inline def map[B <: Quoted](inline f: EntityRef[A] => B): Query[B] =
Ast.Map(qa, Ast.Ident("x"), f(EntityRef[A]))
inline def filter(inline f: EntityRef[A] => QuotedExpr[Boolean]) = {
Ast.Filter(qa, Ast.Ident("x"), f(EntityRef[A]))
}
}
}
class EntityRef[A] extends Selectable {
type Fields = A
}
extension [A](e: EntityRef[A]) {
inline def selectDynamic(inline n: String) = Ast.Prop(Ast.Ident("x"), n)
}
inline def staticAst(inline x: Ast): Option[String] = ${
staticAstImpl('x)
}
private def staticAstImpl(x: Expr[Ast])(using Quotes): Expr[Option[String]] = {
x.value match {
case Some(astInstance) =>
Expr(Some(astInstance.toString))
case None =>
Expr(None)
}
}
inline def Persons: Query[(id: Col[Long])] = Query(Ast.Entity("person"))
staticAst(Persons.map(p => p.id))
// Some(Map(Entity(person),Ident(x),Prop(Ident(x),id)))
staticAst(Persons.filter(p => p.id === Quoted.const(1L)))
// Some(Filter(Entity(person),Ident(x),BinOp(Eq,Prop(Ident(x),id),Constant(1))))v3xro
Metadata
Metadata
Assignees
Labels
No labels