-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathMiniQuill.scala
207 lines (173 loc) · 10.2 KB
/
MiniQuill.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package org.stuff
import scala.quoted._
object MiniQuill:
sealed trait Ast
case class Entity(name:String) extends Ast
case class Ident(name:String) extends Ast
case class Filter(query:Ast, alias:Ident, body: Ast) extends Ast
case class Map(query:Ast, alias:Ident, body: Ast) extends Ast
case class FlatMap(query:Ast, alias:Ident, body: Ast) extends Ast
case class Property(ast: Ast, name: String) extends Ast
case class Tuple(values: List[Ast]) extends Ast
case class Constant(value: Any) extends Ast
case class Function(params: List[Ident], body: Ast) extends Ast
case class FunctionApply(function: Ast, values: List[Ast]) extends Ast
case class Block(statements: List[Ast]) extends Ast
case class Val(name: Ident, body: Ast) extends Ast
sealed trait Operator
object Operator:
case object `==` extends Operator
case object `&&` extends Operator
case class BinaryOperation(left:Ast, op:Operator, right:Ast) extends Ast
case class Quoted[T](ast: Ast):
def unquote = throw new IllegalArgumentException("Only a compile-time-construct")
class Query[T] {
def filter(e:T => Boolean): Query[T] = throw new IllegalArgumentException("This can only be used inside a quoted block")
def withFilter(e:T => Boolean): Query[T] = throw new IllegalArgumentException("This can only be used inside a quoted block")
def map[R](e:T => R): Query[R] = throw new IllegalArgumentException("This can only be used inside a quoted block")
def flatMap[R](e:T => Query[R]): Query[R] = throw new IllegalArgumentException("This can only be used inside a quoted block")
}
object Dsl {
def query[T]: Query[T] = throw new IllegalArgumentException("This can only be used inside a quoted block")
inline def unquote[T](inline quoted:Quoted[T]): T = ${ unquoteImpl[T]('quoted) }
def unquoteImpl[T:Type](quoted: Expr[Quoted[T]])(using Quotes): Expr[T] = {
import quotes.reflect._
'{ $quoted.unquote } /*Quoted[Query[T]] => Query[T]*/
}
implicit inline def autoUnquote[T](inline quoted: Quoted[T]): T = unquote(quoted)
/** ============================================ Quotation ===================================== **/
inline def quote[T](inline quoted:T): Quoted[T] = ${ quoteImpl[T]('quoted) }
def quoteImpl[T:Type](quoted: Expr[T])(using Quotes): Expr[Quoted[T]] = {
import quotes.reflect.{Ident => TIdent, Constant => TConstant, Block => TBlock, ValDef => TValDef, _}
val quotedRaw = quoted.asTerm.underlyingArgument.asExpr
/** =========================== Parse ======================== **/
object Parser:
import Extractors._
def astParse(expr: Expr[Any]): Ast =
expr match
case '{ ($q: Quoted[t]).unquote } => astParse(q)
case '{ Quoted.apply[t]($ast) } => Unlifter(ast)
case '{ query[t] } => Entity(TypeRepr.of[t].classSymbol.get.name)
case '{ ($query: Query[t]).filter(${Lambda1(alias, body)}) } => Filter(astParse(query), Ident(alias), astParse(body))
case '{ ($query: Query[t]).withFilter(${Lambda1(alias, body)}) } => Filter(astParse(query), Ident(alias), astParse(body))
case '{ ($query: Query[t]).map[mt](${Lambda1(alias, body)}) } => Map(astParse(query), Ident(alias), astParse(body))
case '{ ($query: Query[t]).flatMap[mt](${Lambda1( alias, body)}) } => FlatMap(astParse(query), Ident(alias), astParse(body))
case NamedOp1(left, "==", right) => BinaryOperation(astParse(left), Operator.==, astParse(right))
case NamedOp1(left, "&&", right) => BinaryOperation(astParse(left), Operator.&&, astParse(right))
case Unseal(Apply(TypeApply(Select(TupleIdent(), "apply"), types), values)) => Tuple(values.map(v => astParse(v.asExpr)))
case block @ Unseal(TBlock(parts, lastPart)) if (parts.length > 0) =>
val partsAsts =
parts.map {
case term: Term => astParse(term.asExpr)
case ValDefTerm(ast, bodyExpr) => Val(Ident(ast), astParse(bodyExpr))
case other => report.throwError(s"Illegal statement ${other.show} in block ${block.show}")
}
val lastPartAst = astParse(lastPart.asExpr)
Block((partsAsts :+ lastPartAst))
case Unseal(Select(TIdent(id: String), prop)) => Property(Ident(id), prop)
case id @ Unseal(i @ TIdent(x)) =>
val owner = Symbol.spliceOwner
if (isTermExternal(i)) report.warning(s"The term: ${i.show} is external to the quote")
Ident(x)
case Unseal(Typed(inside /*Term*/, _)) => astParse(inside.asExpr)
case _ => report.throwError(
s"""
|Cannot parse the tree:
|=================== Simple ==============
|${Printer.TreeShortCode.show(expr.asTerm)}
|=================== Full AST ==============
|${Printer.TreeStructure.show(expr.asTerm)}
""".stripMargin)
end Parser
val quillAst: Ast = Parser.astParse(quotedRaw)
val liftedQuillAst: Expr[Ast] = Lifter(quillAst)
'{ Quoted($liftedQuillAst) }
}
}
/** =========================== Unlift ======================== **/
object Unlifter:
def apply(ast: Expr[Ast]): Quotes ?=> Ast = unliftAst.apply(ast) // can also do ast.lift but this makes some error messages simpler
extension [T](t: Expr[T])(using FromExpr[T], Quotes)
def unexpr: T = t.valueOrError
trait NiceUnliftable[T] extends FromExpr[T]:
def unlift: Quotes ?=> PartialFunction[Expr[T], T]
def apply(expr: Expr[T])(using Quotes): T =
import quotes.reflect._
unlift.lift(expr).getOrElse { throw new IllegalArgumentException(s"Could not Unlift ${Printer.TreeShortCode.show(expr.asTerm)}") }
def unapply(expr: Expr[T])(using Quotes): Option[T] = unlift.lift(expr)
given unliftProperty: NiceUnliftable[Property] with
def unlift =
case '{ Property(${ast}, ${Expr(name: String)}) } => Property(ast.unexpr, name)
given unliftIdent: NiceUnliftable[Ident] with
def unlift =
case '{ Ident(${Expr(name: String)}) } => Ident(name)
given unliftAst: NiceUnliftable[Ast] with
def unlift =
case '{ Constant(${Expr(b: Double)}: Double) } => Constant(b)
case '{ Constant(${Expr(b: Boolean)}: Boolean) } => Constant(b)
case '{ Constant(${Expr(b: String)}: String) } => Constant(b)
case '{ Constant(${Expr(b: Int)}: Int) } => Constant(b)
case '{ Entity.apply(${Expr(b: String)}) } => Entity(b)
case '{ Function($params, $body) } => Function(params.unexpr, body.unexpr)
case '{ FunctionApply($function, $values) } => FunctionApply(function.unexpr, values.unexpr)
case '{ Map(${query}, ${alias}, ${body}: Ast) } => Map(query.unexpr, alias.unexpr, body.unexpr)
case '{ FlatMap(${query}, ${alias}, ${body}: Ast) } => FlatMap(query.unexpr, alias.unexpr, body.unexpr)
case '{ Filter(${query}, ${alias}, ${body}: Ast) } => Filter(query.unexpr, alias.unexpr, body.unexpr)
case '{ BinaryOperation(${a}, ${operator}, ${b}: Ast) } => BinaryOperation(a.unexpr, unliftOperator(operator).asInstanceOf[Operator], b.unexpr)
case '{ Property(${ast}, ${Expr(name: String)}) } => Property(ast.unexpr, name)
case '{ Tuple.apply($values) } => Tuple(values.unexpr)
case '{ $p: Property } => unliftProperty(p)
case '{ $id: Ident } => unliftIdent(id)
given unliftOperator: NiceUnliftable[Operator] with
def unlift =
case '{ Operator.== } => Operator.==
case '{ Operator.&& } => Operator.&&
end Unlifter
/** =========================== Lift ======================== **/
object Lifter:
def apply(ast: Ast): Quotes ?=> Expr[Ast] = liftableAst(ast)
extension [T](t: T)(using ToExpr[T], Quotes)
def expr: Expr[T] = Expr(t)
trait NiceLiftable[T] extends ToExpr[T]:
def lift: Quotes ?=> PartialFunction[T, Expr[T]]
def apply(t: T)(using Quotes): Expr[T] = lift.lift(t).getOrElse { throw new IllegalArgumentException(s"Could not Lift ${t}") }
def unapply(t: T)(using Quotes) = Some(apply(t))
given liftableProperty : NiceLiftable[Property] with
def lift =
case Property(core: Ast, name: String) => '{ Property(${core.expr}, ${name.expr}) }
given liftableIdent : NiceLiftable[Ident] with
def lift =
case Ident(name: String) => '{ Ident(${name.expr}) }
extension [T: Type](list: List[T])(using ToExpr[T], Quotes)
def spliceVarargs = Varargs(list.map(Expr(_)).toSeq)
given liftableEntity : NiceLiftable[Entity] with
def lift =
case Entity(name: String) => '{ Entity(${name.expr}) }
given liftableTuple: NiceLiftable[Tuple] with
def lift =
case Tuple(values) => '{ Tuple(${values.expr}) }
given liftableAst : NiceLiftable[Ast] with
def lift =
case Constant(v: Double) => '{ Constant(${Expr(v)}) }
case Constant(v: Boolean) => '{ Constant(${Expr(v)}) }
case Constant(v: String) => '{ Constant(${Expr(v)}) }
case Constant(v: Int) => '{ Constant(${Expr(v)}) }
case Function(params: List[Ident], body: Ast) => '{ Function(${params.expr}, ${body.expr}) }
case FunctionApply(function: Ast, values: List[Ast]) => '{ FunctionApply(${function.expr}, ${values.expr}) }
case v: Entity => liftableEntity(v)
case v: Tuple => liftableTuple(v)
case Map(query: Ast, alias: Ident, body: Ast) => '{ Map(${query.expr}, ${alias.expr}, ${body.expr}) }
case FlatMap(query: Ast, alias: Ident, body: Ast) => '{ FlatMap(${query.expr}, ${alias.expr}, ${body.expr}) }
case Filter(query: Ast, alias: Ident, body: Ast) => '{ Filter(${query.expr}, ${alias.expr}, ${body.expr}) }
case BinaryOperation(a: Ast, operator: Operator, b: Ast) =>
val castOp = liftOperator(operator).asInstanceOf[Expr[Operator]]
'{ BinaryOperation(${a.expr}, ${castOp}, ${b.expr}) }
case v: Property => liftableProperty(v)
case v: Ident => liftableIdent(v)
import Operator.{ == => ee}
given liftOperator : NiceLiftable[Operator] with
def lift =
case _: ee.type => '{ Operator.== }
case Operator.&& => '{ Operator.&& }
end Lifter
end MiniQuill