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.
- 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)
|