activefacts 0.8.8 → 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,5 +5,5 @@
5
5
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
6
  #
7
7
  module ActiveFacts
8
- VERSION = '0.8.8'
8
+ VERSION = '0.8.9'
9
9
  end
@@ -89,7 +89,11 @@ module ActiveFacts
89
89
 
90
90
  class RoleRef
91
91
  def describe
92
- role_name + (join_node ? " JN#{join_node.ordinal}" : '')
92
+ role_name
93
+ end
94
+
95
+ def preferred_role_ref
96
+ role.fact_type.preferred_reading.role_sequence.all_role_ref.detect{|rr| rr.role == role}
93
97
  end
94
98
 
95
99
  def role_name(joiner = "-")
@@ -486,104 +490,128 @@ module ActiveFacts
486
490
  def describe(role = nil)
487
491
  "#{subtype.name} is a kind of #{supertype.name}"
488
492
  end
493
+
494
+ def supertype_role
495
+ (roles = all_role.to_a)[0].concept == supertype ? roles[0] : roles[1]
496
+ end
497
+
498
+ def subtype_role
499
+ (roles = all_role.to_a)[0].concept == subtype ? roles[0] : roles[1]
500
+ end
489
501
  end
490
502
 
491
503
  class JoinStep
492
504
  def describe
493
- input_role_ref = input_join_node.all_role_ref.detect{|rr| rr.role.fact_type == fact_type}
494
- output_role_ref = output_join_node.all_role_ref.detect{|rr| rr.role.fact_type == fact_type}
495
- "from node #{input_join_node.ordinal} #{input_role_ref ? input_role_ref.role.concept.name : input_join_node.concept.name}"+
496
- " to node #{output_join_node.ordinal} #{output_role_ref ? output_role_ref.role.concept.name : output_join_node.concept.name}"+
497
- ": #{is_anti && 'not '}#{is_outer && 'maybe '}#{fact_type.default_reading}"
505
+ "JoinStep " +
506
+ "#{is_outer && 'maybe '}" +
507
+ (is_unary_step ? " (unary) " : "from #{input_join_role.describe} ") +
508
+ "#{is_anti && 'not '}" +
509
+ "to #{output_join_role.describe} " +
510
+ "over " + (is_objectification_step ? 'objectification ' : '') +
511
+ "'#{fact_type.default_reading}'"
498
512
  end
499
513
 
500
514
  def is_unary_step
501
515
  # Preserve this in case we have to use a real join_node for the phantom
502
- # input_join_node.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ImplicitFactType) && rr.role.fact_type.role.fact_type.all_role.size == 1 }
503
- input_join_node == output_join_node
516
+ input_join_role == output_join_role
504
517
  end
505
518
 
506
519
  def is_objectification_step
507
520
  fact_type.is_a?(ImplicitFactType)
508
521
  end
522
+
523
+ def all_join_role
524
+ [input_join_role, output_join_role].uniq + all_incidental_join_role.to_a
525
+ end
526
+
527
+ def external_fact_type
528
+ fact_type.is_a?(ImplicitFactType) ? fact_type.role.fact_type : fact_type
529
+ end
509
530
  end
510
531
 
511
532
  class JoinNode
512
533
  def describe
513
- concept.name
534
+ concept.name +
535
+ (subscript ? "(#{subscript})" : '') +
536
+ " JN#{ordinal}" +
537
+ # (all_join_role.detect{|jr| jr.role_ref} ? " (projected)" : "") +
538
+ (value ? ' = '+value.describe : '')
539
+ end
540
+
541
+ def all_join_step
542
+ all_join_role.map do |jr|
543
+ jr.all_join_step_as_input_join_role.to_a +
544
+ jr.all_join_step_as_output_join_role.to_a
545
+ end.
546
+ flatten.
547
+ uniq
548
+ end
549
+ end
550
+
551
+ class JoinRole
552
+ def describe
553
+ "#{role.concept.name} JN#{join_node.ordinal}" +
554
+ (role_ref ? " (projected)" : "")
555
+ end
556
+
557
+ def all_join_step
558
+ (all_join_step_as_input_join_role.to_a +
559
+ all_join_step_as_output_join_role.to_a +
560
+ [join_step]).flatten.compact.uniq
514
561
  end
515
562
  end
516
563
 
517
564
  class Join
518
565
  def show
566
+ steps_shown = {}
519
567
  debug :join, "Displaying full contents of Join #{join_id}" do
520
568
  all_join_node.sort_by{|jn| jn.ordinal}.each do |join_node|
521
- debug :join, "Node #{join_node.ordinal} for #{join_node.concept.name}" do
522
- (join_node.all_join_step_as_input_join_node.to_a +
523
- join_node.all_join_step_as_output_join_node.to_a).
524
- uniq.
569
+ debug :join, "#{join_node.describe}" do
570
+ join_node.all_join_step.
525
571
  each do |join_step|
526
- debug :join, "#{
527
- join_step.is_unary_step ? 'unary ' : ''
528
- }#{
529
- join_step.is_objectification_step ? 'objectification ' : ''
530
- }step #{join_step.describe}"
572
+ next if steps_shown[join_step]
573
+ steps_shown[join_step] = true
574
+ debug :join, "#{join_step.describe}"
531
575
  end
532
- join_node.all_role_ref.each do |role_ref|
533
- debug :join, "reference #{role_ref.describe} in '#{role_ref.role.fact_type.default_reading}' over #{role_ref.role_sequence.describe}#{role_ref.role_sequence == role_sequence ? ' (projected)' : ''}"
576
+ join_node.all_join_role.each do |join_role|
577
+ debug :join, "role of #{join_role.describe} in '#{join_role.role.fact_type.default_reading}'"
534
578
  end
535
579
  end
536
580
  end
537
581
  end
538
582
  end
539
583
 
584
+ def all_join_step
585
+ all_join_node.map{|jn| jn.all_join_step.to_a}.flatten.uniq
586
+ end
587
+
588
+ # Check all parts of this join for validity
540
589
  def validate
541
590
  show
542
591
  return
543
592
 
544
- # Check all parts of this join for validity
545
- jns = all_join_node.sort_by{|jn| jn.ordinal}
546
- jns.each_with_index do |jn, i|
547
- raise "Join node #{i} should have ordinal #{jn.ordinal}" unless jn.ordinal == i
548
- end
549
-
550
593
  # Check the join nodes:
551
- steps = []
552
- jns.each_with_index do |join_node, i|
594
+ join_steps = []
595
+ join_nodes = all_join_node.sort_by{|jn| jn.ordinal}
596
+ join_nodes.each_with_index do |join_node, i|
597
+ raise "Join node #{i} should have ordinal #{join_node.ordinal}" unless join_node.ordinal == i
553
598
  raise "Join Node #{i} has missing concept" unless join_node.concept
