module ActiveFacts module CQL grammar FactTypes rule named_fact_type s name:id ( s '=' s / defined_as / s is s where ) anonymous_fact_type { def value f = anonymous_fact_type.value f[0] = name.text_value f end } end rule anonymous_fact_type f0:fact_clause ftail:( (',' / and ) s f1:fact_clause s )* ctail:( (':' / where) s c:conditions s)? returning_clause? s ';' s { def value [ nil, # Anonymous fact type [ :fact_type, [ f0.body, *ftail.elements.map{|e| e.f1.body } ], !ctail.empty? ? ctail.c.condition_list : [] ] ] end } end rule returning_clause returning return (',' return)* end rule return by order 'REVISIT: return' end rule conditions head:condition s tail:( (',' s / and S) next:condition s )* { def condition_list [head.value] + tail.elements.map{|i| i.next.value} end } end rule condition head:clause s # tail:(or S alternate:clause s )* { def value # if tail.elements.size == 0 head.clause # else # [:"||", head.clause] + tail.elements.map{|i| i.alternate.clause} # end end } end rule clause (comparison / fact_clause) { def clause self.body end } end rule fact_clause s q:qualifier? s reading s p:post_qualifiers? s { def body [ :fact_clause, (q.empty? ? [] : [ q.text_value ]) + (p.empty? ? [] : p.list), reading.value ] end } end rule qualifier maybe / definitely end rule post_qualifiers '[' s q0:post_qualifier tail:( s ',' s q1:post_qualifier )* s ']' s { def list [q0.text_value] + tail.elements.map{|e| e.q1.text_value} end } end rule post_qualifier static / transient / intransitive / transitive / acyclic / symmetric end rule reading subtype_invocation / role+ { def value elements.map{|r| r.value} end } end # REVISIT: This allows invocation from subtype to supertype. We need the reverse as well (Employee is a Manager). # Now that subtyping fact types have readings created during compilation, perhaps these custom rules can be removed? rule subtype_invocation (('some'/'that') S)? subtype:id s subtype_prefix (('some'/'that') S)? supertype:id { def value [{:subtype => subtype.text_value, :supertype => supertype.text_value }] # [subtype.text_value, "is", "a", "subtype", "of", supertype.text_value].map{|w| {:word => w}} end } end # This is the rule that causes most back-tracking. I think you can see why. # When we have an expression, we will come down here perhaps multiple times, # but find no way out as soon as we hit the trailing non_role. rule role q:quantifier? adj0:(a:role_word '-' s)? player:role_word !'-' s? adj1:( '-' a:(a:role_word s)? )? func:function_call? role_name:( '(' s as S r:id s ')' s )? lr:( literal / restriction )? !non_role { def value r = {} quantifier = !q.empty? && q.value # "some" quantifier has nil value r[:quantifier] = quantifier if quantifier r[:leading_adjective] = adj0.a.text_value unless adj0.empty? r[:word] = player.text_value r[:trailing_adjective] = adj1.a.a.text_value unless adj1.empty? r[:function] = func.value if !func.empty? r[:role_name] = role_name.r.text_value unless role_name.empty? r[:restriction] = lr.ranges if !lr.empty? && lr.respond_to?(:ranges) r[:literal] = lr.value if !lr.empty? && lr.respond_to?(:value) r end } end rule non_role # Any of these is illegal in or following a reading: comparator / add_op / mul_op end rule role_word !non_role_word id end rule non_role_word # These words are illegal in (but maybe ok following) a reading where a role word is expected: and / if / only / or / quantifier / restriction end end end end