This commit is contained in:
jilen 2024-12-06 19:25:54 +08:00
parent 26572ffa0d
commit bd1dbaa8e2
6 changed files with 267 additions and 21 deletions

View 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] = {
???
}

View file

@ -2,7 +2,7 @@ package minisql.ast
import minisql.NamingStrategy import minisql.NamingStrategy
import scala.quoted._ import scala.quoted.*
sealed trait Ast { sealed trait Ast {
@ -20,7 +20,6 @@ sealed trait Ast {
override def apply(a: Ast) = override def apply(a: Ast) =
super.apply(a.neutral) super.apply(a.neutral)
}.apply(this) }.apply(this)
} }
//************************************************************ //************************************************************
@ -45,7 +44,8 @@ case class Entity(
properties: List[PropertyAlias], properties: List[PropertyAlias],
renameable: Renameable renameable: Renameable
) extends Query { ) extends Query {
override def neutral: Entity = Entity(name, properties, Renameable.neutral) override inline def neutral: Entity =
Entity(name, properties, Renameable.neutral)
} }
object Entity { object Entity {
@ -182,7 +182,7 @@ case class ExternalIdent(name: String) extends Ast
*/ */
sealed trait Opinion[T] sealed trait Opinion[T]
sealed trait OpinionValues[T <: Opinion[T]] { sealed trait OpinionValues[T <: Opinion[T]] {
def neutral: T inline def neutral: T
} }
sealed trait Visibility extends Opinion[Visibility] sealed trait Visibility extends Opinion[Visibility]
@ -226,7 +226,7 @@ case class Property(
visibility: Visibility visibility: Visibility
) extends Ast { ) extends Ast {
override def neutral: Property = override inline def neutral: Property =
copy(renameable = Renameable.neutral, visibility = Visibility.neutral) copy(renameable = Renameable.neutral, visibility = Visibility.neutral)
} }
@ -374,13 +374,6 @@ sealed trait Lift extends Ast {
val liftId: String 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 sealed trait ScalarLift extends Lift
case class ScalarValueLift( case class ScalarValueLift(

View file

@ -1,6 +1,7 @@
package minisql.ast package minisql.ast
import scala.quoted._ import minisql.util.*
import scala.quoted.*
private given FromExpr[PropertyAlias] with { private given FromExpr[PropertyAlias] with {
def unapply(x: Expr[PropertyAlias])(using Quotes): Option[PropertyAlias] = 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] = { def unapply(e: Expr[Ast])(using Quotes): Option[Ast] = {
val et = extractTerm(e.toTerm) val et = extractTerm(e.toTerm)
et match { 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 b: quotes.reflect.Ident => Some(Ident(b.name))
case o => case o =>
o.asExpr match { o.asExpr match {

View file

@ -11,9 +11,10 @@ sealed trait Dsl {
trait Query[E] extends 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]) { extension [E](inline e: EntityQuery[E]) {
inline def mapAst[E1](inline f: Ast => Ast): EntityQuery[E1] = inline def mapAst[E1](inline f: Ast => Ast): EntityQuery[E1] =
EntityQuery[E1](f(e.ast)) 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"))
)

View 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)
}
}

View 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())
}