ProtoQuill allows you to define custom parsing logic so that regular Scala methods (not just quoted ones) can be used within quote blocks. This prevents the need to define duplicate logic for both Scala and Quill.
Workflow:
- Define Business Logic: Create an object containing the methods you want to use. Ensure the implementation is not
inline in this object. - Define a Parser: Implement a
Parser.Clause[Ast] that intercepts the specific syntax (e.g., an infix operator) and translates it into a Quill Ast (like Infix). - Register the Parser: Create a
ParserLibrary that includes your custom parser in the operationsParser series. - Use in Application: Define your custom parser as a
given in your application code to enable the syntax within quote blocks.
Important: The parser definition must reside in a separate compilation unit (e.g., a separate SBT project) that is compiled before your main application code.
// 1. Define business logic (NOT inline)
object MyBusinessLogic:
extension (i: Int)
def **(exponent: Int) = Math.pow(i, exponent)
// 2. Define the parser (in a separate compilation unit)
import io.getquill.parser._
import io.getquill.ast.{ Ast, Infix }
import io.getquill.quat.Quat
case class CustomOperationsParser(root: Parser[Ast] = Parser.empty)(override implicit val qctx: Quotes) extends Parser.Clause[Ast] {
import quotes.reflect._
import CustomOps._
def reparent(newRoot: Parser[Ast]) = this.copy(root = newRoot)
def delegate: PartialFunction[Expr[_], Ast] =
case '{ ($i: Int)**($j: Int) } =>
Infix(
List("power(", " ,", ")"),
List(astParse(i), astParse(j)), true, Quat.Value)
}
object CustomParser extends ParserLibrary:
import Parser._
override def operationsParser(using qctx: Quotes) =
Series.of(new OperationsParser, new CustomOperationsParser)
// 3. Use in application
given myParser: CustomParser.type = CustomParser
import MyBusinessLogic._
inline def q = quote { query[Person].map(p => p.age ** 2) }