save
This commit is contained in:
parent
26572ffa0d
commit
bd1dbaa8e2
6 changed files with 267 additions and 21 deletions
51
src/main/scala/minisql/Parser.scala
Normal file
51
src/main/scala/minisql/Parser.scala
Normal file
|
@ -0,0 +1,51 @@
|
|||
package minisql.parsing
|
||||
|
||||
import minisql.ast
|
||||
import minisql.ast.Ast
|
||||
import scala.quoted.*
|
||||
|
||||
type Parser[O <: Ast] = PartialFunction[Expr[?], Expr[O]]
|
||||
|
||||
private[minisql] def parseFunction[X](
|
||||
x: Expr[X]
|
||||
)(using Quotes): Expr[(List[Ast], Ast)] = {
|
||||
import quotes.reflect.*
|
||||
x.asTerm match {
|
||||
case Lambda(vals, body) =>
|
||||
val paramExprs = vals.map {
|
||||
case ValDef(n, _, _) => '{ ast.Ident(${ Expr(n) }) }
|
||||
}
|
||||
???
|
||||
}
|
||||
}
|
||||
private def isNumeric(x: Expr[?])(using Quotes): Boolean = {
|
||||
import quotes.reflect.*
|
||||
x.asTerm.tpe match {
|
||||
case t if t <:< TypeRepr.of[Int] => true
|
||||
case t if t <:< TypeRepr.of[Long] => true
|
||||
case t if t <:< TypeRepr.of[Float] => true
|
||||
case t if t <:< TypeRepr.of[Double] => true
|
||||
case t if t <:< TypeRepr.of[BigDecimal] => true
|
||||
case t if t <:< TypeRepr.of[BigInt] => true
|
||||
case t if t <:< TypeRepr.of[java.lang.Integer] => true
|
||||
case t if t <:< TypeRepr.of[java.lang.Long] => true
|
||||
case t if t <:< TypeRepr.of[java.lang.Float] => true
|
||||
case t if t <:< TypeRepr.of[java.lang.Double] => true
|
||||
case t if t <:< TypeRepr.of[java.math.BigDecimal] => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def optionOperationParser(
|
||||
astParser: Parser[Ast]
|
||||
)(using Quotes): Parser[ast.OptionOperation] = {
|
||||
case '{ ($x: Option[t]).isEmpty } =>
|
||||
'{ ast.OptionIsEmpty(${ astParser(x) }) }
|
||||
}
|
||||
|
||||
def binaryOperationParser(
|
||||
astParser: Parser[Ast]
|
||||
)(using Quotes): Parser[ast.BinaryOperation] = {
|
||||
???
|
||||
}
|
|
@ -2,7 +2,7 @@ package minisql.ast
|
|||
|
||||
import minisql.NamingStrategy
|
||||
|
||||
import scala.quoted._
|
||||
import scala.quoted.*
|
||||
|
||||
sealed trait Ast {
|
||||
|
||||
|
@ -20,7 +20,6 @@ sealed trait Ast {
|
|||
override def apply(a: Ast) =
|
||||
super.apply(a.neutral)
|
||||
}.apply(this)
|
||||
|
||||
}
|
||||
|
||||
//************************************************************
|
||||
|
@ -45,7 +44,8 @@ case class Entity(
|
|||
properties: List[PropertyAlias],
|
||||
renameable: Renameable
|
||||
) extends Query {
|
||||
override def neutral: Entity = Entity(name, properties, Renameable.neutral)
|
||||
override inline def neutral: Entity =
|
||||
Entity(name, properties, Renameable.neutral)
|
||||
}
|
||||
|
||||
object Entity {
|
||||
|
@ -182,7 +182,7 @@ case class ExternalIdent(name: String) extends Ast
|
|||
*/
|
||||
sealed trait Opinion[T]
|
||||
sealed trait OpinionValues[T <: Opinion[T]] {
|
||||
def neutral: T
|
||||
inline def neutral: T
|
||||
}
|
||||
|
||||
sealed trait Visibility extends Opinion[Visibility]
|
||||
|
@ -226,7 +226,7 @@ case class Property(
|
|||
visibility: Visibility
|
||||
) extends Ast {
|
||||
|
||||
override def neutral: Property =
|
||||
override inline def neutral: Property =
|
||||
copy(renameable = Renameable.neutral, visibility = Visibility.neutral)
|
||||
}
|
||||
|
||||
|
@ -374,13 +374,6 @@ sealed trait Lift extends Ast {
|
|||
val liftId: String
|
||||
}
|
||||
|
||||
object Lift {
|
||||
private final val idGen = new java.util.concurrent.atomic.AtomicInteger()
|
||||
private[minisql] def newLiftId(): Int = {
|
||||
idGen.incrementAndGet()
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait ScalarLift extends Lift
|
||||
|
||||
case class ScalarValueLift(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package minisql.ast
|
||||
|
||||
import scala.quoted._
|
||||
import minisql.util.*
|
||||
import scala.quoted.*
|
||||
|
||||
private given FromExpr[PropertyAlias] with {
|
||||
def unapply(x: Expr[PropertyAlias])(using Quotes): Option[PropertyAlias] =
|
||||
|
@ -282,7 +283,7 @@ given astFromExpr: FromExpr[Ast] = new FromExpr[Ast] {
|
|||
def unapply(e: Expr[Ast])(using Quotes): Option[Ast] = {
|
||||
val et = extractTerm(e.toTerm)
|
||||
et match {
|
||||
case b: quotes.reflect.Block => fromBlock(b)
|
||||
case b: quotes.reflect.Block => fromBlock(b).map(BetaReduction(_))
|
||||
case b: quotes.reflect.Ident => Some(Ident(b.name))
|
||||
case o =>
|
||||
o.asExpr match {
|
||||
|
|
|
@ -11,9 +11,10 @@ sealed trait Dsl {
|
|||
|
||||
trait Query[E] extends Dsl
|
||||
|
||||
class EntityQuery[E](val ast: Ast) extends Query
|
||||
case class EntityQuery[E](val ast: Ast) extends Query[E]
|
||||
|
||||
extension [E](inline e: EntityQuery[E]) {
|
||||
|
||||
inline def mapAst[E1](inline f: Ast => Ast): EntityQuery[E1] =
|
||||
EntityQuery[E1](f(e.ast))
|
||||
}
|
||||
|
@ -49,9 +50,3 @@ private def compileImpl(x: Expr[Dsl])(using Quotes): Expr[Option[String]] = {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
case class Foo(id: Int)
|
||||
inline def queryFooId =
|
||||
query[Foo]("foo").mapAst[Int](x =>
|
||||
Map(x, Ident("x"), Property(Ident("x"), "id"))
|
||||
)
|
||||
|
|
154
src/main/scala/minisql/util/BetaReduction.scala
Normal file
154
src/main/scala/minisql/util/BetaReduction.scala
Normal file
|
@ -0,0 +1,154 @@
|
|||
package minisql.util
|
||||
|
||||
import minisql.ast.*
|
||||
import scala.collection.immutable.{Map => IMap}
|
||||
|
||||
case class BetaReduction(replacements: Replacements)
|
||||
extends StatelessTransformer {
|
||||
|
||||
override def apply(ast: Ast): Ast =
|
||||
ast match {
|
||||
|
||||
case ast if replacements.contains(ast) =>
|
||||
BetaReduction(replacements - ast - replacements(ast))(replacements(ast))
|
||||
|
||||
case Property(Tuple(values), name) =>
|
||||
apply(values(name.drop(1).toInt - 1))
|
||||
|
||||
case Property(CaseClass(tuples), name) =>
|
||||
apply(tuples.toMap.apply(name))
|
||||
|
||||
case FunctionApply(Function(params, body), values) =>
|
||||
val conflicts = values
|
||||
.flatMap(CollectAst.byType[Ident])
|
||||
.map { i =>
|
||||
i -> Ident(s"tmp_${i.name}")
|
||||
}
|
||||
.toMap[Ast, Ast]
|
||||
val newParams = params.map { p =>
|
||||
conflicts.getOrElse(p, p)
|
||||
}
|
||||
val bodyr = BetaReduction(
|
||||
Replacements(conflicts ++ params.zip(newParams))
|
||||
).apply(body)
|
||||
apply(
|
||||
BetaReduction(replacements ++ newParams.zip(values).toMap)
|
||||
.apply(bodyr)
|
||||
)
|
||||
|
||||
case Function(params, body) =>
|
||||
val newParams = params.map { p =>
|
||||
replacements.get(p) match {
|
||||
case Some(i: Ident) => i
|
||||
case _ => p
|
||||
}
|
||||
}
|
||||
Function(
|
||||
newParams,
|
||||
BetaReduction(replacements ++ params.zip(newParams).toMap)(body)
|
||||
)
|
||||
|
||||
case Block(statements) =>
|
||||
apply {
|
||||
statements.reverse.tail
|
||||
.foldLeft((IMap[Ast, Ast](), statements.last)) {
|
||||
case ((map, stmt), line) =>
|
||||
BetaReduction(Replacements(map))(line) match {
|
||||
case Val(name, body) =>
|
||||
val newMap = map + (name -> body)
|
||||
val newStmt = BetaReduction(stmt, Replacements(newMap))
|
||||
(newMap, newStmt)
|
||||
case _ =>
|
||||
(map, stmt)
|
||||
}
|
||||
}
|
||||
._2
|
||||
}
|
||||
|
||||
case Foreach(query, alias, body) =>
|
||||
Foreach(query, alias, BetaReduction(replacements - alias)(body))
|
||||
case Returning(action, alias, prop) =>
|
||||
val t = BetaReduction(replacements - alias)
|
||||
Returning(apply(action), alias, t(prop))
|
||||
|
||||
case ReturningGenerated(action, alias, prop) =>
|
||||
val t = BetaReduction(replacements - alias)
|
||||
ReturningGenerated(apply(action), alias, t(prop))
|
||||
|
||||
case other =>
|
||||
super.apply(other)
|
||||
}
|
||||
|
||||
override def apply(o: OptionOperation): OptionOperation =
|
||||
o match {
|
||||
case other @ OptionTableFlatMap(a, b, c) =>
|
||||
OptionTableFlatMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case OptionTableMap(a, b, c) =>
|
||||
OptionTableMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case OptionTableExists(a, b, c) =>
|
||||
OptionTableExists(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case OptionTableForall(a, b, c) =>
|
||||
OptionTableForall(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case other @ OptionFlatMap(a, b, c) =>
|
||||
OptionFlatMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case OptionMap(a, b, c) =>
|
||||
OptionMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case OptionForall(a, b, c) =>
|
||||
OptionForall(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case OptionExists(a, b, c) =>
|
||||
OptionExists(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case other =>
|
||||
super.apply(other)
|
||||
}
|
||||
|
||||
override def apply(e: Assignment): Assignment =
|
||||
e match {
|
||||
case Assignment(alias, prop, value) =>
|
||||
val t = BetaReduction(replacements - alias)
|
||||
Assignment(alias, t(prop), t(value))
|
||||
}
|
||||
|
||||
override def apply(query: Query): Query =
|
||||
query match {
|
||||
case Filter(a, b, c) =>
|
||||
Filter(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case Map(a, b, c) =>
|
||||
Map(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case FlatMap(a, b, c) =>
|
||||
FlatMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case ConcatMap(a, b, c) =>
|
||||
ConcatMap(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case SortBy(a, b, c, d) =>
|
||||
SortBy(apply(a), b, BetaReduction(replacements - b)(c), d)
|
||||
case GroupBy(a, b, c) =>
|
||||
GroupBy(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case Join(t, a, b, iA, iB, on) =>
|
||||
Join(
|
||||
t,
|
||||
apply(a),
|
||||
apply(b),
|
||||
iA,
|
||||
iB,
|
||||
BetaReduction(replacements - iA - iB)(on)
|
||||
)
|
||||
case FlatJoin(t, a, iA, on) =>
|
||||
FlatJoin(t, apply(a), iA, BetaReduction(replacements - iA)(on))
|
||||
case DistinctOn(a, b, c) =>
|
||||
DistinctOn(apply(a), b, BetaReduction(replacements - b)(c))
|
||||
case _: Take | _: Entity | _: Drop | _: Union | _: UnionAll |
|
||||
_: Aggregation | _: Distinct | _: Nested =>
|
||||
super.apply(query)
|
||||
}
|
||||
}
|
||||
|
||||
object BetaReduction {
|
||||
|
||||
def apply(ast: Ast, t: (Ast, Ast)*): Ast =
|
||||
apply(ast, Replacements(t.toMap))
|
||||
|
||||
def apply(ast: Ast, replacements: Replacements): Ast =
|
||||
BetaReduction(replacements).apply(ast) match {
|
||||
case `ast` => ast
|
||||
case other => apply(other, Replacements.empty)
|
||||
}
|
||||
}
|
52
src/main/scala/minisql/util/Replacements.scala
Normal file
52
src/main/scala/minisql/util/Replacements.scala
Normal file
|
@ -0,0 +1,52 @@
|
|||
package minisql.util
|
||||
|
||||
import minisql.ast.Ast
|
||||
import scala.collection.immutable.Map
|
||||
|
||||
/**
|
||||
* When doing beta reductions, the Opinions of AST elements need to be set to
|
||||
* their neutral positions.
|
||||
*
|
||||
* For example, the `Property` AST element has a field called `renameable` which
|
||||
* dicatates whether to use a `NamingStrategy` during tokenization in `SqlIdiom`
|
||||
* (and other idioms) or not. Since this property only does things after
|
||||
* Normalization, it should be completely transparent to beta reduction (all AST
|
||||
* Opinion's have the same behavior). This is why we need to automatically set
|
||||
* the `renameable` field to a pre-defined value every time `Property` is looked
|
||||
* up. This is done via the `Ast.neutralize` method.
|
||||
*/
|
||||
case class Replacements(map: collection.Map[Ast, Ast]) {
|
||||
|
||||
/** First transformed object to meet criteria * */
|
||||
def apply(key: Ast): Ast =
|
||||
map.map { case (k, v) => (k.neutralize, v) }
|
||||
.filter(_._1 == key.neutralize)
|
||||
.head
|
||||
._2
|
||||
|
||||
/** First transformed object to meet criteria or none of none meets * */
|
||||
def get(key: Ast): Option[Ast] =
|
||||
map.map { case (k, v) => (k.neutralize, v) }
|
||||
.filter(_._1 == key.neutralize)
|
||||
.headOption
|
||||
.map(_._2)
|
||||
|
||||
/** Does the map contain a normalized version of the view you want to see */
|
||||
def contains(key: Ast): Boolean =
|
||||
map.map { case (k, v) => k.neutralize }.toList.contains(key.neutralize)
|
||||
|
||||
def ++(otherMap: collection.Map[Ast, Ast]): Replacements =
|
||||
Replacements(map ++ otherMap)
|
||||
|
||||
def -(key: Ast): Replacements = {
|
||||
val newMap = map.toList.filterNot {
|
||||
case (k, v) => k.neutralize == key.neutralize
|
||||
}.toMap
|
||||
Replacements(newMap)
|
||||
}
|
||||
}
|
||||
|
||||
object Replacements {
|
||||
def empty: Replacements =
|
||||
Replacements(Map())
|
||||
}
|
Loading…
Reference in a new issue