Last active
June 22, 2018 22:59
-
-
Save anthonynsimon/2ca44fbb5fd82fca7051df779d1586b5 to your computer and use it in GitHub Desktop.
BooleanQueryDSL.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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