activefacts 0.8.6 → 0.8.8
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +33 -2
- data/README.rdoc +30 -36
- data/Rakefile +16 -20
- data/bin/afgen +17 -11
- data/bin/cql +313 -36
- data/download.html +43 -19
- data/examples/CQL/Address.cql +15 -15
- data/examples/CQL/Blog.cql +8 -8
- data/examples/CQL/CompanyDirectorEmployee.cql +6 -5
- data/examples/CQL/Death.cql +3 -3
- data/examples/CQL/Diplomacy.cql +48 -0
- data/examples/CQL/Genealogy.cql +41 -41
- data/examples/CQL/Insurance.cql +311 -0
- data/examples/CQL/JoinEquality.cql +35 -0
- data/examples/CQL/Marriage.cql +1 -1
- data/examples/CQL/Metamodel.cql +290 -185
- data/examples/CQL/MetamodelNext.cql +420 -0
- data/examples/CQL/Monogamy.cql +24 -0
- data/examples/CQL/MonthInSeason.cql +27 -0
- data/examples/CQL/Moon.cql +23 -0
- data/examples/CQL/MultiInheritance.cql +4 -4
- data/examples/CQL/NonRoleId.cql +14 -0
- data/examples/CQL/OddIdentifier.cql +18 -0
- data/examples/CQL/OilSupply.cql +24 -24
- data/examples/CQL/OneToOnes.cql +17 -0
- data/examples/CQL/Orienteering.cql +55 -55
- data/examples/CQL/OrienteeringER.cql +58 -0
- data/examples/CQL/PersonPlaysGame.cql +2 -2
- data/examples/CQL/RedundantDependency.cql +34 -0
- data/examples/CQL/SchoolActivities.cql +5 -5
- data/examples/CQL/SeparateSubtype.cql +28 -0
- data/examples/CQL/ServiceDirector.cql +283 -0
- data/examples/CQL/SimplestUnary.cql +2 -2
- data/examples/CQL/SubtypePI.cql +11 -11
- data/examples/CQL/Supervision.cql +38 -0
- data/examples/CQL/Tests.Test5.Load.cql +38 -0
- data/examples/CQL/WaiterTips.cql +33 -0
- data/examples/CQL/Warehousing.cql +55 -53
- data/examples/CQL/WindowInRoomInBldg.cql +9 -9
- data/examples/CQL/unit.cql +433 -544
- data/examples/index.html +314 -170
- data/examples/intro.html +6 -176
- data/examples/local.css +8 -4
- data/index.html +40 -25
- data/lib/activefacts/api/concept.rb +2 -2
- data/lib/activefacts/api/constellation.rb +4 -4
- data/lib/activefacts/api/instance.rb +2 -2
- data/lib/activefacts/api/instance_index.rb +4 -0
- data/lib/activefacts/api/numeric.rb +3 -1
- data/lib/activefacts/api/role.rb +1 -1
- data/lib/activefacts/api/standard_types.rb +23 -16
- data/lib/activefacts/api/support.rb +3 -1
- data/lib/activefacts/api/vocabulary.rb +4 -0
- data/lib/activefacts/cql/CQLParser.treetop +87 -39
- data/lib/activefacts/cql/Concepts.treetop +95 -69
- data/lib/activefacts/cql/Context.treetop +11 -2
- data/lib/activefacts/cql/Expressions.treetop +23 -59
- data/lib/activefacts/cql/FactTypes.treetop +141 -95
- data/lib/activefacts/cql/Language/English.treetop +33 -21
- data/lib/activefacts/cql/LexicalRules.treetop +6 -1
- data/lib/activefacts/cql/Terms.treetop +75 -26
- data/lib/activefacts/cql/ValueTypes.treetop +52 -54
- data/lib/activefacts/cql/compiler.rb +46 -1691
- data/lib/activefacts/cql/compiler/constraint.rb +602 -0
- data/lib/activefacts/cql/compiler/entity_type.rb +425 -0
- data/lib/activefacts/cql/compiler/fact.rb +300 -0
- data/lib/activefacts/cql/compiler/fact_type.rb +230 -0
- data/lib/activefacts/cql/compiler/reading.rb +832 -0
- data/lib/activefacts/cql/compiler/shared.rb +109 -0
- data/lib/activefacts/cql/compiler/value_type.rb +104 -0
- data/lib/activefacts/cql/parser.rb +132 -81
- data/lib/activefacts/generate/cql.rb +397 -274
- data/lib/activefacts/generate/oo.rb +13 -12
- data/lib/activefacts/generate/ordered.rb +107 -117
- data/lib/activefacts/generate/ruby.rb +34 -38
- data/lib/activefacts/generate/sql/mysql.rb +62 -45
- data/lib/activefacts/generate/sql/server.rb +59 -42
- data/lib/activefacts/input/cql.rb +6 -3
- data/lib/activefacts/input/orm.rb +991 -557
- data/lib/activefacts/persistence/columns.rb +16 -12
- data/lib/activefacts/persistence/foreignkey.rb +7 -4
- data/lib/activefacts/persistence/index.rb +3 -4
- data/lib/activefacts/persistence/reference.rb +5 -2
- data/lib/activefacts/support.rb +20 -14
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary.rb +1 -0
- data/lib/activefacts/vocabulary/extensions.rb +328 -44
- data/lib/activefacts/vocabulary/metamodel.rb +145 -20
- data/lib/activefacts/vocabulary/verbaliser.rb +621 -0
- data/spec/absorption_spec.rb +4 -4
- data/spec/api/value_type.rb +1 -1
- data/spec/cql/context_spec.rb +45 -22
- data/spec/cql/deontic_spec.rb +88 -0
- data/spec/cql/matching_spec.rb +517 -0
- data/spec/cql/samples_spec.rb +88 -31
- data/spec/cql/unit_spec.rb +58 -37
- data/spec/cql_cql_spec.rb +12 -7
- data/spec/cql_mysql_spec.rb +3 -7
- data/spec/cql_parse_spec.rb +0 -4
- data/spec/cql_ruby_spec.rb +1 -4
- data/spec/cql_sql_spec.rb +5 -18
- data/spec/cql_symbol_tables_spec.rb +3 -0
- data/spec/cqldump_spec.rb +0 -2
- data/spec/helpers/array_matcher.rb +35 -0
- data/spec/helpers/ctrl_c_support.rb +52 -0
- data/spec/helpers/diff_matcher.rb +38 -0
- data/spec/helpers/file_matcher.rb +5 -3
- data/spec/helpers/string_matcher.rb +39 -0
- data/spec/helpers/test_parser.rb +13 -0
- data/spec/norma_cql_spec.rb +13 -5
- data/spec/norma_ruby_spec.rb +11 -3
- data/spec/{absorption_ruby_spec.rb → norma_ruby_sql_spec.rb} +37 -32
- data/spec/norma_sql_spec.rb +11 -5
- data/spec/norma_tables_spec.rb +33 -29
- data/spec/spec_helper.rb +4 -1
- data/status.html +92 -23
- metadata +102 -36
- 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
|
49
|
-
value_type
|
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 <
|
89
|
-
value_type :length =>
|
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 :
|
177
|
+
identified_by :constraint
|
149
178
|
has_one :agent # See Agent.all_enforcement
|
150
|
-
one_to_one :
|
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
|
-
|
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
|
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
|
-
|
477
|
+
one_to_one :value_constraint # See ValueConstraint.value_type
|
347
478
|
end
|
348
479
|
|
349
480
|
class AllowedRange
|
350
|
-
identified_by :
|
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
|
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
|