activefacts-generators 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
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)