554
- if join_node.all_role_ref.detect{|rr| rr.role.concept != join_node.concept }
555
- raise "All role references for join node #{join_node.ordinal} should be for #{
556
- join_node.concept.name
557
- } but we have #{
558
- (join_node.all_role_ref.map{|rr| rr.role.concept.name}-[join_node.concept.name]).uniq*', '
559
- }"
599
+ join_node.all_join_role do |join_role|
600
+ raise "Join Node for #{concept.name} includes role played by #{join_role.concept.name}" unless join_role.concept == concept
560
601
  end
561
- steps += join_node.all_join_step_as_input_join_node.to_a
562
- steps += join_node.all_join_step_as_output_join_node.to_a
563
-
564
- # REVISIT: All Role References must be in a role sequence that covers one fact type exactly (why?)
565
- # REVISIT: All such role references must have a join node in this join. (why?)
602
+ join_steps += join_node.all_join_step
566
603
  end
604
+ join_steps.uniq!
567
605
 
568
606
  # Check the join steps:
569
- steps.uniq!
570
- steps.each_with_index do |join_step, i|
571
- raise "Join Step #{i} has missing fact type" unless join_step.fact_type
572
- raise "Join Step #{i} has missing input node" unless join_step.input_join_node
573
- raise "Join Step #{i} has missing output node" unless join_step.output_join_node
574
- debugger
575
- p join_step.fact_type.default_reading
576
- p join_step.input_join_node.all_role_ref.map(&:describe)
577
- p join_step.output_join_node.all_role_ref.map(&:describe)
578
- =begin
579
- unless join_step.input_join_node.all_role_ref.
580
- detect do |rr|
581
- rr.role.fact_type == join_step.fact_type
582
- or rr.role.fact_type.is_a?(ImplicitFactType) && rr.role.fact_type.role.fact_type == rr.join_step.concept
583
- end
584
- raise "Join Step #{join_step.describe} has nodes not matching its fact type"
607
+ join_steps.each do |join_step|
608
+ raise "Join Step has missing fact type" unless join_step.fact_type
609
+ raise "Join Step has missing input node" unless join_step.input_join_role
610
+ raise "Join Step has missing output node" unless join_step.output_join_role
611
+ if (role = input_join_role).role.fact_type != fact_type or
612
+ (role = output_join_role).role.fact_type != fact_type
613
+ raise "Join Step has role #{role.describe} which doesn't belong to the fact type '#{fact_type.default_reading}' it traverses"
585
614
  end
586
- =end
587
615
  end
588
616
 
589
617
  # REVISIT: Do a connectivity check
@@ -619,6 +647,7 @@ module ActiveFacts
619
647
  @role_sequence = role_sequence
620
648
  end
621
649
  def join_node; nil; end
650
+ def join_role; nil; end
622
651
  def leading_adjective; nil; end
623
652
  def trailing_adjective; nil; end
624
653
  def describe
@@ -705,7 +734,7 @@ module ActiveFacts
705
734
  objectification_role = role.implicit_fact_type.all_role.single # Find the phantom role here
706
735
  else
707
736
  objectification_role_supertypes = counterpart_role_supertypes
708
- objectification_role = nil
737
+ objectification_role = counterpart_role
709
738
  end
710
739
 
711
740
  if !d_c_o
@@ -733,7 +762,7 @@ module ActiveFacts
733
762
  end
734
763
 
735
764
  # Choose the first entry in the first non-empty supertypes list:
736
- if options != :counterpart
765
+ if options != :counterpart && proximate_sups[0]
737
766
  [ proximate_sups[0], roles ]
738
767
  elsif !counterpart_sups.empty?
739
768
  [ counterpart_sups[0], counterpart_roles ]
@@ -742,5 +771,67 @@ module ActiveFacts
742
771
  end
743
772
  end
744
773
 
