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.