lib/activefacts/cql/compiler/fact.rb in activefacts-0.8.8 vs lib/activefacts/cql/compiler/fact.rb in activefacts-0.8.9

- old
+ new

@@ -13,16 +13,16 @@ @context = CompilationContext.new(@vocabulary) @readings.each{ |reading| reading.identify_players_with_role_name(@context) } @readings.each{ |reading| reading.identify_other_players(@context) } @readings.each{ |reading| reading.bind_roles @context } + @readings.each{ |reading| reading.match_existing_fact_type @context } # Figure out the simple existential facts and find fact types: - @bound_instances = {} # Instances indexed by binding - @bound_fact_types = [] @bound_facts = [] - @unbound_readings = @readings. + @ojr_by_role_ref = {} + @unbound_readings = all_readings. map do |reading| bind_literal_or_fact_type reading end. compact @@ -41,104 +41,164 @@ # Every bound word (term) in the phrases must have a literal # OR be bound to an entity type identified by the phrases # Any clause that has one binding and no other word is # either a value instance or a simply-identified entity. - reading.role_refs.map do |role_ref| - next role_ref unless l = role_ref.literal + reading.role_refs.each do |role_ref| + next unless l = role_ref.literal # No literal + next if role_ref.binding.instance # Already bound player = role_ref.binding.player # raise "A literal may not be an objectification" if role_ref.role_ref.objectification_join # raise "Not processing facts involving objectification joins yet" if role_ref.role_ref debug :instance, "Making #{player.class.basename} #{player.name} using #{l.inspect}" do - @bound_instances[role_ref.binding] = - instance_identified_by_literal player, l + role_ref.binding.instance = instance_identified_by_literal(player, l) end role_ref end - if reading.phrases.size == 1 && (role_ref = reading.phrases[0]).is_a?(Compiler::RoleRef) - # This is an existential fact (like "Name 'foo'", or "Company 'Microsoft'") - # @bound_instances[role_ref.binding] - nil # Nothing to see here, move along + if reading.phrases.size == 1 and (role_ref = reading.phrases[0]).is_a?(Compiler::RoleRef) + if role_ref.objectification_join + # Assign the objectified fact type as this reading's fact type? + reading.fact_type = role_ref.player.fact_type + reading + else + # This is an existential fact (like "Name 'foo'", or "Company 'Microsoft'") + nil # Nothing to see here, move along + end else - @bound_fact_types << reading.match_existing_fact_type(@context) + raise "Fact Type not found: '#{reading.display}'" unless reading.fact_type + # This instance will be associated with its binding by our caller reading end end + # + # Try to bind this reading, and return true if it can be completed + # + def bind_reading reading + return true if reading.fact + + # Find the roles of this reading that do not yet have an instance + bare_roles = reading.role_refs. + select do |role_ref| + next false if role_ref.binding.instance + next false if role_ref.literal and + role_ref.binding.instance = instance_identified_by_literal(role_ref.binding.player, role_ref.literal) + true + end + + debug :instance, "Considering '#{reading.display}' with "+ + (bare_roles.empty? ? "no bare roles" : "bare roles: #{bare_roles.map{|role_ref| role_ref.player.name}*", "}") do + + # If all the roles are in place, we can bind the rest of this reading: + return true if bare_roles.size == 0 && bind_complete_fact(reading) + + progress = false + if bare_roles.size == 1 && + (binding = bare_roles[0].binding) && + (et = binding.player).is_a?(ActiveFacts::Metamodel::EntityType) + if et.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type == reading.fact_type} && + bind_entity_if_identifier_ready(reading, et, binding) + progress = true + end + end + + return true if progress + debug :instance, "Can't make progress on '#{reading.display}'" + nil + end + end + # Take one pass through the @unbound_readings, processing (and removing) any that have all pre-requisites def bind_more_facts + return false unless @unbound_readings.size > 0 @pass += 1 progress = false debug :instance, "Pass #{@pass} with #{@unbound_readings.size} readings to consider" do - @unbound_readings.map! do |reading| - # See if we can create the fact instance for this reading yet - - # Find the roles of this reading that do not yet have an entry in @bound_instances: - bare_roles = reading.role_refs. - select do |role_ref| - !role_ref.literal && !@bound_instances[role_ref.binding] - end - - debug :instance, "Considering '#{reading.fact_type.preferred_reading.expand}' with bare roles: #{bare_roles.map{|role_ref| role_ref.player.name}*", "} " - - if bare_roles.size == 0 - reading = nil if bind_complete_fact reading - elsif bare_roles.size == 1 && - (binding = bare_roles[0].binding) && - (et = binding.player).is_a?(ActiveFacts::Metamodel::EntityType) && - et.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type == reading.fact_type} - - reading = nil if bind_entity_if_identifier_ready reading, et, binding + @unbound_readings = + @unbound_readings.select do |reading| + action = bind_reading(reading) + progress = true if action + !action end - progress = true unless reading - reading - end - @unbound_readings.compact! + debug :instance, "end of pass, unbound readings are #{@unbound_readings.map(&:display)*', '}" end # debug progress end + # Occasionally we need to search through all the readings: + def all_readings + @readings.map do |reading| + [reading] + reading.role_refs.map{|rr| rr.objectification_join} + end.flatten.compact + end + def bind_complete_fact reading - debug :instance, "All bindings in '#{reading.fact_type.preferred_reading.expand}' contain instances; create the fact type" - instances = reading.role_refs.map{|rr| @bound_instances[rr.binding]} + return true unless reading.fact_type # An bare objectification + debug :instance, "All bindings in '#{reading.display}' contain instances; create the fact type" + instances = reading.role_refs.map{|rr| rr.binding.instance} debug :instance, "Instances are #{instances.map{|i| "#{i.concept.name} #{i.value.inspect}"}*", "}" - # Check that this fact doesn't already exist - fact = reading.fact_type.all_fact.detect{|f| - # Get the role values of this fact in the order of the reading we just bound - role_values_in_reading_order = f.all_role_value.sort_by do |rv| - reading.reading.role_sequence.all_role_ref.detect{|rr| rr.role == rv.role}.ordinal + if e = reading.fact_type.entity_type and + reading.role_refs[0].binding.instance.concept == e + fact = reading.role_refs[0].binding.instance.fact + else + # Check that this fact doesn't already exist + fact = reading.fact_type.all_fact.detect do |f| + # Get the role values of this fact in the order of the reading we just bound + role_values_in_reading_order = f.all_role_value.sort_by do |rv| + debugger unless reading.reading + reading.reading.role_sequence.all_role_ref.detect{|rr| rr.role == rv.role}.ordinal + end + # If all this fact's role values are played by the bound instances, it's the same fact + !role_values_in_reading_order.zip(instances).detect{|rv, i| rv.instance != i } end - # If all this fact's role values are played by the bound instances, it's the same fact - !role_values_in_reading_order.zip(instances).detect{|rv, i| rv.instance != i } - } - unless fact - fact = @constellation.Fact(:new, :fact_type => reading.fact_type, :population => @population) + end + if fact + reading.fact = fact + debug :instance, "Found existing fact type instance" + else + fact = + reading.fact = + @constellation.Fact(:new, :fact_type => reading.fact_type, :population => @population) @bound_facts << fact - instance = - @constellation.Instance(:new, :concept => reading.fact_type.entity_type, :fact => fact, :population => @population) - @bound_facts << instance - reading.reading.role_sequence.all_role_ref.zip(instances).each do |rr, instance| + + reading.reading.role_sequence.all_role_ref_in_order.zip(instances).each do |rr, instance| debug :instance, "New fact has #{instance.concept.name} role #{instance.value.inspect}" # REVISIT: Any residual adjectives after the fact type matching are lost here. @constellation.RoleValue(:fact => fact, :instance => instance, :role => rr.role, :population => @population) end - else - debug :instance, "Found existing fact type instance" end + + if !fact.instance && reading.fact_type.entity_type + # Objectified fact type; create the instance + # Create the instance that objectifies this fact. We don't have the binding to assign it to though; that'll happen in our caller + debug :instance, "Objectifying fact as #{reading.fact_type.entity_type.name}" + instance = + @constellation.Instance(:new, :concept => reading.fact_type.entity_type, :fact => fact, :population => @population) + @bound_facts << instance + end + + if reading.fact and + reading.objectified_as and + instance = reading.fact.instance and + instance.concept == reading.objectified_as.binding.player + reading.objectified_as.binding.instance = instance + end + true end # If we have one bare role (no literal or instance) played by an entity type, # and the bound fact type participates in the identifier, we might now be able # to create the entity instance. def bind_entity_if_identifier_ready reading, entity_type, binding # Check this instance doesn't already exist already: identifying_binding = (reading.role_refs.map{|rr| rr.binding}-[binding])[0] - identifying_instance = @bound_instances[identifying_binding] + return false unless identifying_binding # This happens when we have a bare objectification + identifying_instance = identifying_binding.instance preferred_identifier = entity_type.preferred_identifier debug :instance, "This clause associates a new #{binding.player.name} with a #{identifying_binding.player.name}#{identifying_instance ? " which exists" : ""}" identifying_role_ref = preferred_identifier.role_sequence.all_role_ref.detect { |rr| @@ -153,42 +213,50 @@ rv.fact.fact_type == identifying_role_ref.role.fact_type end if role_value instance = (role_value.fact.all_role_value.to_a-[role_value])[0].instance debug :instance, "Found an existing instance (of #{instance.concept.name}) from a previous definition" - @bound_instances[binding] = instance + binding.instance = instance return true # Done with this reading end pi_role_refs = preferred_identifier.role_sequence.all_role_ref # For each pi role, we have to find the fact clause, which contains the binding we need. # Then we have to create an instance of each fact identifiers = pi_role_refs.map do |rr| - fact_a = @readings.detect{|reading| rr.role.fact_type == reading.fact_type} - identifying_role_ref = fact_a.role_refs.select{|role_ref| role_ref.binding != binding}[0] + # Find a reading that provides the identifying_role_ref for this player: + identifying_reading = all_readings.detect do |reading| + rr.role.fact_type == reading.fact_type && + reading.role_refs.detect{|r1| r1.binding == binding} + end + return false unless identifying_reading + identifying_role_ref = identifying_reading.role_refs.select{|role_ref| role_ref.binding != binding}[0] identifying_binding = identifying_role_ref ? identifying_role_ref.binding : nil - identifying_instance = @bound_instances[identifying_binding] + identifying_instance = identifying_binding.instance - [rr, fact_a, identifying_binding, identifying_instance] + [rr, identifying_reading, identifying_binding, identifying_instance] end if identifiers.detect{ |i| !i[3] } # Not all required facts are bound yet debug :instance, "Can't go through with creating #{binding.player.name}; not all the identifying facts are in" return false end - debug :instance, "Going ahead with creating #{binding.player.name} using #{identifiers.size} roles" - instance = @constellation.Instance(:new, :concept => entity_type, :population => @population) - @bound_instances[binding] = instance - @bound_facts << instance - identifiers.each do |rr, fact_a, identifying_binding, identifying_instance| - # This reading provides the identifying literal for the entity_type - id_fact = @constellation.Fact(:new, :fact_type => rr.role.fact_type, :population => @population) - @bound_facts << id_fact - role = (rr.role.fact_type.all_role.to_a-[rr.role])[0] - @constellation.RoleValue(:instance => instance, :fact => id_fact, :population => @population, :role => role) - @constellation.RoleValue(:instance => identifying_instance, :fact => id_fact, :role => rr.role, :population => @population) + debug :instance, "Going ahead with creating #{binding.player.name} using #{identifiers.size} roles" do + instance = @constellation.Instance(:new, :concept => entity_type, :population => @population) + binding.instance = instance + @bound_facts << instance + identifiers.each do |rr, identifying_reading, identifying_binding, identifying_instance| + # This reading provides the identifying literal for the entity_type + id_fact = + identifying_reading.fact = + @constellation.Fact(:new, :fact_type => rr.role.fact_type, :population => @population) + @bound_facts << id_fact + role = (rr.role.fact_type.all_role.to_a-[rr.role])[0] + @constellation.RoleValue(:instance => instance, :fact => id_fact, :population => @population, :role => role) + @constellation.RoleValue(:instance => identifying_instance, :fact => id_fact, :role => rr.role, :population => @population) + end end true # Done with this reading end @@ -229,10 +297,11 @@ # That role is played either by a value type, or by another similarly single-identified entity type debug "Making EntityType #{concept.name} identified by '#{literal}' #{@population.name.size>0 ? " in "+@population.name.inspect : ''}" do identifying_role_refs = concept.preferred_identifier.role_sequence.all_role_ref raise "Single literal cannot satisfy multiple identifying roles for #{concept.name}" if identifying_role_refs.size > 1 role = identifying_role_refs.single.role + # This instance has no binding; the binding is of the entity type not the identifying value type identifying_instance = instance_identified_by_literal role.concept, literal existing_instance = nil instance_rv = identifying_instance.all_role_value.detect { |rv| next false unless rv.population == @population # Not this population next false unless rv.fact.fact_type == role.fact_type # Not this fact type @@ -242,12 +311,14 @@ } if instance_rv instance = existing_instance debug :instance, "This #{concept.name} entity already exists" else + # This fact has no reading. fact = @constellation.Fact(:new, :fact_type => role.fact_type, :population => @population) @bound_facts << fact + # This instance will be associated with its binding by our caller instance = @constellation.Instance(:new, :concept => concept, :population => @population) @bound_facts << instance # The identifying fact type has two roles; create both role instances: @constellation.RoleValue(:instance => identifying_instance, :fact => fact, :population => @population, :role => role) @constellation.RoleValue(:instance => instance, :fact => fact, :population => @population, :role => (role.fact_type.all_role-[role])[0]) @@ -261,10 +332,10 @@ # Provide a readable description of the problem here, by showing each binding with no instance missing_bindings = @unbound_readings. map do |reading| reading.role_refs. select do |rr| - !@bound_instances[rr.binding] + !rr.binding.instance end. map do |role_ref| role_ref.binding end end.