activefacts-generators 1.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +30 -0
- data/Rakefile +6 -0
- data/activefacts-generators.gemspec +26 -0
- data/lib/activefacts/dependency_analyser.rb +182 -0
- data/lib/activefacts/generators/absorption.rb +71 -0
- data/lib/activefacts/generators/composition.rb +119 -0
- data/lib/activefacts/generators/cql.rb +715 -0
- data/lib/activefacts/generators/diagrams/json.rb +340 -0
- data/lib/activefacts/generators/help.rb +64 -0
- data/lib/activefacts/generators/helpers/inject.rb +16 -0
- data/lib/activefacts/generators/helpers/oo.rb +162 -0
- data/lib/activefacts/generators/helpers/ordered.rb +605 -0
- data/lib/activefacts/generators/helpers/rails.rb +57 -0
- data/lib/activefacts/generators/html/glossary.rb +462 -0
- data/lib/activefacts/generators/metadata/json.rb +204 -0
- data/lib/activefacts/generators/null.rb +32 -0
- data/lib/activefacts/generators/rails/models.rb +247 -0
- data/lib/activefacts/generators/rails/schema.rb +217 -0
- data/lib/activefacts/generators/ruby.rb +134 -0
- data/lib/activefacts/generators/sql/mysql.rb +281 -0
- data/lib/activefacts/generators/sql/server.rb +274 -0
- data/lib/activefacts/generators/stats.rb +70 -0
- data/lib/activefacts/generators/text.rb +29 -0
- data/lib/activefacts/generators/traits/datavault.rb +241 -0
- data/lib/activefacts/generators/traits/oo.rb +73 -0
- data/lib/activefacts/generators/traits/ordered.rb +33 -0
- data/lib/activefacts/generators/traits/ruby.rb +210 -0
- data/lib/activefacts/generators/transform/datavault.rb +303 -0
- data/lib/activefacts/generators/transform/surrogate.rb +215 -0
- data/lib/activefacts/registry.rb +11 -0
- 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)
|