774
+ class Fact
775
+ def verbalise(context = nil)
776
+ reading = fact_type.preferred_reading
777
+ reading_roles = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role }
778
+ role_values_in_reading_order = all_role_value.sort_by{|rv| reading_roles.index(rv.role) }
779
+ instance_verbalisations = role_values_in_reading_order.map do |rv|
780
+ if rv.instance.value
781
+ v = rv.instance.verbalise
782
+ else
783
+ if (c = rv.instance.concept).is_a?(EntityType)
784
+ if !c.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type == fact_type}
785
+ v = rv.instance.verbalise
786
+ end
787
+ end
788
+ end
789
+ next nil unless v
790
+ v.to_s.sub(/(#{rv.instance.concept.name}|\S*)\s/,'')
791
+ end
792
+ reading.expand([], false, instance_verbalisations)
793
+ end
794
+ end
795
+
796
+ class Instance
797
+ def verbalise(context = nil)
798
+ return "#{concept.name} #{value}" if concept.is_a?(ValueType)
799
+
800
+ return "#{concept.name} where #{fact.verbalise(context)}" if concept.fact_type
801
+
802
+ # It's an entity that's not an objectified fact type
803
+ # REVISIT: If it has a simple identifier, there's no need to fully verbalise the identifying facts
804
+ pi = concept.preferred_identifier
805
+ identifying_role_refs = pi.role_sequence.all_role_ref_in_order
806
+ "#{concept.name}" +
807
+ " is identified by " + # REVISIT: Where the single fact type is TypeInheritance, we can shrink this
808
+ if identifying_role_refs.size == 1 &&
809
+ (role = identifying_role_refs[0].role) &&
810
+ (my_role = (role.fact_type.all_role.to_a-[role])[0]) &&
811
+ (identifying_fact = my_role.all_role_value.detect{|rv| rv.instance == self}.fact) &&
812
+ (identifying_instance = identifying_fact.all_role_value.detect{|rv| rv.role == role}.instance)
813
+
814
+ "its #{identifying_instance.verbalise(context)}"
815
+ else
816
+ identifying_role_refs.map do |rr|
817
+ rr = rr.preferred_role_ref
818
+ [ (l = rr.leading_adjective) ? l+"-" : nil,
819
+ rr.role.role_name || rr.role.concept.name,
820
+ (t = rr.trailing_adjective) ? l+"-" : nil
821
+ ].compact*""
822
+ end * " and " +
823
+ " where " +
824
+ identifying_role_refs.map do |rr| # Go through the identifying roles and emit the facts that define them
825
+ instance_role = concept.all_role.detect{|r| r.fact_type == rr.role.fact_type}
826
+ identifying_fact = all_role_value.detect{|rv| rv.fact.fact_type == rr.role.fact_type}.fact
827
+ #counterpart_role = (rr.role.fact_type.all_role.to_a-[instance_role])[0]
828
+ #identifying_instance = counterpart_role.all_role_value.detect{|rv| rv.fact == identifying_fact}.instance
829
+ identifying_fact.verbalise(context)
830
+ end*", "
831
+ end
832
+
833
+ end
834
+ end
835
+
745
836
  end
746
837
  end
@@ -128,6 +128,10 @@ module ActiveFacts
128
128
  value_type
129
129
  end
130
130
 
131
+ class Subscript < UnsignedInteger
132
+ value_type :length => 16
133
+ end
134
+
131
135
  class Text < String
132
136
  value_type :length => 256
133
137
  end
@@ -207,7 +211,6 @@ module ActiveFacts
207
211
  class Join
208
212
  identified_by :join_id
209
213
  one_to_one :join_id, :mandatory => true # See JoinId.join
210
- has_one :role_sequence, :mandatory => true # See RoleSequence.all_join
211
214
  end
212
215
 
213
216
  class JoinNode
@@ -215,15 +218,8 @@ module ActiveFacts
215
218
  has_one :concept, :mandatory => true # See Concept.all_join_node
216
219
  has_one :join, :mandatory => true # See Join.all_join_node
217
220
  has_one :ordinal, :mandatory => true # See Ordinal.all_join_node
218
- end
219
-
220
- class JoinStep
221
- identified_by :input_join_node, :output_join_node
222
- has_one :fact_type, :mandatory => true # See FactType.all_join_step
223
- has_one :input_join_node, :class => JoinNode, :mandatory => true # See JoinNode.all_join_step_as_input_join_node
224
- maybe :is_anti
225
- maybe :is_outer
226
- has_one :output_join_node, :class => JoinNode, :mandatory => true # See JoinNode.all_join_step_as_output_join_node
221
+ has_one :subscript # See Subscript.all_join_node
222
+ has_one :value # See Value.all_join_node
227
223
  end
228
224
 
229
225
  class Position
@@ -381,6 +377,22 @@ module ActiveFacts
381
377
  has_one :rotation_setting # See RotationSetting.all_fact_type_shape
382
378
  end
383
379
 
380
+ class JoinRole
381
+ identified_by :join_node, :role
382
+ has_one :join_node, :mandatory => true # See JoinNode.all_join_role
383
+ has_one :role, :mandatory => true # See Role.all_join_role
384
+ has_one :join_step, :counterpart => :incidental_join_role # See JoinStep.all_incidental_join_role
385
+ end
386
+
387
+ class JoinStep
388
+ identified_by :input_join_role, :output_join_role
389
+ has_one :fact_type, :mandatory => true # See FactType.all_join_step
390
+ has_one :input_join_role, :class => JoinRole, :mandatory => true # See JoinRole.all_join_step_as_input_join_role
391
+ maybe :is_anti
392
+ maybe :is_outer
393
+ has_one :output_join_role, :class => JoinRole, :mandatory => true # See JoinRole.all_join_step_as_output_join_role
394
+ end
395
+
384
396
  class ModelNoteShape < Shape
385
397
  has_one :context_note, :mandatory => true # See ContextNote.all_model_note_shape
386
398
  end
@@ -427,7 +439,7 @@ module ActiveFacts
427
439
  has_one :ordinal, :mandatory => true # See Ordinal.all_role_ref
428
440
  has_one :role, :mandatory => true # See Role.all_role_ref
429
441
  has_one :role_sequence, :mandatory => true # See RoleSequence.all_role_ref
430
- has_one :join_node # See JoinNode.all_role_ref
442
+ one_to_one :join_role # See JoinRole.role_ref
431
443
  has_one :leading_adjective, :class => Adjective # See Adjective.all_role_ref_as_leading_adjective
432
444
  has_one :trailing_adjective, :class => Adjective # See Adjective.all_role_ref_as_trailing_adjective
433
445
  end
@@ -15,10 +15,48 @@ module ActiveFacts
15
15
  # There may be more than one Player for the same Concept. If adjectives or role
16
16
  # names don't make such duplicates unambiguous, subscripts will be generated.
17
17
  # Thus, the verbalisation context must be completely populated before subscript
18
- # generation, which must be before any verbalisation occurs.
18
+ # generation, which must be before any Player name gets verbalised.
19
19
  #
20
20
  # When a Player occurs in a Join, it corresponds to one Join Node of that Join.
21
- # Each Player has one or more RoleRefs, which refer to Roles of that Concept.
21
+ # Each such Player has one or more JoinRoles, which refer to roles played by
22
+ # that Concept. Where a join traverses two roles of a ternary fact type, there
23
+ # will be a residual node that has only a single JoinRole with no other meaning.
24
+ # A JoinRole must be for exactly one Player, so is used to identify a Player.
25
+ #
26
+ # When a Player occurs outside a Join, it's identified by a projected RoleRef.
27
+ # REVISIT: This is untrue when a uniqueness constraint is imported from NORMA.
28
+ # In this case no join will be constructed to project the roles of the constrained
29
+ # object type (only the constrained roles will be projected) - this will be fixed.
30
+ #
31
+ # Each constraint (except Ring Constraints) has one or more RoleSequence containing
32
+ # the projected RoleRefs. Each constrained RoleSequence may have an associated Join.
33
+ # If it has a Join, each RoleRef is projected from a JoinRole, otherwise none are.
34
+ #
35
+ # The only type of join possible in a Ring Constraint is a subtyping join, which
36
+ # is always implicit and unambiguous, so is never instantiated.
37
+ #
38
+ # A constrained RoleSequence that has no explicit Join may have an implicit join,
39
+ # as per ORM2, when the roles aren't in the same fact type. These implicit joins
40
+ # are over only one Concept, by traversing a single FactType (and possibly,
41
+ # multiple TypeInheritance FactTypes) for each RoleRef. Note however that when
42
+ # the Concept is an objectified Fact Type, the FactType traversed might be a
43
+ # phantom of the objectification. In the case of implicit joins, each Player is
44
+ # identified by the projected RoleRef, except for the joined-over Concept whose
45
+ # Player is... well, read the next paragraph!
46
+ #
47
+ # REVISIT: I believe that the foregoing paragraph is out of date, except with
48
+ # respect to PresenceConstraints imported from NORMA (both external mandatory
49
+ # and external uniqueness constraints). The joined-over Player in a UC is
50
+ # identified by its RoleRefs in the RoleSequence of the Fact Type's preferred
51
+ # reading. Subtyping joins in a mandatory constraint will probably malfunction.
52
+ # However, all other such joins are expliciti, and these should be also.
53
+ #
54
+ # For a SetComparisonConstraint, there are two or more constrained RoleSequences.
55
+ # The matching RoleRefs (by Ordinal position) are for joined players, that is,
56
+ # one individual instance plays both roles. The RoleRefs must (now) be for the
57
+ # same Concept (no implicit subtyping Join is allowed). Instead, the input modules
58
+ # find the closest common supertype and create explicit JoinSteps so its roles
59
+ # can be projected.
22
60
  #
23
61
  # When expanding Reading text however, the RoleRefs in the reading's RoleSequence
24
62
  # may be expected not to be attached to the Players for that reading. Instead,
@@ -29,8 +67,9 @@ module ActiveFacts
29
67
  class Verbaliser
30
68
  # Verbalisation context:
31
69
  attr_reader :players
32
- attr_reader :player_by_role_ref
33
- attr_reader :player_by_join_node
70
+ attr_reader :player_by_join_role # Used for each join
71
+ attr_reader :player_joined_over # Used when there's an implicit join
72
+ attr_reader :player_by_role_ref # Used when a constrained role sequence has no join
34
73
 
35
74
  # The projected role references over which we're verbalising
36
75
  attr_reader :role_refs
@@ -46,8 +85,9 @@ module ActiveFacts
46
85
 
47
86
  # Verbalisation context:
48
87
  @players = []
88
+ @player_by_join_role = {}
49
89
  @player_by_role_ref = {}
50
- @player_by_join_node = {}
90
+ @player_joined_over = nil
51
91
 
52
92
  # Join Verbaliser context:
53
93
  @join = nil
@@ -59,11 +99,12 @@ module ActiveFacts
59
99
  end
60
100
 
61
101
  class Player
62
- attr_accessor :concept, :join_nodes_by_join, :subscript, :role_refs
102
+ attr_accessor :concept, :join_nodes_by_join, :subscript, :join_roles, :role_refs
63
103
  def initialize concept
64
104
  @concept = concept
65
105
  @join_nodes_by_join = {}
66
106
  @subscript = nil
107
+ @join_roles = []
67
108
  @role_refs = []
68
109
  end
69
110
 
@@ -71,32 +112,49 @@ module ActiveFacts
71
112
  # If more than one set of adjectives was used, this player must have been subject to loose binding.
72
113
  # This method is used to decide when subscripts aren't needed.
73
114
  def role_adjuncts
74
- adjuncts = @role_refs.map{|rr| [rr.leading_adjective, rr.role.role_name, rr.trailing_adjective].compact}.uniq.sort
115
+ adjuncts = @role_refs.map{|rr|
116
+ [
117
+ rr.leading_adjective,
118
+ # rr.role.role_name,
119
+ rr.trailing_adjective
120
+ ].compact}.uniq.sort
75
121
  adjuncts.flatten*"_"
76
122
  end
123
+
124
+ def describe
125
+ @concept.name + (@join_nodes_by_join.size > 0 ? " (in #{@join_nodes_by_join.size} joins)" : "")
126
+ end
77
127
  end
78
128
 
79
129
  # Find or create a Player to which we can add this role_ref
80
- def player(role_ref)
81
- # REVISIT: This doesn't work when there are two joins over the underlying role, say each side of a Subset Constraint (see for example Supervision):
82
- jn = (rrj = role_ref.role.all_role_ref.detect{|rr| rr.join_node}) && rrj.join_node
83
- @player_by_role_ref[role_ref] or
84
- @player_by_join_node[jn] or
85
- @players.push(p = Player.new(role_ref.role.concept)) && p
130
+ def player(ref)
131
+ if ref.is_a?(ActiveFacts::Metamodel::JoinRole)
132
+ @player_by_join_role[ref] or
133
+ @players.push(p = Player.new(ref.role.concept)) && p
134
+ else
135
+ @player_by_role_ref[ref] or
136
+ ref.join_role && @player_by_join_role[ref.join_role] or
137
+ @players.push(p = Player.new(ref.role.concept)) && p
138
+ end
139
+ end
140
+
141
+ def add_join_role player, join_role
142
+ return if player.join_roles.include?(join_role)
143
+ jn = join_role.join_node
144
+ if jn1 = player.join_nodes_by_join[jn.join] and jn1 != jn
145
+ raise "Player for #{player.concept.name} may only have one join node per join, not #{jn1.concept.name} and #{jn.concept.name}"
146
+ end
147
+ player.join_nodes_by_join[jn.join] = jn
148
+ @player_by_join_role[join_role] = player
149
+ player.join_roles << join_role
86
150
  end
87
151
 
88
152
  # Add a RoleRef to an existing Player
89
153
  def add_role_player player, role_ref
90
154
  #debug :subscript, "Adding role_ref #{role_ref.object_id} to player #{player.object_id}"
91
- if jn = role_ref.join_node
92
- if jn1 = player.join_nodes_by_join[jn.join] and jn1 != jn
93
- raise "Player for #{player.concept.name} may only have one join node per join, not #{jn1.concept.name} and #{jn.concept.name}"
94
- end
95
- player.join_nodes_by_join[jn.join] = jn
96
- @player_by_join_node[jn] = player
97
- end
98
-
99
- if !player.role_refs.include?(role_ref)
155
+ if jr = role_ref.join_role
156
+ add_join_role(player, jr)
157
+ elsif !player.role_refs.include?(role_ref)
100
158
  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"
101
159
  player.role_refs.push(role_ref)
102
160
  @player_by_role_ref[role_ref] = player
@@ -147,19 +205,47 @@ module ActiveFacts
147
205
  end
148
206
  end
149
207
 
208
+ def join_roles_have_same_player join_roles
209
+ return if join_roles.empty?
210
+
211
+ # If any of these join_roles are for a known player, use that, else make a new player.
212
+ existing_players = join_roles.map{|jr| @player_by_join_role[jr] }.compact.uniq
213
+ if existing_players.size > 1
214
+ raise "Can't join these roles to more than one existing player: #{existing_players.map{|p|p.concept.name}*', '}!"
215
+ end
216
+ p = existing_players[0] || player(join_roles[0])
217
+ debugger if join_roles.detect{|jr| jr.role.concept != p.concept }
218
+ debug :subscript, "Joining roles to #{p.describe}" do
219
+ join_roles.each do |jr|
220
+ debug :subscript, "#{jr.describe}" do
221
+ add_join_role p, jr
222
+ end
223
+ end
224
+ end
225
+ end
226
+
150
227
  # These RoleRefs are all for the same player. Find whether any of them has a player already
151
228
  def role_refs_have_same_player role_refs
152
229
  role_refs = role_refs.is_a?(Array) ? role_refs : role_refs.all_role_ref.to_a
153
230
  return if role_refs.empty?
231
+
154
232
  # If any of these role_refs are for a known player, use that, else make a new player.
155
233
  existing_players =
156
- role_refs.map{|rr| @player_by_role_ref[rr] || @player_by_join_node[rr.join_node] }.compact.uniq
234
+ role_refs.map{|rr| @player_by_role_ref[rr] || @player_by_join_role[rr.join_role] }.compact.uniq
157
235
  if existing_players.size > 1
158
236
  raise "Can't join these role_refs to more than one existing player: #{existing_players.map{|p|p.concept.name}*', '}!"
159
237
  end
160
238
  p = existing_players[0] || player(role_refs[0])
239
+
161
240
  debug :subscript, "#{existing_players[0] ? 'Adding to existing' : 'Creating new'} player for #{role_refs.map{|rr| rr.role.concept.name}.uniq*', '}" do
162
241
  role_refs.each do |rr|
242
+ unless p.concept == rr.role.concept
243
+ # This happens in SubtypePI because uniqueness constraint is built without its implicit subtyping join.
244
+ # For now, explode only if there's no common supertype:
245
+ if 0 == (p.concept.supertypes_transitive & rr.role.concept.supertypes_transitive).size
246
+ raise "REVISIT: Internal error, trying to add role of #{rr.role.concept.name} to player #{p.concept.name}"
247
+ end
248
+ end
163
249
  add_role_player(p, rr)
164
250
  end
165
251
  end
@@ -167,9 +253,11 @@ module ActiveFacts
167
253
 
168
254
  def create_subscripts
169
255
  # Create subscripts, where necessary
256
+ @players.each { |p| p.subscript = nil } # Wipe subscripts
170
257
  @players.
171
258
  map{|p| [p, p.concept] }.
172
259
  each do |player, concept|
260
+ next if player.subscript # Done previously
173
261
  dups = @players.select{|p| p.concept == concept && p.role_adjuncts == player.role_adjuncts }
174
262
  if dups.size == 1
175
263
  debug :subscript, "No subscript needed for #{concept.name}"
@@ -188,7 +276,7 @@ module ActiveFacts
188
276
  # and also define adjectives by using the hyphenated form (on at least the first occurrence).
189
277
  def expand_reading(reading, frequency_constraints = [], define_role_names = nil, value_constraints = [], &subscript_block)
190
278
  reading.expand(frequency_constraints, define_role_names, value_constraints) do |role_ref|
191
- (p = player(role_ref) and p.subscript) ? "(#{p.subscript})" : ""
279
+ (!(role_ref.role.role_name and define_role_names) and p = player(role_ref) and p.subscript) ? "(#{p.subscript})" : ""
192
280
  end
193
281
  end
194
282
 
@@ -197,7 +285,6 @@ module ActiveFacts
197
285
  def role_refs_are_subtype_joined roles
198
286
  role_refs = roles.is_a?(Array) ? roles : roles.all_role_ref.to_a
199
287
  role_refs_by_concept = role_refs.inject({}) { |h, r| (h[r.role.concept] ||= []) << r; h }
200
- # debugger if role_refs_by_concept.size > 1
201
288
  role_refs_by_concept.values.each { |rrs| role_refs_have_same_player(rrs) }
202
289
  end
203
290
 
@@ -212,11 +299,11 @@ module ActiveFacts
212
299
  role_refs_have_same_player(role_refs)
213
300
  end
214
301
 
215
- def prepare_role_sequence role_sequence
302
+ def prepare_role_sequence role_sequence, join_over = nil
216
303
  @role_refs = role_sequence.is_a?(Array) ? role_sequence : role_sequence.all_role_ref.to_a
217
304
 
218
- if jrr = @role_refs.detect{|rr| rr.join_node}
219
- return prepare_join_players(jrr.join_node.join)
305
+ if jrr = @role_refs.detect{|rr| rr.join_role && rr.join_role.join_node}
306
+ return prepare_join_players(jrr.join_role.join_node.join)
220
307
  end
221
308
 
222
309
  # Ensure that all the joined-over role_refs are indexed for subscript generation.
@@ -229,7 +316,7 @@ module ActiveFacts
229
316
  prrs = fact_type.preferred_reading.role_sequence.all_role_ref
230
317
  residual_roles = fact_type.all_role.select{|r| !@role_refs.detect{|rr| rr.role == r} }
231
318
  residual_roles.each do |role|
232
- debug :subscript, "Adding residual role for #{role.concept.name} not covered in role sequence"
319
+ debug :subscript, "Adding residual role for #{role.concept.name} (in #{fact_type.default_reading}) not covered in role sequence"
233
320
  preferred_role_ref = prrs.detect{|rr| rr.role == role}
234
321
  if p = @player_by_role_ref[preferred_role_ref] and !p.role_refs.include?(preferred_role_ref)
235
322
  raise "Adding DUPLICATE residual role for #{role.concept.name}"
@@ -240,47 +327,66 @@ module ActiveFacts
240
327
  end
241
328
 
242
329
  def prepare_join_players join
243
- debug :subscript, "Indexing roles of fact types in #{@join_steps.size} join steps" do
330
+ debug :subscript, "Indexing roles of fact types in #{join.all_join_step.size} join steps" do
244
331
  join_steps = []
245
332
  # Register all references to each join node as being for the same player:
246
333
  join.all_join_node.sort_by{|jn| jn.ordinal}.each do |join_node|
247
- role_refs_have_same_player(join_node.all_role_ref.to_a)
248
- join_steps += join_node.all_join_step_as_input_join_node.to_a + join_node.all_join_step_as_output_join_node.to_a
334
+ debug :subscript, "Adding Roles of #{join_node.describe}" do
335
+ join_roles_have_same_player(join_node.all_join_role.to_a)
336
+ join_steps = join_steps | join_node.all_join_step
337
+ end
249
338
  end
339
+
340
+ =begin
250
341
  # For each fact type traversed, register a player for each role *not* linked to this join
251
342
  # REVISIT: Using the preferred_reading role_ref is wrong here; the same preferred_reading might occur twice,
252
343
  # so the respective concept will need more than one Player and will be subscripted to keep them from being joined.
253
344
  # Accordingly, there must be a join step for each such role, and to enforce that, I raise an exception here on duplication.
254
- join_steps.map{|js|js.fact_type}.uniq.each do |fact_type|
345
+ # This isn't needed now all JoinNodes have at least one JoinRole
346
+
347
+ join_steps.map do |js|
348
+ if js.fact_type.is_a?(ActiveFacts::Metamodel::ImplicitFactType)
349
+ js.fact_type.role.fact_type
350
+ else
351
+ js.fact_type
352
+ end
353
+ end.uniq.each do |fact_type|
354
+ #join_steps.map{|js|js.fact_type}.uniq.each do |fact_type|
255
355
  next if fact_type.is_a?(ActiveFacts::Metamodel::ImplicitFactType)
256
- prrs = fact_type.preferred_reading.role_sequence.all_role_ref
257
- residual_roles = fact_type.all_role.select{|r| !r.all_role_ref.detect{|rr| rr.join_node && rr.join_node.join == join} }
258
- residual_roles.each do |r|
259
- debug :subscript, "Adding residual role for #{r.concept.name} not covered in join"
260
- preferred_role_ref = prrs.detect{|rr| rr.role == r}
261
- if p = @player_by_role_ref[preferred_role_ref] and !p.role_refs.include?(preferred_role_ref)
262
- raise "Adding DUPLICATE residual role for #{r.concept.name} not covered in join"
356
+
357
+ debug :subscript, "Residual roles in '#{fact_type.default_reading}' are" do
358
+ prrs = fact_type.preferred_reading.role_sequence.all_role_ref
359
+ residual_roles = fact_type.all_role.select{|r| !r.all_role_ref.detect{|rr| rr.join_node && rr.join_node.join == join} }
360
+ residual_roles.each do |r|
361
+ debug :subscript, "Adding residual role for #{r.concept.name} (in #{fact_type.default_reading}) not covered in join"
362
+ preferred_role_ref = prrs.detect{|rr| rr.role == r}
363
+ if p = @player_by_role_ref[preferred_role_ref] and !p.role_refs.include?(preferred_role_ref)
364
+ raise "Adding DUPLICATE residual role for #{r.concept.name} not covered in join"
365
+ end
366
+ role_refs_have_same_player([preferred_role_ref])
263
367
  end
264
- role_refs_have_same_player([preferred_role_ref])
265
368
  end
266
369
  end
370
+ =end
267
371
  end
268
372
  end
269
373
 
270
374
  def verbalise_over_role_sequence role_sequence, joiner = ' and ', role_proximity = :both
271
375
  @role_refs = role_sequence.is_a?(Array) ? role_sequence : role_sequence.all_role_ref.to_a
272
376
 
273
- if jrr = role_refs.detect{|rr| rr.join_node}
274
- return verbalise_join(jrr.join_node.join)
377
+ if jrr = role_refs.detect{|rr| rr.join_role}
378
+ return verbalise_join(jrr.join_role.join_node.join)
275
379
  end
276
380
 
277
381
  # First, figure out whether there's a join:
278
382
  join_over, joined_roles = *Metamodel.join_roles_over(role_sequence.all_role_ref.map{|rr|rr.role}, role_proximity)
279
383
 
280
- fact_types = @role_refs.map{|rr| rr.role.fact_type}.uniq
384
+ role_by_fact_type = {}
385
+ fact_types = @role_refs.map{|rr| ft = rr.role.fact_type; role_by_fact_type[ft] ||= rr.role; ft}.uniq
281
386
  readings = fact_types.map do |fact_type|
282
387
  name_substitutions = []
283
- reading = fact_type.preferred_reading
388
+ # Choose a reading that start with the (first) role which caused us to emit this fact type:
389
+ reading = fact_type.reading_preferably_starting_with_role(role_by_fact_type[fact_type])
284
390
  if join_over and # Find a reading preferably starting with the joined_over role:
285
391
  joined_role = fact_type.all_role.select{|r| join_over.subtypes_transitive.include?(r.concept)}[0]
286
392
  reading = fact_type.reading_preferably_starting_with_role joined_role
@@ -295,46 +401,56 @@ module ActiveFacts
295
401
  next unless subscript = player.subscript
296
402
  debug :subscript, "Need to apply subscript #{subscript} to #{rr.role.concept.name}"
297
403
  end
298
- role_refs = @player_by_role_ref.keys.select{|rr| rr.role.fact_type == fact_type}
299
- expand_reading_text(nil, reading.text, reading.role_sequence, role_refs)
404
+ player_by_role = {}
405
+ @player_by_role_ref.keys.each{|rr| player_by_role[rr.role] = @player_by_role_ref[rr] if rr.role.fact_type == fact_type }
406
+ #role_refs = @player_by_role_ref.keys.select{|rr| rr.role.fact_type == fact_type}
407
+ expand_reading_text(nil, reading.text, reading.role_sequence, player_by_role)
300
408
  #reading.expand(name_substitutions)
301
409
  end
302
410
  joiner ? readings*joiner : readings
303
411
  end
304
412
 
305
413
  # Expand this reading (or partial reading, during contraction)
306
- def expand_reading_text(step, text, role_sequence, role_refs = [])
414
+ def expand_reading_text(step, text, role_sequence, player_by_role = {})
415
+ if !player_by_role.empty? and !player_by_role.is_a?(Hash) || player_by_role.keys.detect{|k| !k.is_a?(ActiveFacts::Metamodel::Role)}
416
+ debugger
417
+ raise "Need to change this call to expand_reading_text to pass a role->join_node hash"
418
+ end
307
419
  rrs = role_sequence.all_role_ref_in_order
308
- debug :subscript, "expanding #{text} with #{role_sequence.describe}" do
420
+ debug :subscript, "expanding '#{text}' with #{role_sequence.describe}" do
309
421
  text.gsub(/\{(\d)\}/) do
310
422
  role_ref = rrs[$1.to_i]
311
423
  # REVISIT: We may need to use the step's role_refs to expand the role players here, not the reading's one (extra adjectives?)
312
424
  # REVISIT: There's no way to get literals to be emitted here (value join step?)
313
425
 
426
+ player = player_by_role[role_ref.role]
427
+
428
+ =begin
314
429
  rr = role_refs.detect{|rr| rr.role == role_ref.role} || role_ref
315
430
 
316
431
  player = @player_by_role_ref[rr] and subscript = player.subscript
317
432
  if !subscript and
318
433
  pp = @players.select{|p|p.concept == rr.role.concept} and
319
434
  pp.detect{|p|p.subscript}
320
- raise "Internal error: Subscripted players (of the same concept #{p.concept.name}) when this player isn't subscripted"
435
+ # raise "Internal error: Subscripted players (of the same concept #{pp[0].concept.name}) when this player isn't subscripted"
321
436
  end
437
+ =end
322
438
 
323
- subscripted_player(rr, role_ref) +
439
+ subscripted_player(role_ref, player && player.subscript) +
324
440
  objectification_verbalisation(role_ref.role.concept)
325
441
  end
326
442
  end
327
443
  end
328
444
 
329
- def subscripted_player role_ref, reading_role_ref = nil
330
- if player = @player_by_role_ref[role_ref] and subscript = player.subscript
445
+ def subscripted_player role_ref, subscript = nil
446
+ if subscript
331
447
  debug :subscript, "Need to apply subscript #{subscript} to #{role_ref.role.concept.name}"
332
448
  end
333
449
  concept = role_ref.role.concept
334
450
  [
335
- (reading_role_ref || role_ref).leading_adjective,
451
+ role_ref.leading_adjective,
336
452
  concept.name,
337
- (reading_role_ref || role_ref).trailing_adjective
453
+ role_ref.trailing_adjective
338
454
  ].compact*' ' +
339
455
  (subscript ? "(#{subscript})" : '')
340
456
  end
@@ -352,11 +468,10 @@ module ActiveFacts
352
468
 
353
469
  @join_nodes = join.all_join_node.sort_by{|jn| jn.ordinal}
354
470
 
355
- @join_steps = @join_nodes.map{|jn| jn.all_join_step_as_input_join_node.to_a + jn.all_join_step_as_output_join_node.to_a }.flatten.uniq
471
+ @join_steps = @join_nodes.map{|jn| jn.all_join_step }.flatten.uniq
356
472
  @join_steps_by_join_node = @join_nodes.
357
473
  inject({}) do |h, jn|
358
- jn.all_join_step_as_input_join_node.each{|js| (h[jn] ||= []) << js}
359
- jn.all_join_step_as_output_join_node.each{|js| (h[jn] ||= []) << js}
474
+ jn.all_join_step.each{|js| (h[jn] ||= []) << js}
360
475
  h
361
476
  end
362
477
  end
@@ -365,12 +480,12 @@ module ActiveFacts
365
480
  def step_completed(step)
366
481
  @join_steps.delete(step)
367
482
 
368
- input_node = step.input_join_node
483
+ input_node = step.input_join_role.join_node
369
484
  steps = @join_steps_by_join_node[input_node]
370
485
  steps.delete(step)
371
486
  @join_steps_by_join_node.delete(input_node) if steps.empty?
372
487
 
373
- output_node = step.output_join_node
488
+ output_node = step.output_join_role.join_node
374
489
  if (input_node != output_node)
375
490
  steps = @join_steps_by_join_node[output_node]
376
491
  steps.delete(step)
@@ -401,7 +516,7 @@ module ActiveFacts
401
516
  if next_step.is_objectification_step
402
517
  # if this objectification plays any roles (other than its FT roles) in remaining steps, use one of those first:
403
518
  fact_type = next_step.fact_type.role.fact_type
404
- jn = [next_step.input_join_node, next_step.output_join_node].detect{|jn| jn.concept == fact_type.entity_type}
519
+ jn = [next_step.input_join_role.join_node, next_step.output_join_role.join_node].detect{|jn| jn.concept == fact_type.entity_type}
405
520
  sr = @join_steps_by_join_node[jn].reject{|t| t.fact_type.role and t.fact_type.role.fact_type == fact_type}
406
521
  next_step = sr[0] if sr.size > 0
407
522
  end
@@ -417,15 +532,15 @@ module ActiveFacts
417
532
  # Find whether last role has no following text, and its ordinal
418
533
  (reading.text =~ /\{([0-9])\}$/) &&
419
534
  # This reading's RoleRef for that role:
420
- (role_ref = reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}) &&
535
+ (role_ref = reading.role_sequence.all_role_ref_in_order[$1.to_i]) &&
421
536
  # was that RoleRef for the upcoming node?
422
- role_ref.role.all_role_ref.detect{|rr| rr.join_node == next_node}
537
+ role_ref.role.concept == next_node.concept
423
538
  end
424
539
 
425
540
  def reading_starts_with_node(reading, next_node)
426
541
  reading.text =~ /^\{([0-9])\}/ and
427
542
  role_ref = reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i} and
428
- role_ref.role.all_role_ref.detect{|rr| rr.join_node == next_node}
543
+ role_ref.role.concept == next_node.concept
429
544
  end
430
545
 
431
546
  # The last reading we emitted ended with the object type name for next_node.
@@ -456,8 +571,8 @@ module ActiveFacts
456
571
  detect do |js|
457
572
  # The objectifying entity type should always be the input_join_node here, but be safe:
458
573
  js.is_objectification_step and
459
- (objectified_node = js.input_join_node).concept == concept ||
460
- (objectified_node = js.output_join_node).concept == concept
574
+ (objectified_node = js.input_join_role.join_node).concept == concept ||
575
+ (objectified_node = js.output_join_role.join_node).concept == concept
461
576
  end
462
577
  return ''
463
578
  end
@@ -471,7 +586,7 @@ module ActiveFacts
471
586
  @join_steps.
472
587
  detect{|js|
473
588
  js.is_objectification_step and
474
- js.input_join_node.concept == concept || js.output_join_node.concept == concept
589
+ js.input_join_role.join_node.concept == concept || js.output_join_role.join_node.concept == concept
475
590
  }
476
591
  steps << other_step
477
592
  debug :join, "Emitting objectification step allows deleting #{other_step.describe}"
@@ -479,10 +594,17 @@ module ActiveFacts
479
594
  end
480
595
 
481
596
  # Find all references to roles in this objectified fact type which are relevant to the join nodes of these steps:
482
- role_refs = steps.map{|step| [step.input_join_node, step.output_join_node].map{|jn| jn.all_role_ref.detect{|rr| rr.role.fact_type == concept.fact_type}}}.flatten.compact.uniq
597
+ player_by_role = {}
598
+ steps.each do |join_step|
599
+ join_step.all_join_role.to_a.map do |jr|
600
+ player_by_role[jr.role] = @player_by_join_role[jr]
601
+ end
602
+ end
603
+
604
+ # 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
483
605
 
484
606
  reading = concept.fact_type.preferred_reading
485
- " (where #{expand_reading_text(objectification_step, reading.text, reading.role_sequence, role_refs)})"
607
+ " (where #{expand_reading_text(objectification_step, reading.text, reading.role_sequence, player_by_role)})"
486
608
  end
487
609
 
488
610
  def elided_objectification(next_step, fact_type, last_is_contractable, next_node)
@@ -499,27 +621,27 @@ module ActiveFacts
499
621
  # Find which role occurs last in the reading, and which Join Node is attached
500
622
  reading.text =~ /\{(\d)\}[^{]*\Z/
501
623
  last_role_ref = reading.role_sequence.all_role_ref_in_order[$1.to_i]
502
- exit_node = @join_nodes.detect{|jn| jn.all_role_ref.detect{|rr| rr.role == last_role_ref.role}}
624
+ exit_node = @join_nodes.detect{|jn| jn.all_join_role.detect{|jr| jr.role == last_role_ref.role}}
503
625
  exit_step = nil
504
626
 
505
627
  while other_step =
506
628
  @join_steps.
507
629
  detect{|js|
508
630
  next unless js.is_objectification_step
509
- next unless js.input_join_node.concept == fact_type.entity_type || js.output_join_node.concept == fact_type.entity_type
510
- exit_step = js if js.output_join_node == exit_node
631
+ next unless js.input_join_role.join_node.concept == fact_type.entity_type || js.output_join_role.join_node.concept == fact_type.entity_type
632
+ exit_step = js if js.output_join_role.join_node == exit_node
511
633
  true
512
634
  }
513
635
  debug :join, "Emitting objectified FT allows deleting #{other_step.describe}"
514
636
  step_completed(other_step)
515
637
  end
516
- [ reading, exit_step ? exit_step.input_join_node : exit_node, exit_step, last_is_contractable]
638
+ [ reading, exit_step ? exit_step.input_join_role.join_node : exit_node, exit_step, last_is_contractable]
517
639
  end
518
640
 
519
641
  def verbalise_join join
520
642
  prepare_join join
521
643
  readings = ''
522
- next_node = @role_refs[0].join_node # Choose a place to start
644
+ next_node = @role_refs[0].join_role.join_node # Choose a place to start
523
645
  last_is_contractable = false
524
646
  debug :join, "Join Nodes are #{@join_nodes.map{|jn| jn.describe }.inspect}, Join Steps are #{@join_steps.map{|js| js.describe }.inspect}" do
525
647
  until @join_steps.empty?
@@ -535,25 +657,17 @@ module ActiveFacts
535
657
  end
536
658
 
537
659
  if next_step
538
- debug :join, "Chose #{next_step.describe} because it's contractable against last node #{next_node.all_role_ref.to_a[0].role.concept.name} using #{next_reading.expand}"
539
-
540
- step_ft = next_step.fact_type.is_a?(ActiveFacts::Metamodel::ImplicitFactType) ? next_step.fact_type.role.fact_type : next_step.fact_type
541
- step_role_refs = # for the two join nodes of this step, get the relevant role_refs for roles in this fact type
542
- [next_step.input_join_node, next_step.output_join_node].
543
- uniq.
544
- map{|jn| jn.all_role_ref.select{|rr| rr.role.fact_type == step_ft } }.
545
- flatten.uniq
546
- readings += expand_contracted_text(next_step, next_reading, step_role_refs)
660
+ debug :join, "Chose #{next_step.describe} because it's contractable against last node #{next_node.concept.name} using #{next_reading.expand}"
661
+
662
+ player_by_role =
663
+ next_step.all_join_role.inject({}) {|h, jr| h[jr.role] = @player_by_join_role[jr]; h }
664
+ readings += expand_contracted_text(next_step, next_reading, player_by_role)
547
665
  step_completed(next_step)
548
666
  else
549
667
  next_step = choose_step(next_node) if !next_step
550
668
 
551
- step_ft = next_step.fact_type.is_a?(ActiveFacts::Metamodel::ImplicitFactType) ? next_step.fact_type.role.fact_type : next_step.fact_type
552
- step_role_refs = # for the two join nodes of this step, get the relevant role_refs for roles in this fact type
553
- [next_step.input_join_node, next_step.output_join_node].
554
- uniq.
555
- map{|jn| jn.all_role_ref.select{|rr| rr.role.fact_type == step_ft } }.
556
- flatten.uniq
669
+ player_by_role =
670
+ next_step.all_join_role.inject({}) {|h, jr| h[jr.role] = @player_by_join_role[jr]; h }
557
671
 
558
672
  if next_step.is_unary_step
559
673
  # Objectified unaries get emitted as unaries, not as objectifications:
@@ -561,28 +675,34 @@ module ActiveFacts
561
675
  rr = next_step.input_join_node.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ImplicitFactType) }
562
676
  next_reading = rr.role.fact_type.role.fact_type.preferred_reading
563
677
  readings += " and " unless readings.empty?
564
- readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, step_role_refs)
678
+ readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, player_by_role)
565
679
  step_completed(next_step)
566
680
  elsif next_step.is_objectification_step
567
681
  fact_type = next_step.fact_type.role.fact_type
682
+
683
+ # This objectification step is over an implicit fact type, so player_by_role won't have all the players
684
+ # Add the players of other roles associated with steps from this objectified player.
685
+ objectified_node = next_step.input_join_role.join_node
686
+ raise "Assumption violated that the objectification is the input join role" unless objectified_node.concept.fact_type
687
+ objectified_node.all_join_step.map do |other_step|
688
+ (other_step.all_incidental_join_role.to_a + [other_step.output_join_role]).map do |jr|
689
+ player_by_role[jr.role] = @player_by_join_role[jr]
690
+ end
691
+ end
692
+
568
693
  if last_is_contractable and next_node.concept.is_a?(EntityType) and next_node.concept.fact_type == fact_type
569
694
  # The last reading we emitted ended with the name of the objectification of this fact type, so we can contract the objectification
570
- # if (n = next_step.input_join_node).concept == fact_type.entity_type ||
571
- # (n = next_step.output_join_node).concept == fact_type.entity_type
572
- # debugger
573
- # p n.concept.name # This is the join_node which has the role_ref (and subscript!) we should use for the objectification_verbalisation
574
- # end
575
- # REVISIT: Do we need to use step_role_refs here (if this objectification is traversed twice and so is subscripted)
695
+ # REVISIT: Do we need to use player_by_role here (if this objectification is traversed twice and so is subscripted)
576
696
  readings += objectification_verbalisation(fact_type.entity_type)
577
697
  else
578
698
  # This objectified fact type does not need to be made explicit.
579
699
  next_reading, next_node, next_step, last_is_contractable =
580
700
  *elided_objectification(next_step, fact_type, last_is_contractable, next_node)
581
701
  if last_is_contractable
582
- readings += expand_contracted_text(next_step, next_reading, step_role_refs)
702
+ readings += expand_contracted_text(next_step, next_reading, player_by_role)
583
703
  else
584
704
  readings += " and " unless readings.empty?
585
- readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, step_role_refs)
705
+ readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, player_by_role)
586
706
  end
587
707
  # No need to continue if we just deleted the last step
588
708
  break if @join_steps.empty?
@@ -593,21 +713,19 @@ module ActiveFacts
593
713
  # Prefer a reading that starts with the player of next_node
594
714
  next_reading = fact_type.all_reading_by_ordinal.
595
715
  detect do |reading|
596
- reading.text =~ /^\{([0-9])\}/ and
597
- role_ref = reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i} and
598
- role_ref.role.all_role_ref.detect{|rr| rr.join_node == next_node}
716
+ reading_starts_with_node(reading, next_node)
599
717
  end || fact_type.preferred_reading
600
718
  # REVISIT: If this join step and reading has role references with adjectives, we need to expand using those
601
719
  readings += " and " unless readings.empty?
602
- readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, step_role_refs)
720
+ readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, player_by_role)
603
721
  step_completed(next_step)
604
722
  end
605
723
  end
606
724
 
607
725
  # Continue from this step with the node having the most steps remaining
608
- input_steps = @join_steps_by_join_node[next_step.input_join_node] || []
609
- output_steps = @join_steps_by_join_node[next_step.output_join_node] || []
610
- next_node = input_steps.size > output_steps.size ? next_step.input_join_node : next_step.output_join_node
726
+ input_steps = @join_steps_by_join_node[input_node = next_step.input_join_role.join_node] || []
727
+ output_steps = @join_steps_by_join_node[output_node = next_step.output_join_role.join_node] || []
728
+ next_node = input_steps.size > output_steps.size ? input_node : output_node
611
729
  # Prepare for possible contraction following:
612
730
  last_is_contractable = next_reading && node_contractable_against_reading(next_node, next_reading)
613
731