lib/activefacts/cql/compiler/expression.rb in activefacts-0.8.16 vs lib/activefacts/cql/compiler/expression.rb in activefacts-0.8.18

- old
+ new

@@ -2,23 +2,23 @@ module CQL class Compiler # An Operation is a binary or ternary fact type involving an operator, # a result, and one or two operands. - # Viewed as a result, it behaves like a VarRef with a nested Clause. + # Viewed as a result, it behaves like a Reference with a nested Clause. # Viewed as a fact type, it behaves like a Clause. # # The only exception here is an equality comparison, where it may # turn out that the equality is merely a projection. In this case # the Operation is dropped from the clauses and is replaced by the # projected operand. # - # Each operand may be a Literal, a VarRef, or another Operation, - # so we need to recurse down the tree to build the join. + # Each operand may be a Literal, a Reference, or another Operation, + # so we need to recurse down the tree to build the query. # class Operation - # VarRef (in)compatibility: + # Reference (in)compatibility: [ :term, :leading_adjective, :trailing_adjective, :role_name, :quantifier, :value_constraint, :embedded_presence_constraint, :literal ].each do |s| define_method(s) { raise "Unexpected call to Operation\##{s}" } define_method(:"#{s}=") { raise "Unexpected call to Operation\##{s}=" } @@ -26,103 +26,115 @@ def role_name; nil; end def leading_adjective; nil; end def trailing_adjective; nil; end def value_constraint; nil; end def literal; nil; end - attr_accessor :player # What ObjectType does the Variable denote - attr_accessor :variable # What Variable for that ObjectType + def side_effects; nil; end + attr_accessor :player # What ObjectType does the Binding denote + attr_accessor :binding # What Binding for that ObjectType attr_accessor :clause # What clause does the result participate in? attr_accessor :role # Which Role of this ObjectType attr_accessor :role_ref # Which RoleRef to that Role + attr_accessor :certainty # nil, true, false -> maybe, definitely, not def nested_clauses; @nested_clauses ||= [self]; end def clause; self; end def objectification_of; @fact_type; end - # Clause (in)compatibility: [ :phrases, :qualifiers, :context_note, :reading, :role_sequence, :fact ].each do |s| define_method(s) { raise "Unexpected call to Operation\##{s}" } define_method(:"#{s}=") { raise "Unexpected call to Operation\##{s}=" } end def conjunction; nil; end attr_reader :fact_type - def objectified_as; self; end # The VarRef which objectified this fact type + def objectified_as; self; end # The Reference which objectified this fact type + def initialize + @certainty = true # Assume it's definite + end + def operands context = nil raise "REVISIT: Implement operand enumeration in the operator subclass #{self.class.name}" end def identify_players_with_role_name context # Just recurse, there's no way (yet: REVISIT?) to add a role name to the result of an expression - var_refs.each { |o| + refs.each { |o| o.identify_players_with_role_name(context) } # As yet, an operation cannot have a role name: # identify_player context if role_name end def identify_other_players context # Just recurse, there's no way (yet: REVISIT?) to add a role name to the result of an expression - var_refs.each { |o| + refs.each { |o| o.identify_other_players(context) } identify_player context end def bind context - var_refs.each do |o| + refs.each do |o| o.bind context end name = result_type_name(context) @player = result_value_type(context, name) - key = "#{name} #{object_id}" # Every Operation result is a unique Variable - @variable = (context.variables[key] ||= Variable.new(@player)) - @variable.refs << self - @variable + key = "#{name} #{object_id}" # Every Operation result is a unique Binding + @binding = (context.bindings[key] ||= Binding.new(@player)) + @binding.refs << self + @binding end def result_type_name(context) raise "REVISIT: Implement result_type_name in the #{self.class.name} subclass" end def result_value_type(context, name) vocabulary = context.vocabulary constellation = vocabulary.constellation - constellation.ValueType(vocabulary, name, :guid => :new) + vocabulary.valid_value_type_name(name) || + constellation.ValueType(vocabulary, name, :guid => :new) end def is_naked_object_type false # All Operations are non-naked end def match_existing_fact_type context - opnds = var_refs - result_var_ref = VarRef.new(@variable.player.name) - result_var_ref.player = @variable.player - result_var_ref.variable = @variable - @variable.refs << result_var_ref + opnds = refs + result_ref = Reference.new(@binding.player.name) + result_ref.player = @binding.player + result_ref.binding = @binding + @binding.refs << result_ref clause_ast = Clause.new( - [result_var_ref, '='] + + [result_ref, '='] + (opnds.size > 1 ? [opnds[0]] : []) + [operator, opnds[-1]] ) # REVISIT: All operands must be value-types or simply-identified Entity Types. - # REVISIT: We should auto-create joins from Entity Types to an identifying ValueType + # REVISIT: We should auto-create steps from Entity Types to an identifying ValueType # REVISIT: We should traverse up the supertype of ValueTypes to find a DataType @fact_type = clause_ast.match_existing_fact_type(context, :exact_type => true) + if clause.certainty == false + raise "Negated fact types in expressions are not yet supported: #{clause.inspect}" + end return @fact_type if @fact_type @fact_type = clause_ast.make_fact_type context.vocabulary reading = clause_ast.make_reading context.vocabulary, @fact_type rrs = reading.role_sequence.all_role_ref_in_order opnds[0].role_ref = rrs[0] opnds[-1].role_ref = rrs[-1] opnds.each do |opnd| next unless opnd.is_a?(Operation) opnd.match_existing_fact_type context + if opnd.certainty == false + raise "Negated fact types in expressions are not yet supported: #{opnd.inspect}" + end end @fact_type end def is_existential_type @@ -140,32 +152,32 @@ end class Comparison < Operation attr_accessor :operator, :e1, :e2, :qualifiers, :conjunction - def initialize operator, e1, e2, qualifiers = [] - @operator, @e1, @e2, @qualifiers = operator, e1, e2, qualifiers + def initialize operator, e1, e2, certainty = true + @operator, @e1, @e2, @certainty, @qualifiers = operator, e1, e2, certainty, [] end - def var_refs + def refs [@e1, @e2] end def bind context - var_refs.each do |o| + refs.each do |o| o.bind context end # REVISIT: Return the projected binding instead: return @result = nil if @projection name = 'Boolean' @player = result_value_type(context, name) - key = "#{name} #{object_id}" # Every Comparison result is a unique Variable - @variable = (context.variables[key] ||= Variable.new(@player)) - @variable.refs << self - @variable + key = "#{name} #{object_id}" # Every Comparison result is a unique Binding + @binding = (context.bindings[key] ||= Binding.new(@player)) + @binding.refs << self + @binding end def result_type_name(context) "compare#{operator}(#{[@e1,@e2].map{|e| e.player.name}*', '}))" end @@ -178,11 +190,14 @@ @player || begin if @projection raise "REVISIT: The player is the projected expression" end v = context.vocabulary - @player = v.constellation.ValueType(v, 'Boolean', :guid => :new) + @boolean ||= + v.constellation.ValueType[[[v.name], 'Boolean']] || + v.constellation.ValueType(v, 'Boolean', :guid => :new) + @player = @boolean end end =begin def project lr @@ -193,21 +208,35 @@ =end def inspect; to_s; end def to_s - "compare#{operator}(#{e1.to_s} #{e2.to_s}#{@qualifiers.empty? ? '' : ', ['+@qualifiers*', '+']'})" + "compare#{ + operator + }(#{ + case @certainty + when nil; 'maybe ' + when false; 'negated ' + # else 'definitely ' + end + }#{ + e1.to_s + } #{ + e2.to_s + }#{ + @qualifiers.empty? ? '' : ', ['+@qualifiers*', '+']' + })" end end class Sum < Operation attr_accessor :terms def initialize *terms @terms = terms end - def var_refs + def refs @terms end def operator '+' @@ -248,11 +277,11 @@ attr_accessor :factors def initialize *factors @factors = factors end - def var_refs + def refs @factors end def operator '*' @@ -296,11 +325,11 @@ def operator '1/' end - def var_refs + def refs [@divisor] end def identify_player context @player || begin @@ -394,23 +423,23 @@ when Float; 'Real' when Numeric; 'Integer' when TrueClass, FalseClass; 'Boolean' end v = context.vocabulary - @player = v.constellation.ValueType(v, player_name, :guid => :new) + @player = v.constellation.ValueType(v, player_name) end end def bind context - @variable || begin + @binding || begin key = "#{@player.name} #{@literal}" - @variable = (context.variables[key] ||= Variable.new(@player)) - @variable.refs << self + @binding = (context.bindings[key] ||= Binding.new(@player)) + @binding.refs << self end end - def variable - @variable + def binding + @binding end end end end