activefacts 0.8.6 → 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/Manifest.txt +33 -2
  2. data/README.rdoc +30 -36
  3. data/Rakefile +16 -20
  4. data/bin/afgen +17 -11
  5. data/bin/cql +313 -36
  6. data/download.html +43 -19
  7. data/examples/CQL/Address.cql +15 -15
  8. data/examples/CQL/Blog.cql +8 -8
  9. data/examples/CQL/CompanyDirectorEmployee.cql +6 -5
  10. data/examples/CQL/Death.cql +3 -3
  11. data/examples/CQL/Diplomacy.cql +48 -0
  12. data/examples/CQL/Genealogy.cql +41 -41
  13. data/examples/CQL/Insurance.cql +311 -0
  14. data/examples/CQL/JoinEquality.cql +35 -0
  15. data/examples/CQL/Marriage.cql +1 -1
  16. data/examples/CQL/Metamodel.cql +290 -185
  17. data/examples/CQL/MetamodelNext.cql +420 -0
  18. data/examples/CQL/Monogamy.cql +24 -0
  19. data/examples/CQL/MonthInSeason.cql +27 -0
  20. data/examples/CQL/Moon.cql +23 -0
  21. data/examples/CQL/MultiInheritance.cql +4 -4
  22. data/examples/CQL/NonRoleId.cql +14 -0
  23. data/examples/CQL/OddIdentifier.cql +18 -0
  24. data/examples/CQL/OilSupply.cql +24 -24
  25. data/examples/CQL/OneToOnes.cql +17 -0
  26. data/examples/CQL/Orienteering.cql +55 -55
  27. data/examples/CQL/OrienteeringER.cql +58 -0
  28. data/examples/CQL/PersonPlaysGame.cql +2 -2
  29. data/examples/CQL/RedundantDependency.cql +34 -0
  30. data/examples/CQL/SchoolActivities.cql +5 -5
  31. data/examples/CQL/SeparateSubtype.cql +28 -0
  32. data/examples/CQL/ServiceDirector.cql +283 -0
  33. data/examples/CQL/SimplestUnary.cql +2 -2
  34. data/examples/CQL/SubtypePI.cql +11 -11
  35. data/examples/CQL/Supervision.cql +38 -0
  36. data/examples/CQL/Tests.Test5.Load.cql +38 -0
  37. data/examples/CQL/WaiterTips.cql +33 -0
  38. data/examples/CQL/Warehousing.cql +55 -53
  39. data/examples/CQL/WindowInRoomInBldg.cql +9 -9
  40. data/examples/CQL/unit.cql +433 -544
  41. data/examples/index.html +314 -170
  42. data/examples/intro.html +6 -176
  43. data/examples/local.css +8 -4
  44. data/index.html +40 -25
  45. data/lib/activefacts/api/concept.rb +2 -2
  46. data/lib/activefacts/api/constellation.rb +4 -4
  47. data/lib/activefacts/api/instance.rb +2 -2
  48. data/lib/activefacts/api/instance_index.rb +4 -0
  49. data/lib/activefacts/api/numeric.rb +3 -1
  50. data/lib/activefacts/api/role.rb +1 -1
  51. data/lib/activefacts/api/standard_types.rb +23 -16
  52. data/lib/activefacts/api/support.rb +3 -1
  53. data/lib/activefacts/api/vocabulary.rb +4 -0
  54. data/lib/activefacts/cql/CQLParser.treetop +87 -39
  55. data/lib/activefacts/cql/Concepts.treetop +95 -69
  56. data/lib/activefacts/cql/Context.treetop +11 -2
  57. data/lib/activefacts/cql/Expressions.treetop +23 -59
  58. data/lib/activefacts/cql/FactTypes.treetop +141 -95
  59. data/lib/activefacts/cql/Language/English.treetop +33 -21
  60. data/lib/activefacts/cql/LexicalRules.treetop +6 -1
  61. data/lib/activefacts/cql/Terms.treetop +75 -26
  62. data/lib/activefacts/cql/ValueTypes.treetop +52 -54
  63. data/lib/activefacts/cql/compiler.rb +46 -1691
  64. data/lib/activefacts/cql/compiler/constraint.rb +602 -0
  65. data/lib/activefacts/cql/compiler/entity_type.rb +425 -0
  66. data/lib/activefacts/cql/compiler/fact.rb +300 -0
  67. data/lib/activefacts/cql/compiler/fact_type.rb +230 -0
  68. data/lib/activefacts/cql/compiler/reading.rb +832 -0
  69. data/lib/activefacts/cql/compiler/shared.rb +109 -0
  70. data/lib/activefacts/cql/compiler/value_type.rb +104 -0
  71. data/lib/activefacts/cql/parser.rb +132 -81
  72. data/lib/activefacts/generate/cql.rb +397 -274
  73. data/lib/activefacts/generate/oo.rb +13 -12
  74. data/lib/activefacts/generate/ordered.rb +107 -117
  75. data/lib/activefacts/generate/ruby.rb +34 -38
  76. data/lib/activefacts/generate/sql/mysql.rb +62 -45
  77. data/lib/activefacts/generate/sql/server.rb +59 -42
  78. data/lib/activefacts/input/cql.rb +6 -3
  79. data/lib/activefacts/input/orm.rb +991 -557
  80. data/lib/activefacts/persistence/columns.rb +16 -12
  81. data/lib/activefacts/persistence/foreignkey.rb +7 -4
  82. data/lib/activefacts/persistence/index.rb +3 -4
  83. data/lib/activefacts/persistence/reference.rb +5 -2
  84. data/lib/activefacts/support.rb +20 -14
  85. data/lib/activefacts/version.rb +1 -1
  86. data/lib/activefacts/vocabulary.rb +1 -0
  87. data/lib/activefacts/vocabulary/extensions.rb +328 -44
  88. data/lib/activefacts/vocabulary/metamodel.rb +145 -20
  89. data/lib/activefacts/vocabulary/verbaliser.rb +621 -0
  90. data/spec/absorption_spec.rb +4 -4
  91. data/spec/api/value_type.rb +1 -1
  92. data/spec/cql/context_spec.rb +45 -22
  93. data/spec/cql/deontic_spec.rb +88 -0
  94. data/spec/cql/matching_spec.rb +517 -0
  95. data/spec/cql/samples_spec.rb +88 -31
  96. data/spec/cql/unit_spec.rb +58 -37
  97. data/spec/cql_cql_spec.rb +12 -7
  98. data/spec/cql_mysql_spec.rb +3 -7
  99. data/spec/cql_parse_spec.rb +0 -4
  100. data/spec/cql_ruby_spec.rb +1 -4
  101. data/spec/cql_sql_spec.rb +5 -18
  102. data/spec/cql_symbol_tables_spec.rb +3 -0
  103. data/spec/cqldump_spec.rb +0 -2
  104. data/spec/helpers/array_matcher.rb +35 -0
  105. data/spec/helpers/ctrl_c_support.rb +52 -0
  106. data/spec/helpers/diff_matcher.rb +38 -0
  107. data/spec/helpers/file_matcher.rb +5 -3
  108. data/spec/helpers/string_matcher.rb +39 -0
  109. data/spec/helpers/test_parser.rb +13 -0
  110. data/spec/norma_cql_spec.rb +13 -5
  111. data/spec/norma_ruby_spec.rb +11 -3
  112. data/spec/{absorption_ruby_spec.rb → norma_ruby_sql_spec.rb} +37 -32
  113. data/spec/norma_sql_spec.rb +11 -5
  114. data/spec/norma_tables_spec.rb +33 -29
  115. data/spec/spec_helper.rb +4 -1
  116. data/status.html +92 -23
  117. metadata +102 -36
  118. data/lib/activefacts/generate/cql/html.rb +0 -403
@@ -41,12 +41,21 @@ module ActiveFacts
41
41
  value_type
42
42
  end
43
43
 
44
+ class DisplayRoleNamesSetting < String
45
+ value_type
46
+ restrict 'false', 'true'
47
+ end
48
+
44
49
  class EnforcementCode < String
45
50
  value_type :length => 16
46
51
  end
47
52
 
48
- class Exponent < SignedSmallInteger
49
- value_type :length => 32
53
+ class EphemeraURL < String
54
+ value_type
55
+ end
56
+
57
+ class Exponent < SignedInteger
58
+ value_type :length => 16
50
59
  end
51
60
 
52
61
  class FactId < AutoCounter
@@ -65,6 +74,10 @@ module ActiveFacts
65
74
  value_type
66
75
  end
67
76
 
77
+ class JoinId < AutoCounter
78
+ value_type
79
+ end
80
+
68
81
  class Length < UnsignedInteger
69
82
  value_type :length => 32
70
83
  end
@@ -85,8 +98,8 @@ module ActiveFacts
85
98
  value_type
86
99
  end
87
100
 
88
- class Ordinal < UnsignedSmallInteger
89
- value_type :length => 32
101
+ class Ordinal < UnsignedInteger
102
+ value_type :length => 16
90
103
  end
91
104
 
92
105
  class Pronoun < String
@@ -102,10 +115,19 @@ module ActiveFacts
102
115
  value_type
103
116
  end
104
117
 
118
+ class RotationSetting < String
119
+ value_type
120
+ restrict 'left', 'right'
121
+ end
122
+
105
123
  class Scale < UnsignedInteger
106
124
  value_type :length => 32
107
125
  end
108
126
 
127
+ class ShapeId < AutoCounter
128
+ value_type
129
+ end
130
+
109
131
  class Text < String
110
132
  value_type :length => 256
111
133
  end
@@ -114,6 +136,14 @@ module ActiveFacts
114
136
  value_type
115
137
  end
116
138
 
139
+ class X < SignedInteger
140
+ value_type :length => 32
141
+ end
142
+
143
+ class Y < SignedInteger
144
+ value_type :length => 32
145
+ end
146
+
117
147
  class Agent
118
148
  identified_by :agent_name
119
149
  one_to_one :agent_name, :mandatory => true # See AgentName.agent
@@ -129,7 +159,6 @@ module ActiveFacts
129
159
  class Constraint
130
160
  identified_by :constraint_id
131
161
  one_to_one :constraint_id, :mandatory => true # See ConstraintId.constraint
132
- has_one :enforcement # See Enforcement.all_constraint
133
162
  has_one :name # See Name.all_constraint
134
163
  has_one :vocabulary # See Vocabulary.all_constraint
135
164
  end
@@ -145,9 +174,10 @@ module ActiveFacts
145
174
  end
146
175
 
147
176
  class Enforcement
148
- identified_by :enforcement_code
177
+ identified_by :constraint
149
178
  has_one :agent # See Agent.all_enforcement
150
- one_to_one :enforcement_code, :mandatory => true # See EnforcementCode.enforcement
179
+ one_to_one :constraint, :mandatory => true # See Constraint.enforcement
180
+ has_one :enforcement_code, :mandatory => true # See EnforcementCode.all_enforcement
151
181
  end
152
182
 
153
183
  class Fact
@@ -162,6 +192,9 @@ module ActiveFacts
162
192
  one_to_one :fact_type_id, :mandatory => true # See FactTypeId.fact_type
163
193
  end
164
194
 
195
+ class ImplicitFactType < FactType
196
+ end
197
+
165
198
  class Instance
166
199
  identified_by :instance_id
167
200
  has_one :concept, :mandatory => true # See Concept.all_instance
@@ -171,6 +204,34 @@ module ActiveFacts
171
204
  has_one :value # See Value.all_instance
172
205
  end
173
206
 
207
+ class Join
208
+ identified_by :join_id
209
+ one_to_one :join_id, :mandatory => true # See JoinId.join
210
+ has_one :role_sequence, :mandatory => true # See RoleSequence.all_join
211
+ end
212
+
213
+ class JoinNode
214
+ identified_by :join, :ordinal
215
+ has_one :concept, :mandatory => true # See Concept.all_join_node
216
+ has_one :join, :mandatory => true # See Join.all_join_node
217
+ 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
227
+ end
228
+
229
+ class Position
230
+ identified_by :x, :y
231
+ has_one :x, :mandatory => true # See X.all_position
232
+ has_one :y, :mandatory => true # See Y.all_position
233
+ end
234
+
174
235
  class PresenceConstraint < Constraint
175
236
  maybe :is_mandatory
176
237
  maybe :is_preferred_identifier
@@ -198,12 +259,13 @@ module ActiveFacts
198
259
  has_one :fact_type, :mandatory => true # See FactType.all_role
199
260
  has_one :ordinal, :mandatory => true # See Ordinal.all_role
200
261
  has_one :concept, :mandatory => true # See Concept.all_role
262
+ one_to_one :implicit_fact_type # See ImplicitFactType.role
201
263
  has_one :role_name, :class => Name # See Name.all_role_as_role_name
202
- has_one :role_value_restriction, :class => "ValueRestriction" # See ValueRestriction.all_role_as_role_value_restriction
203
264
  end
204
265
 
205
266
  class RoleSequence
206
267
  identified_by :role_sequence_id
268
+ maybe :has_unused_dependency_to_force_table_in_norma
207
269
  one_to_one :role_sequence_id, :mandatory => true # See RoleSequenceId.role_sequence
208
270
  end
209
271
 
@@ -218,6 +280,14 @@ module ActiveFacts
218
280
  class SetConstraint < Constraint
219
281
  end
220
282
 
283
+ class Shape
284
+ identified_by :shape_id
285
+ has_one :diagram, :mandatory => true # See Diagram.all_shape
286
+ maybe :is_expanded
287
+ has_one :position # See Position.all_shape
288
+ one_to_one :shape_id, :mandatory => true # See ShapeId.shape
289
+ end
290
+
221
291
  class SubsetConstraint < SetConstraint
222
292
  has_one :subset_role_sequence, :class => RoleSequence, :mandatory => true # See RoleSequence.all_subset_constraint_as_subset_role_sequence
223
293
  has_one :superset_role_sequence, :class => RoleSequence, :mandatory => true # See RoleSequence.all_subset_constraint_as_superset_role_sequence
@@ -226,10 +296,11 @@ module ActiveFacts
226
296
  class Unit
227
297
  identified_by :unit_id
228
298
  has_one :coefficient # See Coefficient.all_unit
229
- maybe :is_ephemeral
299
+ has_one :ephemera_url, :class => EphemeraURL # See EphemeraURL.all_unit
230
300
  maybe :is_fundamental
231
301
  has_one :name, :mandatory => true # See Name.all_unit
232
302
  has_one :offset # See Offset.all_unit
303
+ has_one :plural_name, :class => Name # See Name.all_unit_as_plural_name
233
304
  one_to_one :unit_id, :mandatory => true # See UnitId.unit
234
305
  has_one :vocabulary, :mandatory => true # See Vocabulary.all_unit
235
306
  end
@@ -241,7 +312,8 @@ module ActiveFacts
241
312
  has_one :unit # See Unit.all_value
242
313
  end
243
314
 
244
- class ValueRestriction < Constraint
315
+ class ValueConstraint < Constraint
316
+ one_to_one :role, :counterpart => :role_value_constraint # See Role.role_value_constraint
245
317
  end
246
318
 
247
319
  class Vocabulary
@@ -269,10 +341,15 @@ module ActiveFacts
269
341
  has_one :vocabulary, :mandatory => true # See Vocabulary.all_concept
270
342
  end
271
343
 
344
+ class ConstraintShape < Shape
345
+ has_one :constraint, :mandatory => true # See Constraint.all_constraint_shape
346
+ end
347
+
272
348
  class ContextAccordingTo
273
349
  identified_by :context_note, :agent
274
350
  has_one :agent, :mandatory => true # See Agent.all_context_according_to
275
351
  has_one :context_note, :mandatory => true # See ContextNote.all_context_according_to
352
+ has_one :date # See Date.all_context_according_to
276
353
  end
277
354
 
278
355
  class ContextAgreedBy
@@ -288,21 +365,69 @@ module ActiveFacts
288
365
  has_one :exponent # See Exponent.all_derivation
289
366
  end
290
367
 
368
+ class Diagram
369
+ identified_by :vocabulary, :name
370
+ has_one :name, :mandatory => true # See Name.all_diagram
371
+ has_one :vocabulary, :mandatory => true # See Vocabulary.all_diagram
372
+ end
373
+
291
374
  class EntityType < Concept
292
375
  one_to_one :fact_type # See FactType.entity_type
293
376
  end
294
377
 
378
+ class FactTypeShape < Shape
379
+ has_one :display_role_names_setting # See DisplayRoleNamesSetting.all_fact_type_shape
380
+ has_one :fact_type, :mandatory => true # See FactType.all_fact_type_shape
381
+ has_one :rotation_setting # See RotationSetting.all_fact_type_shape
382
+ end
383
+
384
+ class ModelNoteShape < Shape
385
+ has_one :context_note, :mandatory => true # See ContextNote.all_model_note_shape
386
+ end
387
+
388
+ class ObjectTypeShape < Shape
389
+ has_one :concept, :mandatory => true # See Concept.all_object_type_shape
390
+ maybe :has_expanded_reference_mode
391
+ end
392
+
393
+ class ObjectifiedFactTypeNameShape < Shape
394
+ identified_by :fact_type_shape
395
+ one_to_one :fact_type_shape, :mandatory => true # See FactTypeShape.objectified_fact_type_name_shape
396
+ end
397
+
295
398
  class Population
296
399
  identified_by :vocabulary, :name
297
400
  has_one :name, :mandatory => true # See Name.all_population
298
401
  has_one :vocabulary # See Vocabulary.all_population
299
402
  end
300
403
 
404
+ class ReadingShape < Shape
405
+ identified_by :fact_type_shape
406
+ one_to_one :fact_type_shape, :mandatory => true # See FactTypeShape.reading_shape
407
+ has_one :reading, :mandatory => true # See Reading.all_reading_shape
408
+ end
409
+
410
+ class RingConstraintShape < ConstraintShape
411
+ has_one :fact_type, :mandatory => true # See FactType.all_ring_constraint_shape
412
+ end
413
+
414
+ class RoleDisplay
415
+ identified_by :fact_type_shape, :ordinal
416
+ has_one :fact_type_shape, :mandatory => true # See FactTypeShape.all_role_display
417
+ has_one :ordinal, :mandatory => true # See Ordinal.all_role_display
418
+ has_one :role, :mandatory => true # See Role.all_role_display
419
+ end
420
+
421
+ class RoleNameShape < Shape
422
+ one_to_one :role_display, :mandatory => true # See RoleDisplay.role_name_shape
423
+ end
424
+
301
425
  class RoleRef
302
426
  identified_by :role_sequence, :ordinal
303
427
  has_one :ordinal, :mandatory => true # See Ordinal.all_role_ref
304
428
  has_one :role, :mandatory => true # See Role.all_role_ref
305
429
  has_one :role_sequence, :mandatory => true # See RoleSequence.all_role_ref
430
+ has_one :join_node # See JoinNode.all_role_ref
306
431
  has_one :leading_adjective, :class => Adjective # See Adjective.all_role_ref_as_leading_adjective
307
432
  has_one :trailing_adjective, :class => Adjective # See Adjective.all_role_ref_as_trailing_adjective
308
433
  end
@@ -332,6 +457,11 @@ module ActiveFacts
332
457
  maybe :provides_identification
333
458
  end
334
459
 
460
+ class ValueConstraintShape < ConstraintShape
461
+ has_one :object_type_shape # See ObjectTypeShape.all_value_constraint_shape
462
+ one_to_one :role_display # See RoleDisplay.value_constraint_shape
463
+ end
464
+
335
465
  class ValueRange
336
466
  identified_by :minimum_bound, :maximum_bound
337
467
  has_one :maximum_bound, :class => Bound # See Bound.all_value_range_as_maximum_bound
@@ -339,26 +469,21 @@ module ActiveFacts
339
469
  end
340
470
 
341
471
  class ValueType < Concept
472
+ maybe :is_auto_assigned
342
473
  has_one :length # See Length.all_value_type
343
474
  has_one :scale # See Scale.all_value_type
344
475
  has_one :supertype, :class => ValueType # See ValueType.all_value_type_as_supertype
345
476
  has_one :unit # See Unit.all_value_type
346
- has_one :value_restriction # See ValueRestriction.all_value_type
477
+ one_to_one :value_constraint # See ValueConstraint.value_type
347
478
  end
348
479
 
349
480
  class AllowedRange
350
- identified_by :value_restriction, :value_range
481
+ identified_by :value_constraint, :value_range
482
+ has_one :value_constraint, :mandatory => true # See ValueConstraint.all_allowed_range
351
483
  has_one :value_range, :mandatory => true # See ValueRange.all_allowed_range
352
- has_one :value_restriction, :mandatory => true # See ValueRestriction.all_allowed_range
353
484
  end
354
485
 
355
- class Join
356
- identified_by :role_ref, :join_step
357
- has_one :join_step, :class => Ordinal, :mandatory => true # See Ordinal.all_join_as_join_step
358
- has_one :role_ref, :mandatory => true # See RoleRef.all_join
359
- has_one :concept # See Concept.all_join
360
- has_one :input_role, :class => Role # See Role.all_join_as_input_role
361
- has_one :output_role, :class => Role # See Role.all_join_as_output_role
486
+ class ImplicitBooleanValueType < ValueType
362
487
  end
363
488
 
364
489
  class Parameter
@@ -0,0 +1,621 @@
1
+ #
2
+ # ActiveFacts Vocabulary Metamodel.
3
+ # Verbaliser for the ActiveFacts Vocabulary
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module Metamodel
9
+ #
10
+ # The Verbaliser fulfils two roles:
11
+ # * Maintains verbalisation context to expand readings using subscripting where needed
12
+ # * Verbalises Joins by iteratively choosing a Join Step and expanding readings
13
+ #
14
+ # The verbalisation context consists of a set of Players, each for one Concept.
15
+ # There may be more than one Player for the same Concept. If adjectives or role
16
+ # names don't make such duplicates unambiguous, subscripts will be generated.
17
+ # Thus, the verbalisation context must be completely populated before subscript
18
+ # generation, which must be before any verbalisation occurs.
19
+ #
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.
22
+ #
23
+ # When expanding Reading text however, the RoleRefs in the reading's RoleSequence
24
+ # may be expected not to be attached to the Players for that reading. Instead,
25
+ # the set of one or more RoleRefs which caused that reading to be expanded must
26
+ # be passed in, and the corresponding roles matched with Players to determine
27
+ # the need to emit a subscript.
28
+ #
29
+ class Verbaliser
30
+ # Verbalisation context:
31
+ attr_reader :players
32
+ attr_reader :player_by_role_ref
33
+ attr_reader :player_by_join_node
34
+
35
+ # The projected role references over which we're verbalising
36
+ attr_reader :role_refs
37
+
38
+ # Join Verbaliser context:
39
+ attr_reader :join
40
+ attr_reader :join_nodes # All Join Nodes
41
+ attr_reader :join_steps # All remaining unemitted Join Steps
42
+ attr_reader :join_steps_by_join_node # A Hash by Join Node containing an array of remaining steps
43
+
44
+ def initialize role_refs = nil
45
+ @role_refs = role_refs
46
+
47
+ # Verbalisation context:
48
+ @players = []
49
+ @player_by_role_ref = {}
50
+ @player_by_join_node = {}
51
+
52
+ # Join Verbaliser context:
53
+ @join = nil
54
+ @join_nodes = []
55
+ @join_steps = []
56
+ @join_steps_by_join_node = {}
57
+
58
+ add_role_refs role_refs if role_refs
59
+ end
60
+
61
+ class Player
62
+ attr_accessor :concept, :join_nodes_by_join, :subscript, :role_refs
63
+ def initialize concept
64
+ @concept = concept
65
+ @join_nodes_by_join = {}
66
+ @subscript = nil
67
+ @role_refs = []
68
+ end
69
+
70
+ # What words are used (across all roles) for disambiguating the references to this player?
71
+ # If more than one set of adjectives was used, this player must have been subject to loose binding.
72
+ # This method is used to decide when subscripts aren't needed.
73
+ def role_adjuncts
74
+ adjuncts = @role_refs.map{|rr| [rr.leading_adjective, rr.role.role_name, rr.trailing_adjective].compact}.uniq.sort
75
+ adjuncts.flatten*"_"
76
+ end
77
+ end
78
+
79
+ # 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
86
+ end
87
+
88
+ # Add a RoleRef to an existing Player
89
+ def add_role_player player, role_ref
90
+ #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)
100
+ 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
+ player.role_refs.push(role_ref)
102
+ @player_by_role_ref[role_ref] = player
103
+ end
104
+ end
105
+
106
+ def add_role_ref role_ref
107
+ add_role_player(player(role_ref), role_ref)
108
+ end
109
+
110
+ # Add RoleRefs to one or more Players, creating players where needed
111
+ def add_role_refs role_refs
112
+ role_refs.each{|rr| add_role_ref(rr) }
113
+ end
114
+
115
+ # Return an array of the names of these identifying_roles.
116
+ def identifying_role_names identifying_role_refs
117
+ identifying_role_refs.map do |role_ref|
118
+ preferred_role_ref = role_ref.role.fact_type.preferred_reading.role_sequence.all_role_ref.detect{|reading_rr|
119
+ reading_rr.role == role_ref.role
120
+ }
121
+
122
+ if (role_ref.role.fact_type.all_role.size == 1)
123
+ role_ref.role.fact_type.default_reading # Need whole reading for a unary.
124
+ elsif role_name = role_ref.role.role_name and role_name != ''
125
+ role_name
126
+ else
127
+ role_words = []
128
+ role_words << preferred_role_ref.leading_adjective if preferred_role_ref.leading_adjective != ""
129
+ role_words << preferred_role_ref.role.concept.name
130
+ role_words << preferred_role_ref.trailing_adjective if preferred_role_ref.trailing_adjective != ""
131
+ role_name = role_words.compact*"-"
132
+ if p = player(preferred_role_ref) and p.subscript
133
+ role_name += "(#{p.subscript})"
134
+ end
135
+ role_name
136
+ end
137
+ end
138
+ end
139
+
140
+ # All these readings are for the same fact type, and all will be emitted, so the roles cover the same players
141
+ # This is used when verbalising fact types and entity types.
142
+ def alternate_readings readings
143
+ readings.map do |reading|
144
+ reading.role_sequence.all_role_ref.sort_by{|rr| rr.role.ordinal}
145
+ end.transpose.each do |role_refs|
146
+ role_refs_have_same_player role_refs
147
+ end
148
+ end
149
+
150
+ # These RoleRefs are all for the same player. Find whether any of them has a player already
151
+ def role_refs_have_same_player role_refs
152
+ role_refs = role_refs.is_a?(Array) ? role_refs : role_refs.all_role_ref.to_a
153
+ return if role_refs.empty?
154
+ # If any of these role_refs are for a known player, use that, else make a new player.
155
+ existing_players =
156
+ role_refs.map{|rr| @player_by_role_ref[rr] || @player_by_join_node[rr.join_node] }.compact.uniq
157
+ if existing_players.size > 1
158
+ raise "Can't join these role_refs to more than one existing player: #{existing_players.map{|p|p.concept.name}*', '}!"
159
+ end
160
+ p = existing_players[0] || player(role_refs[0])
161
+ debug :subscript, "#{existing_players[0] ? 'Adding to existing' : 'Creating new'} player for #{role_refs.map{|rr| rr.role.concept.name}.uniq*', '}" do
162
+ role_refs.each do |rr|
163
+ add_role_player(p, rr)
164
+ end
165
+ end
166
+ end
167
+
168
+ def create_subscripts
169
+ # Create subscripts, where necessary
170
+ @players.
171
+ map{|p| [p, p.concept] }.
172
+ each do |player, concept|
173
+ dups = @players.select{|p| p.concept == concept && p.role_adjuncts == player.role_adjuncts }
174
+ if dups.size == 1
175
+ debug :subscript, "No subscript needed for #{concept.name}"
176
+ next
177
+ end
178
+ debug :subscript, "Applying subscripts to #{dups.size} occurrences of #{concept.name}" do
179
+ dups.each_with_index do |player, index|
180
+ player.subscript = index+1
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ # Expand a reading for an entity type or fact type definition. Unlike expansions in constraints,
187
+ # these expansions include frequency constraints, role names and value constraints as passed-in,
188
+ # and also define adjectives by using the hyphenated form (on at least the first occurrence).
189
+ def expand_reading(reading, frequency_constraints = [], define_role_names = nil, value_constraints = [], &subscript_block)
190
+ reading.expand(frequency_constraints, define_role_names, value_constraints) do |role_ref|
191
+ (p = player(role_ref) and p.subscript) ? "(#{p.subscript})" : ""
192
+ end
193
+ end
194
+
195
+ # Where no explicit Join has been created, a join is still sometimes present (e.g. in a constraint from NORMA)
196
+ # REVISIT: This probably doesn't produce the required result. Need to fix the NORMA importer to create the join.
197
+ def role_refs_are_subtype_joined roles
198
+ role_refs = roles.is_a?(Array) ? roles : roles.all_role_ref.to_a
199
+ 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
+ role_refs_by_concept.values.each { |rrs| role_refs_have_same_player(rrs) }
202
+ end
203
+
204
+ # These roles are the players in an implicit counterpart join in a Presence Constraint.
205
+ # REVISIT: It's not clear that we can safely use the preferred_reading's RoleRefs here.
206
+ # Fix the CQL compiler to create proper joins for these presence constraints instead.
207
+ def roles_have_same_player roles
208
+ role_refs = roles.map do |role|
209
+ pr = role.fact_type.preferred_reading
210
+ pr.role_sequence.all_role_ref.detect{|rr| rr.role == role}
211
+ end
212
+ role_refs_have_same_player(role_refs)
213
+ end
214
+
215
+ def prepare_role_sequence role_sequence
216
+ @role_refs = role_sequence.is_a?(Array) ? role_sequence : role_sequence.all_role_ref.to_a
217
+
218
+ if jrr = @role_refs.detect{|rr| rr.join_node}
219
+ return prepare_join_players(jrr.join_node.join)
220
+ end
221
+
222
+ # Ensure that all the joined-over role_refs are indexed for subscript generation.
223
+ role_refs_by_fact_type =
224
+ @role_refs.inject({}) { |hash, rr| (hash[rr.role.fact_type] ||= []) << rr; hash }
225
+ role_refs_by_fact_type.each do |fact_type, role_refs|
226
+ role_refs.each { |rr| role_refs_have_same_player([rr]) }
227
+
228
+ # Register the role_refs in the preferred reading which refer to roles not covered in the role sequence.
229
+ prrs = fact_type.preferred_reading.role_sequence.all_role_ref
230
+ residual_roles = fact_type.all_role.select{|r| !@role_refs.detect{|rr| rr.role == r} }
231
+ residual_roles.each do |role|
232
+ debug :subscript, "Adding residual role for #{role.concept.name} not covered in role sequence"
233
+ preferred_role_ref = prrs.detect{|rr| rr.role == role}
234
+ if p = @player_by_role_ref[preferred_role_ref] and !p.role_refs.include?(preferred_role_ref)
235
+ raise "Adding DUPLICATE residual role for #{role.concept.name}"
236
+ end
237
+ role_refs_have_same_player([prrs.detect{|rr| rr.role == role}])
238
+ end
239
+ end
240
+ end
241
+
242
+ def prepare_join_players join
243
+ debug :subscript, "Indexing roles of fact types in #{@join_steps.size} join steps" do
244
+ join_steps = []
245
+ # Register all references to each join node as being for the same player:
246
+ 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
249
+ end
250
+ # For each fact type traversed, register a player for each role *not* linked to this join
251
+ # REVISIT: Using the preferred_reading role_ref is wrong here; the same preferred_reading might occur twice,
252
+ # so the respective concept will need more than one Player and will be subscripted to keep them from being joined.
253
+ # 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|
255
+ 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"
263
+ end
264
+ role_refs_have_same_player([preferred_role_ref])
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ def verbalise_over_role_sequence role_sequence, joiner = ' and ', role_proximity = :both
271
+ @role_refs = role_sequence.is_a?(Array) ? role_sequence : role_sequence.all_role_ref.to_a
272
+
273
+ if jrr = role_refs.detect{|rr| rr.join_node}
274
+ return verbalise_join(jrr.join_node.join)
275
+ end
276
+
277
+ # First, figure out whether there's a join:
278
+ join_over, joined_roles = *Metamodel.join_roles_over(role_sequence.all_role_ref.map{|rr|rr.role}, role_proximity)
279
+
280
+ fact_types = @role_refs.map{|rr| rr.role.fact_type}.uniq
281
+ readings = fact_types.map do |fact_type|
282
+ name_substitutions = []
283
+ reading = fact_type.preferred_reading
284
+ if join_over and # Find a reading preferably starting with the joined_over role:
285
+ joined_role = fact_type.all_role.select{|r| join_over.subtypes_transitive.include?(r.concept)}[0]
286
+ reading = fact_type.reading_preferably_starting_with_role joined_role
287
+
288
+ # Use the name of the joined_over object, not the role player, in case of a subtype join:
289
+ rrrs = reading.role_sequence.all_role_ref_in_order
290
+ role_index = (0..rrrs.size).detect{|i| rrrs[i].role == joined_role }
291
+ name_substitutions[role_index] = [nil, join_over.name]
292
+ end
293
+ reading.role_sequence.all_role_ref.each do |rr|
294
+ next unless player = @player_by_role_ref[rr]
295
+ next unless subscript = player.subscript
296
+ debug :subscript, "Need to apply subscript #{subscript} to #{rr.role.concept.name}"
297
+ 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)
300
+ #reading.expand(name_substitutions)
301
+ end
302
+ joiner ? readings*joiner : readings
303
+ end
304
+
305
+ # Expand this reading (or partial reading, during contraction)
306
+ def expand_reading_text(step, text, role_sequence, role_refs = [])
307
+ rrs = role_sequence.all_role_ref_in_order
308
+ debug :subscript, "expanding #{text} with #{role_sequence.describe}" do
309
+ text.gsub(/\{(\d)\}/) do
310
+ role_ref = rrs[$1.to_i]
311
+ # 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
+ # REVISIT: There's no way to get literals to be emitted here (value join step?)
313
+
314
+ rr = role_refs.detect{|rr| rr.role == role_ref.role} || role_ref
315
+
316
+ player = @player_by_role_ref[rr] and subscript = player.subscript
317
+ if !subscript and
318
+ pp = @players.select{|p|p.concept == rr.role.concept} and
319
+ pp.detect{|p|p.subscript}
320
+ raise "Internal error: Subscripted players (of the same concept #{p.concept.name}) when this player isn't subscripted"
321
+ end
322
+
323
+ subscripted_player(rr, role_ref) +
324
+ objectification_verbalisation(role_ref.role.concept)
325
+ end
326
+ end
327
+ end
328
+
329
+ def subscripted_player role_ref, reading_role_ref = nil
330
+ if player = @player_by_role_ref[role_ref] and subscript = player.subscript
331
+ debug :subscript, "Need to apply subscript #{subscript} to #{role_ref.role.concept.name}"
332
+ end
333
+ concept = role_ref.role.concept
334
+ [
335
+ (reading_role_ref || role_ref).leading_adjective,
336
+ concept.name,
337
+ (reading_role_ref || role_ref).trailing_adjective
338
+ ].compact*' ' +
339
+ (subscript ? "(#{subscript})" : '')
340
+ end
341
+
342
+ def expand_contracted_text(step, reading, role_refs = [])
343
+ ' that ' +
344
+ expand_reading_text(step, reading.text.sub(/\A\{\d\} /,''), reading.role_sequence, role_refs)
345
+ end
346
+
347
+ # Each join we wish to verbalise must first have had its players prepared.
348
+ # Then, this prepares the join for verbalising:
349
+ def prepare_join join
350
+ @join = join
351
+ return unless join
352
+
353
+ @join_nodes = join.all_join_node.sort_by{|jn| jn.ordinal}
354
+
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
356
+ @join_steps_by_join_node = @join_nodes.
357
+ 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}
360
+ h
361
+ end
362
+ end
363
+
364
+ # Remove this step now that we've processed it:
365
+ def step_completed(step)
366
+ @join_steps.delete(step)
367
+
368
+ input_node = step.input_join_node
369
+ steps = @join_steps_by_join_node[input_node]
370
+ steps.delete(step)
371
+ @join_steps_by_join_node.delete(input_node) if steps.empty?
372
+
373
+ output_node = step.output_join_node
374
+ if (input_node != output_node)
375
+ steps = @join_steps_by_join_node[output_node]
376
+ steps.delete(step)
377
+ @join_steps_by_join_node.delete(output_node) if steps.empty?
378
+ end
379
+ end
380
+
381
+ def choose_step(next_node)
382
+ next_steps = @join_steps_by_join_node[next_node]
383
+
384
+ # If we don't have a next_node against which we can contract,
385
+ # so just use any join step involving this node, or just any step.
386
+ if next_steps
387
+ if next_step = next_steps.detect { |ns| !ns.is_objectification_step }
388
+ debug :join, "Chose new non-objectification step: #{next_step.describe}"
389
+ return next_step
390
+ end
391
+ end
392
+
393
+ if next_step = @join_steps.detect { |ns| !ns.is_objectification_step }
394
+ debug :join, "Chose random non-objectification step: #{next_step.describe}"
395
+ return next_step
396
+ end
397
+
398
+ next_step = @join_steps[0]
399
+ if next_step
400
+ debug :join, "Chose new random step from #{join_steps.size}: #{next_step.describe}"
401
+ if next_step.is_objectification_step
402
+ # if this objectification plays any roles (other than its FT roles) in remaining steps, use one of those first:
403
+ 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}
405
+ sr = @join_steps_by_join_node[jn].reject{|t| t.fact_type.role and t.fact_type.role.fact_type == fact_type}
406
+ next_step = sr[0] if sr.size > 0
407
+ end
408
+ return next_step
409
+ end
410
+ raise "Internal error: There are more join steps here, but we failed to choose one"
411
+ end
412
+
413
+ # The join step we just emitted (using the reading given) is contractable iff
414
+ # the reading has the next_node's role player as the final text
415
+ def node_contractable_against_reading(next_node, reading)
416
+ reading &&
417
+ # Find whether last role has no following text, and its ordinal
418
+ (reading.text =~ /\{([0-9])\}$/) &&
419
+ # This reading's RoleRef for that role:
420
+ (role_ref = reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}) &&
421
+ # was that RoleRef for the upcoming node?
422
+ role_ref.role.all_role_ref.detect{|rr| rr.join_node == next_node}
423
+ end
424
+
425
+ def reading_starts_with_node(reading, next_node)
426
+ reading.text =~ /^\{([0-9])\}/ and
427
+ 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}
429
+ end
430
+
431
+ # The last reading we emitted ended with the object type name for next_node.
432
+ # Choose a step and a reading that can be contracted against that name
433
+ def contractable_step(next_steps, next_node)
434
+ next_reading = nil
435
+ next_step =
436
+ next_steps.detect do |js|
437
+ next false if js.is_objectification_step
438
+ # If we find a reading here, it can be contracted against the previous one
439
+ next_reading =
440
+ js.fact_type.all_reading_by_ordinal.detect do |reading|
441
+ # This step is contractable iff the FactType has a reading that starts with the role of next_node (no preceding text)
442
+ reading_starts_with_node(reading, next_node)
443
+ end
444
+ next_reading
445
+ end
446
+ debug :join, "#{next_reading ? "'"+next_reading.expand+"'" : "No reading"} contracts against last node '#{next_node.concept.name}'"
447
+ return [next_step, next_reading]
448
+ end
449
+
450
+ # REVISIT: There might be more than one objectification_verbalisation for a given concept. Need to get the Join Node here and emit an objectification step involving that node.
451
+ def objectification_verbalisation(concept)
452
+ objectified_node = nil
453
+ unless concept.is_a?(Metamodel::EntityType) and
454
+ concept.fact_type and # Not objectified
455
+ objectification_step = @join_steps.
456
+ detect do |js|
457
+ # The objectifying entity type should always be the input_join_node here, but be safe:
458
+ js.is_objectification_step and
459
+ (objectified_node = js.input_join_node).concept == concept ||
460
+ (objectified_node = js.output_join_node).concept == concept
461
+ end
462
+ return ''
463
+ end
464
+
465
+ # REVISIT: We need to be working from the role_ref here - pass it in
466
+ # if objectification_step.join_node != role_ref.join_node
467
+
468
+ steps = [objectification_step]
469
+ step_completed(objectification_step)
470
+ while other_step =
471
+ @join_steps.
472
+ detect{|js|
473
+ js.is_objectification_step and
474
+ js.input_join_node.concept == concept || js.output_join_node.concept == concept
475
+ }
476
+ steps << other_step
477
+ debug :join, "Emitting objectification step allows deleting #{other_step.describe}"
478
+ step_completed(other_step)
479
+ end
480
+
481
+ # 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
483
+
484
+ reading = concept.fact_type.preferred_reading
485
+ " (where #{expand_reading_text(objectification_step, reading.text, reading.role_sequence, role_refs)})"
486
+ end
487
+
488
+ def elided_objectification(next_step, fact_type, last_is_contractable, next_node)
489
+ if last_is_contractable
490
+ # Choose a reading that's contractable against the previous step, if possible
491
+ reading = fact_type.all_reading_by_ordinal.
492
+ detect do |reading|
493
+ reading_starts_with_node(reading, next_node)
494
+ end
495
+ end
496
+ last_is_contractable = false unless reading
497
+ reading ||= fact_type.preferred_reading
498
+
499
+ # Find which role occurs last in the reading, and which Join Node is attached
500
+ reading.text =~ /\{(\d)\}[^{]*\Z/
501
+ 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}}
503
+ exit_step = nil
504
+
505
+ while other_step =
506
+ @join_steps.
507
+ detect{|js|
508
+ 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
511
+ true
512
+ }
513
+ debug :join, "Emitting objectified FT allows deleting #{other_step.describe}"
514
+ step_completed(other_step)
515
+ end
516
+ [ reading, exit_step ? exit_step.input_join_node : exit_node, exit_step, last_is_contractable]
517
+ end
518
+
519
+ def verbalise_join join
520
+ prepare_join join
521
+ readings = ''
522
+ next_node = @role_refs[0].join_node # Choose a place to start
523
+ last_is_contractable = false
524
+ debug :join, "Join Nodes are #{@join_nodes.map{|jn| jn.describe }.inspect}, Join Steps are #{@join_steps.map{|js| js.describe }.inspect}" do
525
+ until @join_steps.empty?
526
+ next_reading = nil
527
+ # Choose amonst all remaining steps we can take from the next node, if any
528
+ next_steps = @join_steps_by_join_node[next_node]
529
+ debug :join, "Next Steps from #{next_node.describe} are #{(next_steps||[]).map{|js| js.describe }.inspect}"
530
+
531
+ # See if we can find a next step that contracts against the last (if any):
532
+ next_step = nil
533
+ if last_is_contractable && next_steps
534
+ next_step, next_reading = *contractable_step(next_steps, next_node)
535
+ end
536
+
537
+ 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)
547
+ step_completed(next_step)
548
+ else
549
+ next_step = choose_step(next_node) if !next_step
550
+
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
557
+
558
+ if next_step.is_unary_step
559
+ # Objectified unaries get emitted as unaries, not as objectifications:
560
+ # REVISIT: There must be a simpler way of finding the preferred reading here:
561
+ rr = next_step.input_join_node.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ImplicitFactType) }
562
+ next_reading = rr.role.fact_type.role.fact_type.preferred_reading
563
+ readings += " and " unless readings.empty?
564
+ readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, step_role_refs)
565
+ step_completed(next_step)
566
+ elsif next_step.is_objectification_step
567
+ fact_type = next_step.fact_type.role.fact_type
568
+ if last_is_contractable and next_node.concept.is_a?(EntityType) and next_node.concept.fact_type == fact_type
569
+ # 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)
576
+ readings += objectification_verbalisation(fact_type.entity_type)
577
+ else
578
+ # This objectified fact type does not need to be made explicit.
579
+ next_reading, next_node, next_step, last_is_contractable =
580
+ *elided_objectification(next_step, fact_type, last_is_contractable, next_node)
581
+ if last_is_contractable
582
+ readings += expand_contracted_text(next_step, next_reading, step_role_refs)
583
+ else
584
+ readings += " and " unless readings.empty?
585
+ readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, step_role_refs)
586
+ end
587
+ # No need to continue if we just deleted the last step
588
+ break if @join_steps.empty?
589
+
590
+ end
591
+ else
592
+ fact_type = next_step.fact_type
593
+ # Prefer a reading that starts with the player of next_node
594
+ next_reading = fact_type.all_reading_by_ordinal.
595
+ 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}
599
+ end || fact_type.preferred_reading
600
+ # REVISIT: If this join step and reading has role references with adjectives, we need to expand using those
601
+ readings += " and " unless readings.empty?
602
+ readings += expand_reading_text(next_step, next_reading.text, next_reading.role_sequence, step_role_refs)
603
+ step_completed(next_step)
604
+ end
605
+ end
606
+
607
+ # 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
611
+ # Prepare for possible contraction following:
612
+ last_is_contractable = next_reading && node_contractable_against_reading(next_node, next_reading)
613
+
614
+ end
615
+ end
616
+ readings
617
+ end
618
+ end
619
+
620
+ end
621
+ end