Skip to content

Instantly share code, notes, and snippets.

@arekinath
Created March 11, 2012 01:16
Show Gist options
  • Save arekinath/2014420 to your computer and use it in GitHub Desktop.
Save arekinath/2014420 to your computer and use it in GitHub Desktop.
grammar MessageQuery
rule expr
first:cond exprpart* <Expression>
end
rule exprpart
space* op:('&&' / '||') space* second:cond <ExpressionPart>
end
rule cond
lhs:primary space* op:operator space* rhs:primary <Condition> / '(' ex:expr ')' <NestedExpression>
end
rule primary
path / string / int
end
rule path
base:( [a-zA-Z] [a-zA-Z0-9_\?\!]* ) ( '.' nxt:path )? <DotPath>
end
rule string
['"] [^'"]+ ['"] {
def compile
text_value
end
}
end
rule int
'-'? [1-9] [0-9]* ( '.' [0-9]+ )? {
def compile
text_value
end
}
end
rule operator
('==' / '<' / '>' / '<=' / '>=' / '!=') {
def compile
text_value
end
}
end
rule space
' ' / "\t" / "\n"
end
end
require 'treetop'
module MessageQuery
Parser = Treetop.load 'messagequery'
module Expression
def compile
"(lambda {|ce| " + first.compile + " " + elements[1].elements.map { |e| e.compile }.join(" ") + "})"
end
end
module ExpressionPart
def compile
ophash = {"&&" => "and", "||" => "or"}
ophash[op.text_value] + " " + second.compile
end
end
module NestedExpression
def compile
"(" + ex.compile + ").call(ce)"
end
end
module Condition
def compile
"(" + lhs.compile + " " + op.compile + " " + rhs.compile + ")"
end
end
module DotPath
def self.resolve(object, sym)
if object.kind_of?(Hash)
object[sym.to_s]
else
attre = (object.respond_to?(:attribute_get) ? object.attribute_get(sym) : nil)
if attre
attre
else
rels = (object.class.respond_to?(:relationships) ? object.class.relationships : [])
if rels.select{|r| r.name.to_sym == sym}.size > 0
object.send(sym)
else
wl = (object.class.respond_to?(:query_whitelist) ? object.class.query_whitelist : [])
if wl.include?(sym)
object.send(sym)
else
throw Exception.new("Access violation on method :#{sym}")
end
end
end
end
end
def parts(pts=[])
nxt = (elements[1].respond_to?(:nxt) ? elements[1].nxt : nil)
pts = pts + [base.text_value]
if nxt and nxt.kind_of?(DotPath)
nxt.parts(pts)
else
pts
end
end
def compile(top=true, target="")
nxt = (elements[1].respond_to?(:nxt) ? elements[1].nxt : nil)
if nxt.kind_of?(DotPath)
out = "("
pts = nxt.parts
st = "ce.targets[:#{base.text_value}]"
pts.each do |pt|
st = "MessageQuery::DotPath.resolve(#{st}, :#{pt})"
end
out += st
out += ")"
else
out = "(ce.targets[:#{base.text_value}])"
end
end
end
end
code = "blah == 3 && (foo.bar < 2 || test.foo.x == 9)"
class Foo
def self.query_whitelist; [:bar]; end
def bar
1.0
end
end
class Test
def targets
{:blah => 3, :foo => Foo.new, :test => {:foo => {:x => 10 }}}
end
end
parser = MessageQuery::Parser.new
tree = parser.parse(code)
if tree.nil?
puts parser.failure_reason.inspect
else
c = tree.compile
puts c
l = eval(c)
foo = Test.new
puts l.call(foo).inspect
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment