activefacts 0.8.8 → 0.8.9
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/cql +90 -31
- data/download.html +2 -1
- data/examples/CQL/Diplomacy.cql +8 -8
- data/examples/CQL/Insurance.cql +3 -3
- data/examples/CQL/Metamodel.cql +31 -16
- data/examples/CQL/MetamodelNext.cql +30 -18
- data/examples/CQL/Orienteering.cql +2 -2
- data/examples/CQL/OrienteeringER.cql +6 -6
- data/examples/CQL/SchoolActivities.cql +1 -1
- data/examples/CQL/ServiceDirector.cql +3 -3
- data/examples/CQL/Supervision.cql +3 -2
- data/examples/CQL/Tests.Test5.Load.cql +1 -1
- data/examples/CQL/WaiterTips.cql +1 -1
- data/lib/activefacts/cql/FactTypes.treetop +14 -2
- data/lib/activefacts/cql/compiler/constraint.rb +69 -36
- data/lib/activefacts/cql/compiler/fact.rb +142 -71
- data/lib/activefacts/cql/compiler/fact_type.rb +1 -1
- data/lib/activefacts/cql/compiler/reading.rb +35 -8
- data/lib/activefacts/cql/compiler/shared.rb +4 -3
- data/lib/activefacts/generate/cql.rb +16 -5
- data/lib/activefacts/generate/ordered.rb +1 -1
- data/lib/activefacts/input/orm.rb +136 -57
- data/lib/activefacts/support.rb +40 -16
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +149 -58
- data/lib/activefacts/vocabulary/metamodel.rb +23 -11
- data/lib/activefacts/vocabulary/verbaliser.rb +223 -105
- data/spec/cql/samples_spec.rb +30 -6
- data/spec/cql_mysql_spec.rb +1 -2
- data/spec/norma_ruby_sql_spec.rb +0 -1
- metadata +4 -4
data/lib/activefacts/version.rb
CHANGED
@@ -89,7 +89,11 @@ module ActiveFacts
|
|
89
89
|
|
90
90
|
class RoleRef
|
91
91
|
def describe
|
92
|
-
role_name
|
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
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
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
|
-
|
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, "
|
522
|
-
|
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
|
-
|
527
|
-
|
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.
|
533
|
-
debug :join, "
|
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
|
-
|
552
|
-
|
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
|
-
|
555
|
-
raise "
|
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
|
-
|
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
|
-
|
570
|
-
|
571
|
-
raise "Join Step
|
572
|
-
raise "Join Step
|
573
|
-
|
574
|
-
|
575
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
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
|
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 :
|
33
|
-
attr_reader :
|
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
|
-
@
|
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|
|
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(
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
@
|
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
|
92
|
-
|
93
|
-
|
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] || @
|
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 #{
|
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
|
-
|
248
|
-
|
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
|
-
|
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
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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.
|
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
|
-
|
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
|
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
|
-
|
299
|
-
|
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,
|
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 #{
|
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(
|
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,
|
330
|
-
if
|
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
|
-
|
451
|
+
role_ref.leading_adjective,
|
336
452
|
concept.name,
|
337
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
460
|
-
(objectified_node = js.
|
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.
|
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
|
-
|
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,
|
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.
|
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.
|
510
|
-
exit_step = js if js.
|
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.
|
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.
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
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
|
-
|
552
|
-
|
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,
|
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
|
-
#
|
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,
|
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,
|
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
|
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,
|
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.
|
609
|
-
output_steps = @join_steps_by_join_node[next_step.
|
610
|
-
next_node = input_steps.size > output_steps.size ?
|
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
|
|