activefacts-generators 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +10 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +30 -0
  8. data/Rakefile +6 -0
  9. data/activefacts-generators.gemspec +26 -0
  10. data/lib/activefacts/dependency_analyser.rb +182 -0
  11. data/lib/activefacts/generators/absorption.rb +71 -0
  12. data/lib/activefacts/generators/composition.rb +119 -0
  13. data/lib/activefacts/generators/cql.rb +715 -0
  14. data/lib/activefacts/generators/diagrams/json.rb +340 -0
  15. data/lib/activefacts/generators/help.rb +64 -0
  16. data/lib/activefacts/generators/helpers/inject.rb +16 -0
  17. data/lib/activefacts/generators/helpers/oo.rb +162 -0
  18. data/lib/activefacts/generators/helpers/ordered.rb +605 -0
  19. data/lib/activefacts/generators/helpers/rails.rb +57 -0
  20. data/lib/activefacts/generators/html/glossary.rb +462 -0
  21. data/lib/activefacts/generators/metadata/json.rb +204 -0
  22. data/lib/activefacts/generators/null.rb +32 -0
  23. data/lib/activefacts/generators/rails/models.rb +247 -0
  24. data/lib/activefacts/generators/rails/schema.rb +217 -0
  25. data/lib/activefacts/generators/ruby.rb +134 -0
  26. data/lib/activefacts/generators/sql/mysql.rb +281 -0
  27. data/lib/activefacts/generators/sql/server.rb +274 -0
  28. data/lib/activefacts/generators/stats.rb +70 -0
  29. data/lib/activefacts/generators/text.rb +29 -0
  30. data/lib/activefacts/generators/traits/datavault.rb +241 -0
  31. data/lib/activefacts/generators/traits/oo.rb +73 -0
  32. data/lib/activefacts/generators/traits/ordered.rb +33 -0
  33. data/lib/activefacts/generators/traits/ruby.rb +210 -0
  34. data/lib/activefacts/generators/transform/datavault.rb +303 -0
  35. data/lib/activefacts/generators/transform/surrogate.rb +215 -0
  36. data/lib/activefacts/registry.rb +11 -0
  37. metadata +176 -0
@@ -0,0 +1,715 @@
1
+ #
2
+ # ActiveFacts Generators.
3
+ # Generate CQL from an ActiveFacts vocabulary.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ require 'activefacts/metamodel'
8
+ require 'activefacts/cql/verbaliser'
9
+ require 'activefacts/generators/helpers/ordered'
10
+ require 'activefacts/registry'
11
+
12
+ module ActiveFacts
13
+ module Generators #:nodoc:
14
+ # Generate CQL for an ActiveFacts vocabulary.
15
+ # Invoke as
16
+ # afgen --cql <file>.cql
17
+ class CQL < Helpers::OrderedDumper
18
+ private
19
+ def vocabulary_start
20
+ puts "vocabulary #{@vocabulary.name};\n\n"
21
+ build_indices
22
+ end
23
+
24
+ def vocabulary_end
25
+ end
26
+
27
+ def units_banner
28
+ puts "/*\n * Units\n */"
29
+ end
30
+
31
+ def unit_dump unit
32
+ puts unit.as_cql
33
+ end
34
+
35
+ def units_end
36
+ puts "\n"
37
+ end
38
+
39
+ def value_type_banner
40
+ puts "/*\n * Value Types\n */"
41
+ end
42
+
43
+ def value_type_end
44
+ puts "\n"
45
+ end
46
+
47
+ def data_type_dump(o)
48
+ value_type_dump(o, o.name, {}) if o.all_role.size > 0
49
+ end
50
+
51
+ def value_type_dump(o, super_type_name, facets)
52
+ # No need to dump it if the only thing it does is be a supertype; it'll be created automatically
53
+ # return if o.all_value_type_as_supertype.size == 0
54
+
55
+ # REVISIT: A ValueType that is only used as a reference mode need not be emitted here.
56
+
57
+ puts o.as_cql
58
+ end
59
+
60
+ def entity_type_dump(o)
61
+ o.ordered_dumped!
62
+ pi = o.preferred_identifier
63
+
64
+ supers = o.supertypes
65
+ if (supers.size > 0)
66
+ # Ignore identification by a supertype:
67
+ pi = nil if pi && pi.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) }
68
+ subtype_dump(o, supers, pi)
69
+ else
70
+ non_subtype_dump(o, pi)
71
+ end
72
+ pi.ordered_dumped! if pi
73
+ end
74
+
75
+ def append_ring_to_reading(reading, ring)
76
+ reading << " [#{(ring.ring_type.scan(/StronglyIntransitive|[A-Z][a-z]*/)*", ").downcase}]"
77
+ end
78
+
79
+ def mapping_pragma(entity_type, ignore_independence = false)
80
+ ti = entity_type.all_type_inheritance_as_subtype
81
+ assimilation = ti.map{|t| t.assimilation }.compact[0]
82
+ return "" unless (entity_type.is_independent && !ignore_independence) || assimilation
83
+ " [" +
84
+ [
85
+ entity_type.is_independent && !ignore_independence ? "independent" : nil,
86
+ assimilation || nil
87
+ ].compact*", " +
88
+ "]"
89
+ end
90
+
91
+ # If this entity_type is identified by a single value, return four relevant objects:
92
+ def value_role_identification(entity_type, identifying_facts)
93
+ external_identifying_facts = identifying_facts - [entity_type.fact_type]
94
+ fact_type = external_identifying_facts[0]
95
+ ftr = fact_type && fact_type.all_role.sort_by{|role| role.ordinal}
96
+ if external_identifying_facts.size == 1 and
97
+ entity_role = ftr[n = (ftr[0].object_type == entity_type ? 0 : 1)] and
98
+ value_role = ftr[1-n] and
99
+ value_player = value_role.object_type and
100
+ value_player.is_a?(ActiveFacts::Metamodel::ValueType) and
101
+ value_name = value_player.name and
102
+ value_residual = value_name.sub(%r{^#{entity_role.object_type.name} ?},'') and
103
+ value_residual != '' and
104
+ value_residual != value_name
105
+ [fact_type, entity_role, value_role, value_residual]
106
+ else
107
+ []
108
+ end
109
+ end
110
+
111
+ # This entity is identified by a single value, so find whether standard refmode readings were used
112
+ def detect_standard_refmode_readings fact_type, entity_role, value_role
113
+ forward_reading = reverse_reading = nil
114
+ fact_type.all_reading.each do |reading|
115
+ if reading.text =~ /^\{(\d)\} has \{\d\}$/
116
+ if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == entity_role
117
+ forward_reading = reading
118
+ else
119
+ reverse_reading = reading
120
+ end
121
+ elsif reading.text =~ /^\{(\d)\} is of \{\d\}$/
122
+ if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == value_role
123
+ reverse_reading = reading
124
+ else
125
+ forward_reading = reading
126
+ end
127
+ end
128
+ end
129
+ trace :mode, "Didn't find standard forward reading" unless forward_reading
130
+ trace :mode, "Didn't find standard reverse reading" unless reverse_reading
131
+ [forward_reading, reverse_reading]
132
+ end
133
+
134
+ # If this entity_type is identified by a reference mode, return the verbalisation
135
+ def identified_by_ref_mode(entity_type, identifying_facts)
136
+ fact_type, entity_role, value_role, value_residual =
137
+ *value_role_identification(entity_type, identifying_facts)
138
+ return nil unless fact_type
139
+
140
+ # This EntityType is identified by its association with a single ValueType
141
+ # whose name is an extension (the value_residual) of the EntityType's name.
142
+ # If we have at least one of the standard refmode readings, dump it that way,
143
+ # else exit and use the long-hand verbalisation instead.
144
+
145
+ forward_reading, reverse_reading =
146
+ *detect_standard_refmode_readings(fact_type, entity_role, value_role)
147
+ return nil unless (forward_reading || reverse_reading)
148
+
149
+ # We can't subscript reference modes.
150
+ # If an objectified fact type has a role played by its identifying player, go long-hand.
151
+ return nil if entity_type.fact_type and
152
+ entity_type.fact_type.all_role.detect{|role| role.object_type == value_role.object_type }
153
+
154
+ fact_type.ordered_dumped! # We've covered this fact type
155
+
156
+ # Elide the constraints that would have been emitted on the standard readings.
157
+ # If there is a UC that's not in the standard form for a reference mode,
158
+ # we have to emit the standard reading anyhow.
159
+ fact_constraints = @presence_constraints_by_fact[fact_type]
160
+ fact_constraints.each do |pc|
161
+ if (pc.role_sequence.all_role_ref.size == 1 and pc.max_frequency == 1)
162
+ # It's a uniqueness constraint, and will be regenerated
163
+ pc.ordered_dumped!
164
+ end
165
+ end
166
+
167
+ # Figure out which non-standard readings exist, if any:
168
+ nonstandard_readings = fact_type.all_reading - [forward_reading, reverse_reading]
169
+ trace :mode, "--- nonstandard_readings.size now = #{nonstandard_readings.size}" if nonstandard_readings.size > 0
170
+
171
+ verbaliser = ActiveFacts::CQL::Verbaliser.new
172
+
173
+ # The verbaliser needs to have a Player for the roles of entity_type, so it doesn't get subscripted.
174
+ entity_roles =
175
+ nonstandard_readings.map{|r| r.role_sequence.all_role_ref.detect{|rr| rr.role.object_type == entity_type}}.compact
176
+ verbaliser.role_refs_have_same_player entity_roles
177
+
178
+ verbaliser.alternate_readings nonstandard_readings
179
+ if entity_type.fact_type
180
+ verbaliser.alternate_readings entity_type.fact_type.all_reading
181
+ end
182
+
183
+ verbaliser.create_subscripts(:rolenames) # Ok, the Verbaliser is ready to fly
184
+
185
+ fact_readings =
186
+ nonstandard_readings.map { |reading| expanded_reading(verbaliser, reading, fact_constraints, true) }
187
+ fact_readings +=
188
+ fact_readings_with_constraints(verbaliser, entity_type.fact_type) if entity_type.fact_type
189
+
190
+ # If we emitted a reading for the refmode, it'll include any role_value_constraint already
191
+ if nonstandard_readings.size == 0 and c = value_role.role_value_constraint
192
+ constraint_text = " "+c.as_cql
193
+ end
194
+ (entity_type.is_independent ? ' independent' : '') +
195
+ " identified by its #{value_residual}#{constraint_text}#{mapping_pragma(entity_type, true)}" +
196
+ entity_type.concept.all_context_note_as_relevant_concept.map do |cn|
197
+ cn.verbalise
198
+ end.join("\n") +
199
+ (fact_readings.size > 0 ? " where\n\t" : "") +
200
+ fact_readings*",\n\t"
201
+ end
202
+
203
+ def identified_by_roles_and_facts(entity_type, identifying_role_refs, identifying_facts)
204
+ # Detect standard reference-mode scenarios:
205
+ if srm = identified_by_ref_mode(entity_type, identifying_facts)
206
+ return srm
207
+ end
208
+
209
+ verbaliser = ActiveFacts::CQL::Verbaliser.new
210
+
211
+ # Announce all the identifying fact roles to the verbaliser so it can decide on any necessary subscripting.
212
+ # The verbaliser needs to have a Player for the roles of entity_type, so it doesn't get subscripted.
213
+ entity_roles =
214
+ identifying_facts.map{|ft| ft.preferred_reading.role_sequence.all_role_ref.detect{|rr| rr.role.object_type == entity_type}}.compact
215
+ verbaliser.role_refs_have_same_player entity_roles
216
+ identifying_facts.each do |fact_type|
217
+ # The RoleRefs for corresponding roles across all readings are for the same player.
218
+ verbaliser.alternate_readings fact_type.all_reading
219
+ fact_type.ordered_dumped! unless fact_type.entity_type # Must dump objectification still!
220
+ end
221
+ verbaliser.create_subscripts(:rolenames)
222
+
223
+ irn = verbaliser.identifying_role_names identifying_role_refs
224
+
225
+ identifying_fact_text =
226
+ identifying_facts.map{|f|
227
+ fact_readings_with_constraints(verbaliser, f)
228
+ }.flatten*",\n\t"
229
+
230
+ (entity_type.is_independent ? ' independent' : '') +
231
+ " identified by #{ irn*" and " }" +
232
+ mapping_pragma(entity_type, true) +
233
+ entity_type.concept.all_context_note_as_relevant_concept.map do |cn|
234
+ cn.verbalise
235
+ end.join("\n") +
236
+ " where\n\t"+identifying_fact_text
237
+ end
238
+
239
+ def entity_type_banner
240
+ puts "/*\n * Entity Types\n */"
241
+ end
242
+
243
+ def entity_type_group_end
244
+ puts "\n"
245
+ end
246
+
247
+ def subtype_dump(o, supertypes, pi)
248
+ print "#{o.name} is a kind of #{
249
+ o.is_independent ? 'independent ' : ''
250
+ }#{ o.supertypes.map(&:name)*", " }"
251
+ if pi
252
+ puts identified_by(o, pi)+';'
253
+ return
254
+ end
255
+
256
+ print mapping_pragma(o, true)
257
+
258
+ if o.fact_type
259
+ verbaliser = ActiveFacts::CQL::Verbaliser.new
260
+ # Announce all the objectified fact roles to the verbaliser so it can decide on any necessary subscripting.
261
+ # The RoleRefs for corresponding roles across all readings are for the same player.
262
+ verbaliser.alternate_readings o.fact_type.all_reading
263
+ verbaliser.create_subscripts(:rolenames)
264
+
265
+ print " where\n\t" + fact_readings_with_constraints(verbaliser, o.fact_type)*",\n\t"
266
+ end
267
+ puts ";\n"
268
+ end
269
+
270
+ def non_subtype_dump(o, pi)
271
+ puts "#{o.name} is" + identified_by(o, pi) + ';'
272
+ end
273
+
274
+ def naiive_expand(reading)
275
+ role_refs = reading.role_sequence.all_role_ref_in_order
276
+ reading.text.gsub(/\{(\d+)\}/) do
277
+ role_refs[$1.to_i].role.object_type.name
278
+ end
279
+ end
280
+
281
+ def fact_type_dump(fact_type, name)
282
+
283
+ if (o = fact_type.entity_type)
284
+ print "#{o.name} is"
285
+ supertypes = o.supertypes
286
+ if supertypes.empty?
287
+ print ' independent' if o.is_independent
288
+ else
289
+ print " a kind of#{
290
+ o.is_independent ? ' independent' : ''
291
+ } #{ supertypes.map(&:name)*', ' }"
292
+ end
293
+
294
+ # Alternate identification of objectified fact type?
295
+ primary_supertype = supertypes[0]
296
+ pi = fact_type.entity_type.preferred_identifier
297
+ if pi && primary_supertype && primary_supertype.preferred_identifier != pi
298
+ puts identified_by(o, pi) + ';'
299
+ return
300
+ end
301
+ print " where\n\t"
302
+ end
303
+
304
+ # Check whether this fact type has readings which could be confused for a previously-dumped one:
305
+ reading_texts = fact_type.all_reading.map{|r| naiive_expand(r)}
306
+ if reading_texts.size > 1
307
+ ambiguity =
308
+ fact_type.all_role.to_a[0].object_type.all_role.map{|r| r.fact_type}.
309
+ select{|f| f != fact_type && f.ordered_dumped }.
310
+ detect do |dft|
311
+ ambiguous_readings =
312
+ reading_texts & dft.all_reading.map{|r| naiive_expand(r)}
313
+ ambiguous_readings.size > 0
314
+ end
315
+ if ambiguity
316
+ puts fact_type.default_reading([], true)+'; // Avoid ambiguity; this is a new fact type'
317
+ end
318
+ end
319
+
320
+ # There can be no roles of the objectified fact type in the readings, so no need to tell the Verbaliser anything special
321
+ verbaliser = ActiveFacts::CQL::Verbaliser.new
322
+ verbaliser.alternate_readings fact_type.all_reading
323
+ pr = fact_type.preferred_reading
324
+ if (pr.role_sequence.all_role_ref.to_a[0].play)
325
+ verbaliser.prepare_role_sequence pr.role_sequence
326
+ end
327
+ verbaliser.create_subscripts(:rolenames)
328
+
329
+ print(fact_readings_with_constraints(verbaliser, fact_type)*",\n\t")
330
+ if (pr.role_sequence.all_role_ref.to_a[0].play)
331
+ print " where\n\t"+verbaliser.verbalise_over_role_sequence(pr.role_sequence)
332
+ end
333
+ puts(';')
334
+ end
335
+
336
+ def fact_type_banner
337
+ puts "/*\n * Fact Types\n */"
338
+ end
339
+
340
+ def fact_type_end
341
+ puts "\n"
342
+ end
343
+
344
+ def constraint_banner
345
+ puts "/*\n * Constraints:"
346
+ puts " */"
347
+ end
348
+
349
+ def constraint_end
350
+ end
351
+
352
+ # Of the players of a set of roles, return the one that's a subclass of (or same as) all others, else nil
353
+ def roleplayer_subclass(roles)
354
+ roles[1..-1].inject(roles[0].object_type){|subclass, role|
355
+ next nil unless subclass and EntityType === role.object_type
356
+ role.object_type.supertypes_transitive.include?(subclass) ? role.object_type : nil
357
+ }
358
+ end
359
+
360
+ def dump_presence_constraint(c)
361
+ # Loose binding in PresenceConstraints is limited to explicit role players (in an occurs list)
362
+ # having no exact match, but having instead exactly one role of the same player in the readings.
363
+
364
+ verbaliser = ActiveFacts::CQL::Verbaliser.new
365
+ # For a mandatory constraint (min_frequency == 1, max == nil or 1) any subtyping step is over the proximate role player
366
+ # For all other presence constraints any subtyping step is over the counterpart player
367
+ role_proximity = c.min_frequency == 1 && [nil, 1].include?(c.max_frequency) ? :proximate : :counterpart
368
+ if role_proximity == :proximate
369
+ verbaliser.role_refs_have_subtype_steps(c.role_sequence)
370
+ else
371
+ join_over, joined_roles = ActiveFacts::Metamodel.plays_over(c.role_sequence.all_role_ref.map{|rr|rr.role}, role_proximity)
372
+ verbaliser.roles_have_same_player(joined_roles) if join_over
373
+ end
374
+
375
+ verbaliser.prepare_role_sequence(c.role_sequence, join_over)
376
+ # REVISIT: Need to discount role_adjuncts in here, since this constraint uses loose binding:
377
+ verbaliser.create_subscripts :loose
378
+
379
+ expanded_readings = verbaliser.verbalise_over_role_sequence(c.role_sequence, nil, role_proximity)
380
+ if c.min_frequency == 1 && c.max_frequency == nil and c.role_sequence.all_role_ref.size == 2
381
+ puts "either #{expanded_readings*' or '};"
382
+ else
383
+ roles = c.role_sequence.all_role_ref.map{|rr| rr.role }
384
+ players = c.role_sequence.all_role_ref.map{|rr| verbaliser.subscripted_player(rr) }
385
+ players.uniq! if role_proximity == :proximate
386
+ min, max = c.min_frequency, c.max_frequency
387
+ pl = (min&&min>1)||(max&&max>1) ? 's' : ''
388
+ puts \
389
+ "each #{players.size > 1 ? "combination " : ""}#{players*", "} occurs #{c.frequency} time#{pl} in\n\t"+
390
+ "#{Array(expanded_readings)*",\n\t"};"
391
+ end
392
+ end
393
+
394
+ def dump_set_comparison_constraint(c)
395
+ scrs = c.all_set_comparison_roles.sort_by{|scr| scr.ordinal}
396
+ role_sequences = scrs.map{|scr|scr.role_sequence}
397
+ transposed_role_refs = scrs.map{|scr| scr.role_sequence.all_role_ref_in_order.to_a}.transpose
398
+ verbaliser = ActiveFacts::CQL::Verbaliser.new
399
+
400
+ # Tell the verbaliser all we know, so it can figure out which players to subscript:
401
+ players = []
402
+ trace :subscript, "Preparing query across projected roles in set comparison constraint" do
403
+ transposed_role_refs.each do |role_refs|
404
+ verbaliser.role_refs_have_subtype_steps role_refs
405
+ join_over, = ActiveFacts::Metamodel.plays_over(role_refs.map{|rr| rr.role})
406
+ players << join_over
407
+ end
408
+ end
409
+ trace :subscript, "Preparing query between roles in set comparison constraint" do
410
+ role_sequences.each do |role_sequence|
411
+ trace :subscript, "role sequence is #{role_sequence.describe}" do
412
+ verbaliser.prepare_role_sequence role_sequence
413
+ end
414
+ end
415
+ end
416
+ verbaliser.create_subscripts :normal
417
+
418
+ if role_sequences.detect{|scr| scr.all_role_ref.detect{|rr| rr.play}}
419
+ # This set constraint has an explicit query. Verbalise it.
420
+
421
+ readings_list = role_sequences.
422
+ map do |rs|
423
+ verbaliser.verbalise_over_role_sequence(rs)
424
+ end
425
+ if c.is_a?(ActiveFacts::Metamodel::SetEqualityConstraint)
426
+ puts readings_list.join("\n\tif and only if\n\t") + ';'
427
+ return
428
+ end
429
+ if readings_list.size == 2 && c.is_mandatory # XOR constraint
430
+ puts "either " + readings_list.join(" or ") + " but not both;"
431
+ return
432
+ end
433
+
434
+ # Internal check: We must have located the players here
435
+ if i = players.index(nil)
436
+ rrs = transposed_role_refs[i]
437
+ raise "Internal error detecting constrained object types in query involving #{rrs.map{|rr| rr.role.fact_type.default_reading}.uniq*', '}"
438
+ end
439
+
440
+ # Loose binding will apply only to the constrained roles, not to all roles. Not handled here.
441
+ mode = c.is_mandatory ? "exactly one" : "at most one"
442
+ puts "for each #{players.map{|p| p.name}*", "} #{mode} of these holds:\n\t" +
443
+ readings_list.join(",\n\t") +
444
+ ';'
445
+ return
446
+ end
447
+
448
+ if c.is_a?(ActiveFacts::Metamodel::SetEqualityConstraint)
449
+ puts \
450
+ scrs.map{|scr|
451
+ verbaliser.verbalise_over_role_sequence(scr.role_sequence)
452
+ } * "\n\tif and only if\n\t" + ";"
453
+ return
454
+ end
455
+
456
+ # A constrained role may involve a subtyping step. We substitute the name of the supertype for all occurrences.
457
+ players = transposed_role_refs.map{|role_refs| common_supertype(role_refs.map{|rr| rr.role.object_type})}
458
+ raise "Constraint must cover matching roles" if players.compact.size < players.size
459
+
460
+ readings_expanded = scrs.
461
+ map do |scr|
462
+ # verbaliser.verbalise_over_role_sequence(scr.role_sequence)
463
+ # REVISIT: verbalise_over_role_sequence cannot do what we need here, because of the
464
+ # possibility of subtyping steps in the constrained roles across the different scr's
465
+ # The following code uses "players" and "constrained_roles" to create substitutions.
466
+ # These should instead be passed to the verbaliser (one variable per index, role_refs for each).
467
+ fact_types_processed = {}
468
+ constrained_roles = scr.role_sequence.all_role_ref_in_order.map{|rr| rr.role}
469
+ join_over, joined_roles = *Metamodel.plays_over(constrained_roles)
470
+ constrained_roles.map do |constrained_role|
471
+ fact_type = constrained_role.fact_type
472
+ next nil if fact_types_processed[fact_type] # Don't emit the same fact type twice (in case of objectification step)
473
+ fact_types_processed[fact_type] = true
474
+ reading = fact_type.reading_preferably_starting_with_role(constrained_role)
475
+ expand_constrained(verbaliser, reading, constrained_roles, players)
476
+ end.compact * " and "
477
+ end
478
+
479
+ if scrs.size == 2 && c.is_mandatory
480
+ puts "either " + readings_expanded*" or " + " but not both;"
481
+ else
482
+ mode = c.is_mandatory ? "exactly one" : "at most one"
483
+ puts "for each #{players.map{|p| p.name}*", "} #{mode} of these holds:\n\t" +
484
+ readings_expanded*",\n\t" + ';'
485
+ end
486
+ end
487
+
488
+ def dump_subset_constraint(c)
489
+ # If the role players are identical and not duplicated, we can simply say "reading1 only if reading2"
490
+ subset_roles, subset_fact_types =
491
+ c.subset_role_sequence.all_role_ref_in_order.map{|rr| [rr.role, rr.role.fact_type]}.transpose
492
+ superset_roles, superset_fact_types =
493
+ c.superset_role_sequence.all_role_ref_in_order.map{|rr| [rr.role, rr.role.fact_type]}.transpose
494
+ transposed_role_refs = [c.subset_role_sequence, c.superset_role_sequence].map{|rs| rs.all_role_ref_in_order.to_a}.transpose
495
+
496
+ verbaliser = ActiveFacts::CQL::Verbaliser.new
497
+ transposed_role_refs.each { |role_refs| verbaliser.role_refs_have_subtype_steps role_refs }
498
+ verbaliser.prepare_role_sequence c.subset_role_sequence
499
+ verbaliser.prepare_role_sequence c.superset_role_sequence
500
+ verbaliser.create_subscripts :normal
501
+
502
+ puts \
503
+ verbaliser.verbalise_over_role_sequence(c.subset_role_sequence) +
504
+ "\n\tonly if " +
505
+ verbaliser.verbalise_over_role_sequence(c.superset_role_sequence) +
506
+ ";"
507
+ end
508
+
509
+ def dump_ring_constraint(c)
510
+ # At present, no ring constraint can be missed to be handled in this pass
511
+ puts "// #{c.ring_type} ring over #{c.role.fact_type.default_reading}"
512
+ end
513
+
514
+ def constraint_dump(c)
515
+ case c
516
+ when ActiveFacts::Metamodel::PresenceConstraint
517
+ dump_presence_constraint(c)
518
+ when ActiveFacts::Metamodel::RingConstraint
519
+ dump_ring_constraint(c)
520
+ when ActiveFacts::Metamodel::SetComparisonConstraint # includes SetExclusionConstraint, SetEqualityConstraint
521
+ dump_set_comparison_constraint(c)
522
+ when ActiveFacts::Metamodel::SubsetConstraint
523
+ dump_subset_constraint(c)
524
+ else
525
+ "#{c.class.basename} #{c.name}: unhandled constraint type"
526
+ end
527
+ end
528
+
529
+ # Find the common supertype of these object_types.
530
+ def common_supertype(object_types)
531
+ common = object_types[0].supertypes_transitive
532
+ object_types[1..-1].each do |object_type|
533
+ common &= object_type.supertypes_transitive
534
+ end
535
+ common[0]
536
+ end
537
+
538
+ #============================================================
539
+ # Verbalisation functions for fact type and entity type definitions
540
+ #============================================================
541
+
542
+ def fact_readings_with_constraints(verbaliser, fact_type)
543
+ fact_constraints = @presence_constraints_by_fact[fact_type]
544
+ readings = []
545
+ define_role_names = true
546
+ fact_type.all_reading_by_ordinal.each do |reading|
547
+ readings << expanded_reading(verbaliser, reading, fact_constraints, define_role_names)
548
+ define_role_names = false # No need to define role names in subsequent readings
549
+ end
550
+ readings
551
+ end
552
+
553
+ def expanded_reading(verbaliser, reading, fact_constraints, define_role_names)
554
+ # Arrange the roles in order they occur in this reading:
555
+ role_refs = reading.role_sequence.all_role_ref_in_order
556
+ role_numbers = reading.text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
557
+ roles = role_numbers.map{|m| role_refs[m].role }
558
+
559
+ # Find the constraints that constrain frequency over each role we can verbalise:
560
+ frequency_constraints = []
561
+ value_constraints = []
562
+ roles.each do |role|
563
+ value_constraints <<
564
+ if vc = role.role_value_constraint and !vc.ordered_dumped
565
+ vc.ordered_dumped!
566
+ vc.describe
567
+ else
568
+ nil
569
+ end
570
+
571
+ frequency_constraints <<
572
+ if (role == roles.last) # On the last role of the reading, emit any presence constraint
573
+ constraint = fact_constraints.
574
+ detect do |c| # Find a UC that spans all other Roles
575
+ c.is_a?(ActiveFacts::Metamodel::PresenceConstraint) &&
576
+ !c.ordered_dumped && # Already verbalised
577
+ roles-c.role_sequence.all_role_ref.map(&:role) == [role]
578
+ end
579
+ constraint.ordered_dumped! if constraint
580
+ constraint && constraint.frequency
581
+ else
582
+ nil
583
+ end
584
+ end
585
+
586
+ expanded = verbaliser.expand_reading(reading, frequency_constraints, define_role_names, value_constraints)
587
+ expanded = "it is not the case that "+expanded if (reading.is_negative)
588
+
589
+ if (ft_rings = @ring_constraints_by_fact[reading.fact_type]) &&
590
+ (ring = ft_rings.detect{|rc| !rc.ordered_dumped})
591
+ ring.ordered_dumped!
592
+ append_ring_to_reading(expanded, ring)
593
+ end
594
+ expanded
595
+ end
596
+
597
+ # Expand this reading, substituting players[i].name for the each role in the i'th position in constrained_roles
598
+ def expand_constrained(verbaliser, reading, constrained_roles, players)
599
+ # Make sure that we refer to the constrained players by their common supertype (as passed in)
600
+ frequency_constraints = reading.role_sequence.all_role_ref.
601
+ map do |role_ref|
602
+ player = role_ref.role.object_type
603
+ i = constrained_roles.index(role_ref.role)
604
+ player = players[i] if i
605
+ [ nil, player.name ]
606
+ end
607
+ frequency_constraints = [] unless frequency_constraints.detect{|fc| fc[0] != "some" }
608
+
609
+ expanded = verbaliser.expand_reading(reading, frequency_constraints)
610
+ expanded = "it is not the case that "+expanded if (reading.is_negative)
611
+ expanded
612
+ end
613
+
614
+ def build_indices
615
+ @presence_constraints_by_fact = Hash.new{ |h, k| h[k] = [] }
616
+ @ring_constraints_by_fact = Hash.new{ |h, k| h[k] = [] }
617
+
618
+ @vocabulary.all_constraint.each { |c|
619
+ case c
620
+ when ActiveFacts::Metamodel::PresenceConstraint
621
+ fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq # All fact types spanned by this constraint
622
+ if fact_types.size == 1 # There's only one, save it:
623
+ # trace "Single-fact constraint on #{fact_types[0].concept.guid}: #{c.name}"
624
+ (@presence_constraints_by_fact[fact_types[0]] ||= []) << c
625
+ end
626
+ when ActiveFacts::Metamodel::RingConstraint
627
+ (@ring_constraints_by_fact[c.role.fact_type] ||= []) << c
628
+ else
629
+ # trace "Found unhandled constraint #{c.class} #{c.name}"
630
+ end
631
+ }
632
+ end
633
+
634
+ end
635
+ end
636
+
637
+ module Metamodel
638
+ class ValueType
639
+ def as_cql
640
+ parameters =
641
+ [ length != 0 || scale != 0 ? length : nil,
642
+ scale != 0 ? scale : nil
643
+ ].compact
644
+ parameters = parameters.length > 0 ? "("+parameters.join(",")+")" : ""
645
+
646
+ "#{name
647
+ } #{
648
+ (is_independent ? '[independent] ' : '')
649
+ }is written as #{
650
+ (supertype || self).name
651
+ }#{
652
+ parameters
653
+ }#{
654
+ unit && " "+unit.name
655
+ }#{
656
+ transaction_phase && " auto-assigned at "+transaction_phase
657
+ }#{
658
+ concept.all_context_note_as_relevant_concept.map do |cn|
659
+ cn.verbalise
660
+ end.join("\n")
661
+ }#{
662
+ value_constraint && " "+value_constraint.describe
663
+ };"
664
+ end
665
+ end
666
+
667
+ class Unit
668
+ def as_cql
669
+ if !ephemera_url
670
+ if coefficient
671
+ # REVISIT: Use a smarter algorithm to switch to exponential form when there'd be lots of zeroes.
672
+ coefficient.numerator.to_s('F') +
673
+
674
+ if d = coefficient.denominator and d != 1
675
+ "/#{d}"
676
+ else
677
+ ''
678
+ end +
679
+
680
+ ' '
681
+ else
682
+ '1 '
683
+ end
684
+ else
685
+ ''
686
+ end +
687
+
688
+ all_derivation_as_derived_unit.
689
+ sort_by{|d| d.base_unit.name}.
690
+ # REVISIT: Sort base units
691
+ # REVISIT: convert negative powers to division?
692
+ map do |der|
693
+ base = der.base_unit
694
+ "#{base.name}#{der.exponent and der.exponent != 1 ? "^#{der.exponent}" : ''} "
695
+ end*'' +
696
+
697
+ if o = offset and o != 0
698
+ "+ #{o.to_s('F')} "
699
+ else
700
+ ''
701
+ end +
702
+
703
+ "converts to #{name}#{plural_name ? '/'+plural_name : ''}" +
704
+
705
+ (coefficient && !coefficient.is_precise ? ' approximately' : '') +
706
+
707
+ (ephemera_url ? " ephemera #{ephemera_url}" : '') +
708
+
709
+ ';'
710
+ end
711
+ end
712
+ end
713
+ end
714
+
715
+ ActiveFacts::Registry.generator('cql', ActiveFacts::Generators::CQL)