Skip to content

Instantly share code, notes, and snippets.

@OlegIlyenko
Created June 25, 2014 23:43
Show Gist options
  • Save OlegIlyenko/c452a5692a61336d87b1 to your computer and use it in GitHub Desktop.
Save OlegIlyenko/c452a5692a61336d87b1 to your computer and use it in GitHub Desktop.
Parboiled2 Predicate Parser
import scala.annotation.switch
import org.parboiled2._
import scala.io.Source
import scala.util.{Failure, Success}
class PredicateParser(val input: ParserInput) extends Parser with CommonRules {
def And = Keyword("and")
def Or = Keyword("or")
def Not = Keyword("not")
def Predicate = rule { WhiteSpace ~ Expression ~ EOI }
def Expression: Rule1[AstNode] = rule { Term ~ zeroOrMore(Or ~ Term ~> AstNode.Or.apply _) }
def Term: Rule1[AstNode] = rule { Factor ~ zeroOrMore(And ~ Factor ~> AstNode.And.apply _) }
def Factor = rule { Not ~ Primary ~> AstNode.Not.apply _ | Primary }
def Primary = rule {
IntLiteral ~> AstNode.IntLiteral.apply _ |
StringLiteral ~> AstNode.StringLiteral.apply _ |
Identifier ~> AstNode.Identifier.apply _ |
Parens
}
def Parens = rule { ws('(') ~ Expression ~ ws(')') }
}
trait CommonRules extends StringBuilding {
this: Parser =>
import CharPredicate.{HexDigit, Digit}
def ws(char: Char): Rule0 = rule { ch(char) ~ WhiteSpace }
def ws(s: String): Rule0 = rule { str(s) ~ WhiteSpace }
def Keyword(s: String) = rule { ignoreCase(s) ~ WhiteSpace }
def IntLiteral = rule { capture(Digits) ~> (_.trim.toInt) }
def StringLiteral = rule { '"' ~ clearSB() ~ Characters ~ ws('"') ~ push(sb.toString) }
def Characters = rule { zeroOrMore(NormalChar | '\\' ~ EscapedChar) }
def NormalChar = rule { !QuoteBackslash ~ ANY ~ appendSB() }
def EscapedChar = rule {
QuoteSlashBackSlash ~ appendSB() |
'b' ~ appendSB('\b') |
'f' ~ appendSB('\f') |
'n' ~ appendSB('\n') |
'r' ~ appendSB('\r') |
't' ~ appendSB('\t') |
Unicode ~> { code => sb.append(code.asInstanceOf[Char]); () }
}
def Unicode = rule { 'u' ~ capture(4 times HexDigit) ~> (Integer.parseInt(_, 16)) }
def Digits = rule { oneOrMore(CharPredicate.Digit) ~ WhiteSpace }
def WhiteSpace = rule { zeroOrMore(WhiteSpaceChar) }
def Identifier = rule { capture(IdentifierFirstChar ~ zeroOrMore(IdentifierChar)) ~ WhiteSpace }
val WhiteSpaceChar = CharPredicate(" \n\r\t\f")
val IdentifierFirstChar = CharPredicate.Alpha ++ '_'
val IdentifierChar = IdentifierFirstChar ++ CharPredicate.Digit
val QuoteBackslash = CharPredicate("\"\\")
val QuoteSlashBackSlash = QuoteBackslash ++ "/"
}
sealed trait AstNode
object AstNode {
case class And(left: AstNode, right: AstNode) extends AstNode
case class Or(left: AstNode, right: AstNode) extends AstNode
case class Not(node: AstNode) extends AstNode
case class StringLiteral(string: String) extends AstNode
case class IntLiteral(num: Int) extends AstNode
case class Identifier(name: String) extends AstNode
}
object Parser extends App {
new PredicateParser(Source.fromInputStream(getClass.getResourceAsStream("foo.txt")).mkString)
.Predicate.run() match {
case Success(node) => println(node)
case Failure(e: ParseError) =>
println(e.formatExpectedAsString)
println(e.position)
println(e.formatTraces)
case Failure(e) =>
throw e
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment