Skip to content

Instantly share code, notes, and snippets.

@anthonynsimon
Last active June 22, 2018 22:59
Show Gist options
  • Save anthonynsimon/2ca44fbb5fd82fca7051df779d1586b5 to your computer and use it in GitHub Desktop.
Save anthonynsimon/2ca44fbb5fd82fca7051df779d1586b5 to your computer and use it in GitHub Desktop.
BooleanQueryDSL.scala
package com.anthonynsimon.boolquery
sealed trait BooleanQuery[A]
case class Pure[A](a: A) extends BooleanQuery[A]
case class And[A](x: BooleanQuery[A], y: BooleanQuery[A]) extends BooleanQuery[A]
case class Or[A](x: BooleanQuery[A], y: BooleanQuery[A]) extends BooleanQuery[A]
case class Not[A](x: BooleanQuery[A], y: BooleanQuery[A]) extends BooleanQuery[A]
trait Combinators[A] {
def and(a: A, b: A): A
def or(a: A, b: A): A
def not(a: A, b: A): A
}
object BooleanQueryInterpreter {
def apply[A](query: BooleanQuery[A])(implicit combinators: Combinators[A]): A = {
query match {
case Pure(a) => a
case And(x, y) => combinators.and(apply(x), apply(y))
case Or(x, y) => combinators.or(apply(x), apply(y))
case Not(x, y) => combinators.not(apply(x), apply(y))
}
}
}
object BoolCombinators extends Combinators[Boolean] {
override def and(a: Boolean, b: Boolean): Boolean = a && b
override def or(a: Boolean, b: Boolean): Boolean = a || b
override def not(a: Boolean, b: Boolean): Boolean = a && (!b)
}
class SeqCombinators[A] extends Combinators[Seq[A]] {
override def and(a: Seq[A], b: Seq[A]): Seq[A] = a.intersect(b)
override def or(a: Seq[A], b: Seq[A]): Seq[A] = a.union(b).distinct
override def not(a: Seq[A], b: Seq[A]): Seq[A] = a.diff(b)
}
class TermQueryCombinators[A] extends Combinators[TermQueryResult[A]] {
override def and(a: TermQueryResult[A], b: TermQueryResult[A]): TermQueryResult[A] =
TermQueryResult(s"(${a.str} AND ${b.str})", a.result.intersect(b.result))
override def or(a: TermQueryResult[A], b: TermQueryResult[A]): TermQueryResult[A] =
TermQueryResult(s"(${a.str} OR ${b.str})", a.result.union(b.result))
override def not(a: TermQueryResult[A], b: TermQueryResult[A]): TermQueryResult[A] =
TermQueryResult(s"(${a.str} NOT ${b.str})", a.result.diff(b.result))
}
case class TermQueryResult[A](str: String, result: Seq[A])
object BooleanQueryParser {
sealed trait Token
case object OpenParen extends Token
case object CloseParen extends Token
case class Op(value: String) extends Token
case class Term(value: String) extends Token
def apply(query: String): BooleanQuery[String] = {
val tokens = query.replace("\n", " ").replace("(", "( ").replace(")", " )").split(" ").map(_.toLowerCase()).map {
case "(" => OpenParen
case ")" => CloseParen
case op if Seq("and", "or", "not").contains(op) => Op(op)
case term => Term(term)
}
tokens.foreach(println)
def walk(current: Int, tokens: Seq[Token]): BooleanQuery[String] = {
tokens(current) match {
case OpenParen => walk(current + 1, tokens)
case Term(value) => Pure(value)
case Op(op) => op match {
case "and" => And(walk(current - 1, tokens), walk(current + 1, tokens))
case "or" => Or(walk(current - 1, tokens), walk(current + 1, tokens))
case "not" => Not(walk(current - 1, tokens), walk(current + 1, tokens))
}
case CloseParen => walk(current + 1, tokens)
}
}
walk(0, tokens)
}
}
object Example {
implicit def termQueryCombinators[A]: TermQueryCombinators[A] = new TermQueryCombinators[A]
def main(args: Array[String]): Unit = {
// val query: BooleanQuery[TermQuery[Int]] = And(Not(Pure(TermQuery("hello", Seq(1, 2))), Pure(TermQuery("there", Seq(2, 3, 4)))), Pure(TermQuery("people", Seq(1, 2, 7))))
//
// val result = BooleanQueryInterpreter(query)
//
val result = BooleanQueryParser("(hello NOT there) AND people")
println(result)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment