lib/activefacts/vocabulary/verbaliser.rb in activefacts-0.8.9 vs lib/activefacts/vocabulary/verbaliser.rb in activefacts-0.8.10
- old
+ new
@@ -9,19 +9,19 @@
#
# The Verbaliser fulfils two roles:
# * Maintains verbalisation context to expand readings using subscripting where needed
# * Verbalises Joins by iteratively choosing a Join Step and expanding readings
#
- # The verbalisation context consists of a set of Players, each for one Concept.
- # There may be more than one Player for the same Concept. If adjectives or role
+ # The verbalisation context consists of a set of Players, each for one ObjectType.
+ # There may be more than one Player for the same ObjectType. If adjectives or role
# names don't make such duplicates unambiguous, subscripts will be generated.
# Thus, the verbalisation context must be completely populated before subscript
# generation, which must be before any Player name gets verbalised.
#
# When a Player occurs in a Join, it corresponds to one Join Node of that Join.
# Each such Player has one or more JoinRoles, which refer to roles played by
- # that Concept. Where a join traverses two roles of a ternary fact type, there
+ # that ObjectType. Where a join traverses two roles of a ternary fact type, there
# will be a residual node that has only a single JoinRole with no other meaning.
# A JoinRole must be for exactly one Player, so is used to identify a Player.
#
# When a Player occurs outside a Join, it's identified by a projected RoleRef.
# REVISIT: This is untrue when a uniqueness constraint is imported from NORMA.
@@ -35,15 +35,15 @@
# The only type of join possible in a Ring Constraint is a subtyping join, which
# is always implicit and unambiguous, so is never instantiated.
#
# A constrained RoleSequence that has no explicit Join may have an implicit join,
# as per ORM2, when the roles aren't in the same fact type. These implicit joins
- # are over only one Concept, by traversing a single FactType (and possibly,
+ # are over only one ObjectType, by traversing a single FactType (and possibly,
# multiple TypeInheritance FactTypes) for each RoleRef. Note however that when
- # the Concept is an objectified Fact Type, the FactType traversed might be a
+ # the ObjectType is an objectified Fact Type, the FactType traversed might be a
# phantom of the objectification. In the case of implicit joins, each Player is
- # identified by the projected RoleRef, except for the joined-over Concept whose
+ # identified by the projected RoleRef, except for the joined-over ObjectType whose
# Player is... well, read the next paragraph!
#
# REVISIT: I believe that the foregoing paragraph is out of date, except with
# respect to PresenceConstraints imported from NORMA (both external mandatory
# and external uniqueness constraints). The joined-over Player in a UC is
@@ -52,11 +52,11 @@
# However, all other such joins are expliciti, and these should be also.
#
# For a SetComparisonConstraint, there are two or more constrained RoleSequences.
# The matching RoleRefs (by Ordinal position) are for joined players, that is,
# one individual instance plays both roles. The RoleRefs must (now) be for the
- # same Concept (no implicit subtyping Join is allowed). Instead, the input modules
+ # same ObjectType (no implicit subtyping Join is allowed). Instead, the input modules
# find the closest common supertype and create explicit JoinSteps so its roles
# can be projected.
#
# When expanding Reading text however, the RoleRefs in the reading's RoleSequence
# may be expected not to be attached to the Players for that reading. Instead,
@@ -97,54 +97,65 @@
add_role_refs role_refs if role_refs
end
class Player
- attr_accessor :concept, :join_nodes_by_join, :subscript, :join_roles, :role_refs
- def initialize concept
- @concept = concept
+ attr_accessor :object_type, :join_nodes_by_join, :subscript, :join_roles, :role_refs
+ def initialize object_type
+ @object_type = object_type
@join_nodes_by_join = {}
@subscript = nil
@join_roles = []
@role_refs = []
end
# What words are used (across all roles) for disambiguating the references to this player?
# If more than one set of adjectives was used, this player must have been subject to loose binding.
# This method is used to decide when subscripts aren't needed.
- def role_adjuncts
- adjuncts = @role_refs.map{|rr|
- [
- rr.leading_adjective,
- # rr.role.role_name,
- rr.trailing_adjective
- ].compact}.uniq.sort
+ def role_adjuncts matching
+ if matching == :loose
+ adjuncts = []
+ else
+ adjuncts = @role_refs.map{|rr|
+ [
+ rr.leading_adjective,
+ matching == :rolenames ? rr.role.role_name : nil,
+ rr.trailing_adjective
+ ].compact}.uniq.sort
+ end
+ adjuncts += [@join_nodes_by_join.values.map{|jn| jn.role_name}.compact[0]].compact
adjuncts.flatten*"_"
end
def describe
- @concept.name + (@join_nodes_by_join.size > 0 ? " (in #{@join_nodes_by_join.size} joins)" : "")
+ @object_type.name + (@join_nodes_by_join.size > 0 ? " (in #{@join_nodes_by_join.size} joins)" : "")
end
end
# Find or create a Player to which we can add this role_ref
def player(ref)
- if ref.is_a?(ActiveFacts::Metamodel::JoinRole)
- @player_by_join_role[ref] or
- @players.push(p = Player.new(ref.role.concept)) && p
+ existing_player = if ref.is_a?(ActiveFacts::Metamodel::JoinRole)
+ @player_by_join_role[ref]
+ else
+ @player_by_role_ref[ref] or ref.join_role && @player_by_join_role[ref.join_role]
+ end
+ if existing_player
+ debug :player, "Using existing player for #{ref.role.object_type.name} #{ref.respond_to?(:role_sequence) && ref.role_sequence.all_reading.size > 0 ? ' in reading' : ''}in '#{ref.role.fact_type.default_reading}'"
+ return existing_player
else
- @player_by_role_ref[ref] or
- ref.join_role && @player_by_join_role[ref.join_role] or
- @players.push(p = Player.new(ref.role.concept)) && p
+ debug :player, "Adding new player for #{ref.role.object_type.name} #{ref.respond_to?(:role_sequence) && ref.role_sequence.all_reading.size > 0 ? ' in reading' : ''}in '#{ref.role.fact_type.default_reading}'"
+ p = Player.new(ref.role.object_type)
+ @players.push(p)
+ p
end
end
def add_join_role player, join_role
return if player.join_roles.include?(join_role)
jn = join_role.join_node
if jn1 = player.join_nodes_by_join[jn.join] and jn1 != jn
- raise "Player for #{player.concept.name} may only have one join node per join, not #{jn1.concept.name} and #{jn.concept.name}"
+ raise "Player for #{player.object_type.name} may only have one join node per join, not #{jn1.object_type.name} and #{jn.object_type.name}"
end
player.join_nodes_by_join[jn.join] = jn
@player_by_join_role[join_role] = player
player.join_roles << join_role
end
@@ -153,11 +164,11 @@
def add_role_player player, role_ref
#debug :subscript, "Adding role_ref #{role_ref.object_id} to player #{player.object_id}"
if jr = role_ref.join_role
add_join_role(player, jr)
elsif !player.role_refs.include?(role_ref)
- debug :subscript, "Adding reference to player #{player.object_id} for #{role_ref.role.concept.name} in #{role_ref.role_sequence.describe} with #{role_ref.role_sequence.all_reading.size} readings"
+ debug :subscript, "Adding reference to player #{player.object_id} for #{role_ref.role.object_type.name} in #{role_ref.role_sequence.describe} with #{role_ref.role_sequence.all_reading.size} readings"
player.role_refs.push(role_ref)
@player_by_role_ref[role_ref] = player
end
end
@@ -180,15 +191,11 @@
if (role_ref.role.fact_type.all_role.size == 1)
role_ref.role.fact_type.default_reading # Need whole reading for a unary.
elsif role_name = role_ref.role.role_name and role_name != ''
role_name
else
- role_words = []
- role_words << preferred_role_ref.leading_adjective if preferred_role_ref.leading_adjective != ""
- role_words << preferred_role_ref.role.concept.name
- role_words << preferred_role_ref.trailing_adjective if preferred_role_ref.trailing_adjective != ""
- role_name = role_words.compact*"-"
+ role_name = preferred_role_ref.cql_name
if p = player(preferred_role_ref) and p.subscript
role_name += "(#{p.subscript})"
end
role_name
end
@@ -209,14 +216,14 @@
return if join_roles.empty?
# If any of these join_roles are for a known player, use that, else make a new player.
existing_players = join_roles.map{|jr| @player_by_join_role[jr] }.compact.uniq
if existing_players.size > 1
- raise "Can't join these roles to more than one existing player: #{existing_players.map{|p|p.concept.name}*', '}!"
+ raise "Can't join these roles to more than one existing player: #{existing_players.map{|p|p.object_type.name}*', '}!"
end
p = existing_players[0] || player(join_roles[0])
- debugger if join_roles.detect{|jr| jr.role.concept != p.concept }
+ debugger if join_roles.detect{|jr| jr.role.object_type != p.object_type }
debug :subscript, "Joining roles to #{p.describe}" do
join_roles.each do |jr|
debug :subscript, "#{jr.describe}" do
add_join_role p, jr
end
@@ -231,73 +238,98 @@
# If any of these role_refs are for a known player, use that, else make a new player.
existing_players =
role_refs.map{|rr| @player_by_role_ref[rr] || @player_by_join_role[rr.join_role] }.compact.uniq
if existing_players.size > 1
- raise "Can't join these role_refs to more than one existing player: #{existing_players.map{|p|p.concept.name}*', '}!"
+ raise "Can't join these role_refs to more than one existing player: #{existing_players.map{|p|p.object_type.name}*', '}!"
end
p = existing_players[0] || player(role_refs[0])
- debug :subscript, "#{existing_players[0] ? 'Adding to existing' : 'Creating new'} player for #{role_refs.map{|rr| rr.role.concept.name}.uniq*', '}" do
+ debug :subscript, "#{existing_players[0] ? 'Adding to existing' : 'Creating new'} player for #{role_refs.map{|rr| rr.role.object_type.name}.uniq*', '}" do
role_refs.each do |rr|
- unless p.concept == rr.role.concept
+ unless p.object_type == rr.role.object_type
# This happens in SubtypePI because uniqueness constraint is built without its implicit subtyping join.
# For now, explode only if there's no common supertype:
- if 0 == (p.concept.supertypes_transitive & rr.role.concept.supertypes_transitive).size
- raise "REVISIT: Internal error, trying to add role of #{rr.role.concept.name} to player #{p.concept.name}"
+ if 0 == (p.object_type.supertypes_transitive & rr.role.object_type.supertypes_transitive).size
+ raise "REVISIT: Internal error, trying to add role of #{rr.role.object_type.name} to player #{p.object_type.name}"
end
end
add_role_player(p, rr)
end
end
end
- def create_subscripts
+ # REVISIT: include_rolenames is a bit of a hack. Role names generally serve to disambiguate players,
+ # so subscripts wouldn't be needed, but where a constraint refers to a fact type which is defined with
+ # role names, those are considered. We should instead consider only the role names that are defined
+ # within the constraint, not in the underlying fact types. For now, this parameter is passed as true
+ # from all the object type verbalisations, and not from constraints.
+ def create_subscripts(matching = :normal)
# Create subscripts, where necessary
@players.each { |p| p.subscript = nil } # Wipe subscripts
@players.
- map{|p| [p, p.concept] }.
- each do |player, concept|
+ map{|p| [p, p.object_type] }.
+ each do |player, object_type|
next if player.subscript # Done previously
- dups = @players.select{|p| p.concept == concept && p.role_adjuncts == player.role_adjuncts }
+ dups = @players.select do |p|
+ p.object_type == object_type &&
+ p.role_adjuncts(matching) == player.role_adjuncts(matching)
+ end
if dups.size == 1
- debug :subscript, "No subscript needed for #{concept.name}"
+ debug :subscript, "No subscript needed for #{object_type.name}"
next
end
- debug :subscript, "Applying subscripts to #{dups.size} occurrences of #{concept.name}" do
- dups.each_with_index do |player, index|
- player.subscript = index+1
+ debug :subscript, "Applying subscripts to #{dups.size} occurrences of #{object_type.name}" do
+ s = 0
+ dups.
+ sort_by{|p| # Guarantee stable numbering
+ p.role_adjuncts(:role_name) + ' ' +
+ # Tie-breaker:
+ p.role_refs.map{|rr| rr.role.fact_type.preferred_reading.text}.sort.to_s
+ }.
+ each do |player|
+ jrname = player.join_roles.map{|jr| jr.role_ref && jr.role_ref.role.role_name}.compact[0]
+ rname = (rr = player.role_refs[0]) && rr.role.role_name
+ if jrname and !rname
+ # puts "Oops: rolename #{rname.inspect} != #{jrname.inspect}" if jrname != rname
+ player.join_nodes_by_join.values.each{|jn| jn.role_name = jrname }
+ else
+ player.subscript = s+1
+ s += 1
+ end
end
end
end
end
# Expand a reading for an entity type or fact type definition. Unlike expansions in constraints,
# these expansions include frequency constraints, role names and value constraints as passed-in,
# and also define adjectives by using the hyphenated form (on at least the first occurrence).
def expand_reading(reading, frequency_constraints = [], define_role_names = nil, value_constraints = [], &subscript_block)
reading.expand(frequency_constraints, define_role_names, value_constraints) do |role_ref|
- (!(role_ref.role.role_name and define_role_names) and p = player(role_ref) and p.subscript) ? "(#{p.subscript})" : ""
+ (!(role_ref.role.role_name and define_role_names != nil) and p = player(role_ref) and p.subscript) ? "(#{p.subscript})" : ""
end
end
# Where no explicit Join has been created, a join is still sometimes present (e.g. in a constraint from NORMA)
# REVISIT: This probably doesn't produce the required result. Need to fix the NORMA importer to create the join.
def role_refs_are_subtype_joined roles
role_refs = roles.is_a?(Array) ? roles : roles.all_role_ref.to_a
- role_refs_by_concept = role_refs.inject({}) { |h, r| (h[r.role.concept] ||= []) << r; h }
- role_refs_by_concept.values.each { |rrs| role_refs_have_same_player(rrs) }
+ role_refs_by_object_type = role_refs.inject({}) { |h, r| (h[r.role.object_type] ||= []) << r; h }
+ role_refs_by_object_type.values.each { |rrs| role_refs_have_same_player(rrs) }
end
# These roles are the players in an implicit counterpart join in a Presence Constraint.
# REVISIT: It's not clear that we can safely use the preferred_reading's RoleRefs here.
# Fix the CQL compiler to create proper joins for these presence constraints instead.
def roles_have_same_player roles
role_refs = roles.map do |role|
- pr = role.fact_type.preferred_reading
- pr.role_sequence.all_role_ref.detect{|rr| rr.role == role}
- end
+ role.fact_type.all_reading.map{|reading|
+ reading.role_sequence.all_role_ref.detect{|rr| rr.role == role}
+ } +
+ role.all_role_ref.select{|rr| rr.role_sequence.all_reading.size == 0 }
+ end.flatten.uniq
role_refs_have_same_player(role_refs)
end
def prepare_role_sequence role_sequence, join_over = nil
@role_refs = role_sequence.is_a?(Array) ? role_sequence : role_sequence.all_role_ref.to_a
@@ -314,14 +346,14 @@
# Register the role_refs in the preferred reading which refer to roles not covered in the role sequence.
prrs = fact_type.preferred_reading.role_sequence.all_role_ref
residual_roles = fact_type.all_role.select{|r| !@role_refs.detect{|rr| rr.role == r} }
residual_roles.each do |role|
- debug :subscript, "Adding residual role for #{role.concept.name} (in #{fact_type.default_reading}) not covered in role sequence"
+ debug :subscript, "Adding residual role for #{role.object_type.name} (in #{fact_type.default_reading}) not covered in role sequence"
preferred_role_ref = prrs.detect{|rr| rr.role == role}
if p = @player_by_role_ref[preferred_role_ref] and !p.role_refs.include?(preferred_role_ref)
- raise "Adding DUPLICATE residual role for #{role.concept.name}"
+ raise "Adding DUPLICATE residual role for #{role.object_type.name}"
end
role_refs_have_same_player([prrs.detect{|rr| rr.role == role}])
end
end
end
@@ -338,17 +370,17 @@
end
=begin
# For each fact type traversed, register a player for each role *not* linked to this join
# REVISIT: Using the preferred_reading role_ref is wrong here; the same preferred_reading might occur twice,
- # so the respective concept will need more than one Player and will be subscripted to keep them from being joined.
+ # so the respective object_type will need more than one Player and will be subscripted to keep them from being joined.
# Accordingly, there must be a join step for each such role, and to enforce that, I raise an exception here on duplication.
# This isn't needed now all JoinNodes have at least one JoinRole
join_steps.map do |js|
if js.fact_type.is_a?(ActiveFacts::Metamodel::ImplicitFactType)
- js.fact_type.role.fact_type
+ js.fact_type.implying_role.fact_type
else
js.fact_type
end
end.uniq.each do |fact_type|
#join_steps.map{|js|js.fact_type}.uniq.each do |fact_type|
@@ -356,14 +388,14 @@
debug :subscript, "Residual roles in '#{fact_type.default_reading}' are" do
prrs = fact_type.preferred_reading.role_sequence.all_role_ref
residual_roles = fact_type.all_role.select{|r| !r.all_role_ref.detect{|rr| rr.join_node && rr.join_node.join == join} }
residual_roles.each do |r|
- debug :subscript, "Adding residual role for #{r.concept.name} (in #{fact_type.default_reading}) not covered in join"
+ debug :subscript, "Adding residual role for #{r.object_type.name} (in #{fact_type.default_reading}) not covered in join"
preferred_role_ref = prrs.detect{|rr| rr.role == r}
if p = @player_by_role_ref[preferred_role_ref] and !p.role_refs.include?(preferred_role_ref)
- raise "Adding DUPLICATE residual role for #{r.concept.name} not covered in join"
+ raise "Adding DUPLICATE residual role for #{r.object_type.name} not covered in join"
end
role_refs_have_same_player([preferred_role_ref])
end
end
end
@@ -386,28 +418,26 @@
readings = fact_types.map do |fact_type|
name_substitutions = []
# Choose a reading that start with the (first) role which caused us to emit this fact type:
reading = fact_type.reading_preferably_starting_with_role(role_by_fact_type[fact_type])
if join_over and # Find a reading preferably starting with the joined_over role:
- joined_role = fact_type.all_role.select{|r| join_over.subtypes_transitive.include?(r.concept)}[0]
+ joined_role = fact_type.all_role.select{|r| join_over.subtypes_transitive.include?(r.object_type)}[0]
reading = fact_type.reading_preferably_starting_with_role joined_role
# Use the name of the joined_over object, not the role player, in case of a subtype join:
rrrs = reading.role_sequence.all_role_ref_in_order
role_index = (0..rrrs.size).detect{|i| rrrs[i].role == joined_role }
name_substitutions[role_index] = [nil, join_over.name]
end
reading.role_sequence.all_role_ref.each do |rr|
next unless player = @player_by_role_ref[rr]
next unless subscript = player.subscript
- debug :subscript, "Need to apply subscript #{subscript} to #{rr.role.concept.name}"
+ debug :subscript, "Need to apply subscript #{subscript} to #{rr.role.object_type.name}"
end
player_by_role = {}
@player_by_role_ref.keys.each{|rr| player_by_role[rr.role] = @player_by_role_ref[rr] if rr.role.fact_type == fact_type }
- #role_refs = @player_by_role_ref.keys.select{|rr| rr.role.fact_type == fact_type}
expand_reading_text(nil, reading.text, reading.role_sequence, player_by_role)
- #reading.expand(name_substitutions)
end
joiner ? readings*joiner : readings
end
# Expand this reading (or partial reading, during contraction)
@@ -419,41 +449,33 @@
rrs = role_sequence.all_role_ref_in_order
debug :subscript, "expanding '#{text}' with #{role_sequence.describe}" do
text.gsub(/\{(\d)\}/) do
role_ref = rrs[$1.to_i]
# REVISIT: We may need to use the step's role_refs to expand the role players here, not the reading's one (extra adjectives?)
- # REVISIT: There's no way to get literals to be emitted here (value join step?)
+ # REVISIT: There's no way to get literals to be emitted here (value join step or query result?)
player = player_by_role[role_ref.role]
-=begin
- rr = role_refs.detect{|rr| rr.role == role_ref.role} || role_ref
-
- player = @player_by_role_ref[rr] and subscript = player.subscript
- if !subscript and
- pp = @players.select{|p|p.concept == rr.role.concept} and
- pp.detect{|p|p.subscript}
- # raise "Internal error: Subscripted players (of the same concept #{pp[0].concept.name}) when this player isn't subscripted"
- end
-=end
-
- subscripted_player(role_ref, player && player.subscript) +
- objectification_verbalisation(role_ref.role.concept)
+ join_role_name = player && player.join_nodes_by_join.values.map{|jn| jn.role_name}.compact[0]
+ subscripted_player(role_ref, player && player.subscript, join_role_name) +
+ objectification_verbalisation(role_ref.role.object_type)
end
end
end
- def subscripted_player role_ref, subscript = nil
- if subscript
- debug :subscript, "Need to apply subscript #{subscript} to #{role_ref.role.concept.name}"
- end
- concept = role_ref.role.concept
- [
- role_ref.leading_adjective,
- concept.name,
- role_ref.trailing_adjective
- ].compact*' ' +
+ def subscripted_player role_ref, subscript = nil, join_role_name = nil
+ prr = @player_by_role_ref[role_ref]
+ subscript ||= prr.subscript if prr
+ debug :subscript, "Need to apply subscript #{subscript} to #{role_ref.role.object_type.name}" if subscript
+ object_type = role_ref.role.object_type
+ (join_role_name ||
+ [
+ role_ref.leading_adjective,
+ object_type.name,
+ role_ref.trailing_adjective
+ ].compact*' '
+ ) +
(subscript ? "(#{subscript})" : '')
end
def expand_contracted_text(step, reading, role_refs = [])
' that ' +
@@ -513,13 +535,13 @@
next_step = @join_steps[0]
if next_step
debug :join, "Chose new random step from #{join_steps.size}: #{next_step.describe}"
if next_step.is_objectification_step
# if this objectification plays any roles (other than its FT roles) in remaining steps, use one of those first:
- fact_type = next_step.fact_type.role.fact_type
- jn = [next_step.input_join_role.join_node, next_step.output_join_role.join_node].detect{|jn| jn.concept == fact_type.entity_type}
- sr = @join_steps_by_join_node[jn].reject{|t| t.fact_type.role and t.fact_type.role.fact_type == fact_type}
+ fact_type = next_step.fact_type.implying_role.fact_type
+ jn = [next_step.input_join_role.join_node, next_step.output_join_role.join_node].detect{|jn| jn.object_type == fact_type.entity_type}
+ sr = @join_steps_by_join_node[jn].reject{|t| r = t.fact_type.implying_role and r.fact_type == fact_type}
next_step = sr[0] if sr.size > 0
end
return next_step
end
raise "Internal error: There are more join steps here, but we failed to choose one"
@@ -532,17 +554,17 @@
# Find whether last role has no following text, and its ordinal
(reading.text =~ /\{([0-9])\}$/) &&
# This reading's RoleRef for that role:
(role_ref = reading.role_sequence.all_role_ref_in_order[$1.to_i]) &&
# was that RoleRef for the upcoming node?
- role_ref.role.concept == next_node.concept
+ role_ref.role.object_type == next_node.object_type
end
def reading_starts_with_node(reading, next_node)
reading.text =~ /^\{([0-9])\}/ and
role_ref = reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i} and
- role_ref.role.concept == next_node.concept
+ role_ref.role.object_type == next_node.object_type
end
# The last reading we emitted ended with the object type name for next_node.
# Choose a step and a reading that can be contracted against that name
def contractable_step(next_steps, next_node)
@@ -556,25 +578,25 @@
# This step is contractable iff the FactType has a reading that starts with the role of next_node (no preceding text)
reading_starts_with_node(reading, next_node)
end
next_reading
end
- debug :join, "#{next_reading ? "'"+next_reading.expand+"'" : "No reading"} contracts against last node '#{next_node.concept.name}'"
+ debug :join, "#{next_reading ? "'"+next_reading.expand+"'" : "No reading"} contracts against last node '#{next_node.object_type.name}'"
return [next_step, next_reading]
end
- # REVISIT: There might be more than one objectification_verbalisation for a given concept. Need to get the Join Node here and emit an objectification step involving that node.
- def objectification_verbalisation(concept)
+ # REVISIT: There might be more than one objectification_verbalisation for a given object_type. Need to get the Join Node here and emit an objectification step involving that node.
+ def objectification_verbalisation(object_type)
objectified_node = nil
- unless concept.is_a?(Metamodel::EntityType) and
- concept.fact_type and # Not objectified
+ unless object_type.is_a?(Metamodel::EntityType) and
+ object_type.fact_type and # Not objectified
objectification_step = @join_steps.
detect do |js|
# The objectifying entity type should always be the input_join_node here, but be safe:
js.is_objectification_step and
- (objectified_node = js.input_join_role.join_node).concept == concept ||
- (objectified_node = js.output_join_role.join_node).concept == concept
+ (objectified_node = js.input_join_role.join_node).object_type == object_type ||
+ (objectified_node = js.output_join_role.join_node).object_type == object_type
end
return ''
end
# REVISIT: We need to be working from the role_ref here - pass it in
@@ -584,11 +606,11 @@
step_completed(objectification_step)
while other_step =
@join_steps.
detect{|js|
js.is_objectification_step and
- js.input_join_role.join_node.concept == concept || js.output_join_role.join_node.concept == concept
+ js.input_join_role.join_node.object_type == object_type || js.output_join_role.join_node.object_type == object_type
}
steps << other_step
debug :join, "Emitting objectification step allows deleting #{other_step.describe}"
step_completed(other_step)
end
@@ -599,13 +621,13 @@
join_step.all_join_role.to_a.map do |jr|
player_by_role[jr.role] = @player_by_join_role[jr]
end
end
- # role_refs = steps.map{|step| [step.input_join_role.join_node, step.output_join_role.join_node].map{|jn| jn.all_role_ref.detect{|rr| rr.role.fact_type == concept.fact_type}}}.flatten.compact.uniq
+ # role_refs = steps.map{|step| [step.input_join_role.join_node, step.output_join_role.join_node].map{|jn| jn.all_role_ref.detect{|rr| rr.role.fact_type == object_type.fact_type}}}.flatten.compact.uniq
- reading = concept.fact_type.preferred_reading
+ reading = object_type.fact_type.preferred_reading
" (where #{expand_reading_text(objectification_step, reading.text, reading.role_sequence, player_by_role)})"
end
def elided_objectification(next_step, fact_type, last_is_contractable, next_node)
if last_is_contractable
@@ -622,20 +644,23 @@
reading.text =~ /\{(\d)\}[^{]*\Z/
last_role_ref = reading.role_sequence.all_role_ref_in_order[$1.to_i]
exit_node = @join_nodes.detect{|jn| jn.all_join_role.detect{|jr| jr.role == last_role_ref.role}}
exit_step = nil
+ count = 0
while other_step =
@join_steps.
detect{|js|
next unless js.is_objectification_step
- next unless js.input_join_role.join_node.concept == fact_type.entity_type || js.output_join_role.join_node.concept == fact_type.entity_type
+ # REVISIT: This test is too weak: We need to ensure that the same join nodes are involved, not just the same object types:
+ next unless js.input_join_role.join_node.object_type == fact_type.entity_type || js.output_join_role.join_node.object_type == fact_type.entity_type
exit_step = js if js.output_join_role.join_node == exit_node
true
}
debug :join, "Emitting objectified FT allows deleting #{other_step.describe}"
step_completed(other_step)
+# raise "The objectification of '#{fact_type.default_reading}' should not cause the deletion of more than #{fact_type.all_role.size} other join steps" if (count += 1) > fact_type.all_role.size
end
[ reading, exit_step ? exit_step.input_join_role.join_node : exit_node, exit_step, last_is_contractable]
end
def verbalise_join join
@@ -655,11 +680,11 @@
if last_is_contractable && next_steps
next_step, next_reading = *contractable_step(next_steps, next_node)
end
if next_step
- debug :join, "Chose #{next_step.describe} because it's contractable against last node #{next_node.concept.name} using #{next_reading.expand}"
+ debug :join, "Chose #{next_step.describe} because it's contractable against last node #{next_node.object_type.name} using #{next_reading.expand}"
player_by_role =
next_step.all_join_role.inject({}) {|h, jr| h[jr.role] = @player_by_join_role[jr]; h }
readings += expand_contracted_text(next_step, next_reading, player_by_role)
step_completed(next_step)
@@ -669,29 +694,29 @@
player_by_role =
next_step.all_join_role.inject({}) {|h, jr| h[jr.role] = @player_by_join_role[jr]; h }
if next_step.is_unary_step
# Objectified unaries get emitted as unaries, not as objectifications:
- # REVISIT: There must be a simpler way of finding the preferred reading here:
- rr = next_step.input_join_node.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ImplicitFactType) }
- next_reading = rr.role.fact_type.role.fact_type.preferred_reading
+ role = next_step.input_join_role.role
+ role = role.fact_type.implying_role if role.fact_type.is_a?(ImplicitFactType)
+ next_reading = role.fact_type.preferred_reading
readings += " and " unless readings.empty?
readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, player_by_role)
step_completed(next_step)
elsif next_step.is_objectification_step
- fact_type = next_step.fact_type.role.fact_type
+ fact_type = next_step.fact_type.implying_role.fact_type
# This objectification step is over an implicit fact type, so player_by_role won't have all the players
# Add the players of other roles associated with steps from this objectified player.
objectified_node = next_step.input_join_role.join_node
- raise "Assumption violated that the objectification is the input join role" unless objectified_node.concept.fact_type
+ raise "Assumption violated that the objectification is the input join role" unless objectified_node.object_type.fact_type
objectified_node.all_join_step.map do |other_step|
(other_step.all_incidental_join_role.to_a + [other_step.output_join_role]).map do |jr|
player_by_role[jr.role] = @player_by_join_role[jr]
end
end
- if last_is_contractable and next_node.concept.is_a?(EntityType) and next_node.concept.fact_type == fact_type
+ if last_is_contractable and next_node.object_type.is_a?(EntityType) and next_node.object_type.fact_type == fact_type
# The last reading we emitted ended with the name of the objectification of this fact type, so we can contract the objectification
# REVISIT: Do we need to use player_by_role here (if this objectification is traversed twice and so is subscripted)
readings += objectification_verbalisation(fact_type.entity_type)
else
# This objectified fact type does not need to be made explicit.