activefacts 0.6.0
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.
- data/History.txt +4 -0
- data/Manifest.txt +83 -0
- data/README.rdoc +81 -0
- data/Rakefile +41 -0
- data/bin/afgen +46 -0
- data/bin/cql +52 -0
- data/examples/CQL/Address.cql +46 -0
- data/examples/CQL/Blog.cql +54 -0
- data/examples/CQL/CompanyDirectorEmployee.cql +51 -0
- data/examples/CQL/Death.cql +16 -0
- data/examples/CQL/Genealogy.cql +95 -0
- data/examples/CQL/Marriage.cql +18 -0
- data/examples/CQL/Metamodel.cql +238 -0
- data/examples/CQL/MultiInheritance.cql +19 -0
- data/examples/CQL/OilSupply.cql +47 -0
- data/examples/CQL/Orienteering.cql +108 -0
- data/examples/CQL/PersonPlaysGame.cql +17 -0
- data/examples/CQL/SchoolActivities.cql +31 -0
- data/examples/CQL/SimplestUnary.cql +12 -0
- data/examples/CQL/SubtypePI.cql +32 -0
- data/examples/CQL/Warehousing.cql +99 -0
- data/examples/CQL/WindowInRoomInBldg.cql +22 -0
- data/lib/activefacts.rb +10 -0
- data/lib/activefacts/api.rb +25 -0
- data/lib/activefacts/api/concept.rb +384 -0
- data/lib/activefacts/api/constellation.rb +106 -0
- data/lib/activefacts/api/entity.rb +239 -0
- data/lib/activefacts/api/instance.rb +54 -0
- data/lib/activefacts/api/numeric.rb +158 -0
- data/lib/activefacts/api/role.rb +94 -0
- data/lib/activefacts/api/standard_types.rb +67 -0
- data/lib/activefacts/api/support.rb +59 -0
- data/lib/activefacts/api/value.rb +122 -0
- data/lib/activefacts/api/vocabulary.rb +120 -0
- data/lib/activefacts/cql.rb +31 -0
- data/lib/activefacts/cql/CQLParser.treetop +104 -0
- data/lib/activefacts/cql/Concepts.treetop +112 -0
- data/lib/activefacts/cql/DataTypes.treetop +66 -0
- data/lib/activefacts/cql/Expressions.treetop +113 -0
- data/lib/activefacts/cql/FactTypes.treetop +185 -0
- data/lib/activefacts/cql/Language/English.treetop +92 -0
- data/lib/activefacts/cql/LexicalRules.treetop +169 -0
- data/lib/activefacts/cql/Rakefile +6 -0
- data/lib/activefacts/cql/parser.rb +88 -0
- data/lib/activefacts/generate/absorption.rb +87 -0
- data/lib/activefacts/generate/cql.rb +441 -0
- data/lib/activefacts/generate/cql/html.rb +397 -0
- data/lib/activefacts/generate/null.rb +19 -0
- data/lib/activefacts/generate/ordered.rb +557 -0
- data/lib/activefacts/generate/ruby.rb +326 -0
- data/lib/activefacts/generate/sql/server.rb +164 -0
- data/lib/activefacts/generate/text.rb +21 -0
- data/lib/activefacts/input/cql.rb +1268 -0
- data/lib/activefacts/input/orm.rb +926 -0
- data/lib/activefacts/persistence.rb +1 -0
- data/lib/activefacts/persistence/composition.rb +653 -0
- data/lib/activefacts/support.rb +51 -0
- data/lib/activefacts/version.rb +3 -0
- data/lib/activefacts/vocabulary.rb +6 -0
- data/lib/activefacts/vocabulary/extensions.rb +343 -0
- data/lib/activefacts/vocabulary/metamodel.rb +303 -0
- data/script/txt2html +71 -0
- data/spec/absorption_spec.rb +95 -0
- data/spec/api/autocounter.rb +82 -0
- data/spec/api/constellation.rb +130 -0
- data/spec/api/entity_type.rb +101 -0
- data/spec/api/instance.rb +428 -0
- data/spec/api/roles.rb +122 -0
- data/spec/api/value_type.rb +112 -0
- data/spec/api_spec.rb +14 -0
- data/spec/cql_cql_spec.rb +58 -0
- data/spec/cql_parse_spec.rb +31 -0
- data/spec/cql_ruby_spec.rb +60 -0
- data/spec/cql_sql_spec.rb +54 -0
- data/spec/cql_symbol_tables_spec.rb +259 -0
- data/spec/cql_unit_spec.rb +336 -0
- data/spec/cqldump_spec.rb +169 -0
- data/spec/norma_cql_spec.rb +48 -0
- data/spec/norma_ruby_spec.rb +50 -0
- data/spec/norma_sql_spec.rb +45 -0
- data/spec/norma_tables_spec.rb +94 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- metadata +173 -0
@@ -0,0 +1,397 @@
|
|
1
|
+
#
|
2
|
+
# Generate HTML-highlighted CQL from an ActiveFacts vocabulary.
|
3
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
|
+
#
|
5
|
+
# The text generated here is pre-formatted, and in spans haing the following styles:
|
6
|
+
# keyword: ORM2 standard colour is #00C (blue)
|
7
|
+
# concept: ORM2 standard concept is #808 (purple)
|
8
|
+
# copula: ORM2 standard concept is #060 (green)
|
9
|
+
#
|
10
|
+
require 'activefacts/vocabulary'
|
11
|
+
require 'activefacts/api'
|
12
|
+
require 'activefacts/generate/ordered'
|
13
|
+
require 'activefacts/generate/cql'
|
14
|
+
|
15
|
+
module ActiveFacts
|
16
|
+
|
17
|
+
module Generate
|
18
|
+
class CQL
|
19
|
+
class HTML < CQL
|
20
|
+
|
21
|
+
def initialize(vocabulary, *options)
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def puts s
|
26
|
+
super(s.gsub(/[,;]/) do |p| keyword p; end)
|
27
|
+
end
|
28
|
+
|
29
|
+
def keyword(str)
|
30
|
+
"<span class='keyword'>#{str}</span>"
|
31
|
+
end
|
32
|
+
|
33
|
+
def concept(str)
|
34
|
+
"<span class='concept'>#{str}</span>"
|
35
|
+
end
|
36
|
+
|
37
|
+
def copula(str)
|
38
|
+
"<span class='copula'>#{str}</span>"
|
39
|
+
end
|
40
|
+
|
41
|
+
def vocabulary_start(vocabulary)
|
42
|
+
puts %q{<head>
|
43
|
+
<link rel="stylesheet" href="css/orm2.css" type="text/css"/>
|
44
|
+
</head>
|
45
|
+
<pre class="copula">}
|
46
|
+
puts "#{keyword "vocabulary"} #{concept(vocabulary.name)};\n\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
def vocabulary_end
|
50
|
+
puts %q{</pre>}
|
51
|
+
end
|
52
|
+
|
53
|
+
def value_type_banner
|
54
|
+
puts "/*\n * Value Types\n */"
|
55
|
+
end
|
56
|
+
|
57
|
+
def value_type_dump(o)
|
58
|
+
return unless o.supertype # An imported type
|
59
|
+
if o.name == o.supertype.name
|
60
|
+
# In ActiveFacts, parameterising a ValueType will create a new datatype
|
61
|
+
# throw Can't handle parameterized value type of same name as its datatype" if ...
|
62
|
+
end
|
63
|
+
|
64
|
+
parameters =
|
65
|
+
[ o.length != 0 || o.scale != 0 ? o.length : nil,
|
66
|
+
o.scale != 0 ? o.scale : nil
|
67
|
+
].compact
|
68
|
+
parameters = parameters.length > 0 ? "("+parameters.join(",")+")" : "()"
|
69
|
+
|
70
|
+
puts "#{concept o.name} #{keyword "is defined as"} #{concept o.supertype.name + parameters }#{
|
71
|
+
if (o.value_restriction)
|
72
|
+
keyword("restricted to")+
|
73
|
+
o.value_restriction.all_allowed_range.map{|ar|
|
74
|
+
# REVISIT: Need to display as string or numeric according to type here...
|
75
|
+
min = ar.value_range.minimum_bound
|
76
|
+
max = ar.value_range.maximum_bound
|
77
|
+
|
78
|
+
range = (min ? min.value : "") +
|
79
|
+
(min.value != (max&&max.value) ? (".." + (max ? max.value : "")) : "")
|
80
|
+
keyword range
|
81
|
+
}*", "
|
82
|
+
else
|
83
|
+
""
|
84
|
+
end
|
85
|
+
};"
|
86
|
+
end
|
87
|
+
|
88
|
+
def append_ring_to_reading(reading, ring)
|
89
|
+
reading << keyword(" [#{(ring.ring_type.scan(/[A-Z][a-z]*/)*", ").downcase}]")
|
90
|
+
end
|
91
|
+
|
92
|
+
def identified_by_roles_and_facts(entity_type, identifying_roles, identifying_facts, preferred_readings)
|
93
|
+
identifying_role_names = identifying_roles.map{|role|
|
94
|
+
preferred_role_ref = preferred_readings[role.fact_type].role_sequence.all_role_ref.detect{|reading_rr|
|
95
|
+
reading_rr.role == role
|
96
|
+
}
|
97
|
+
role_words = []
|
98
|
+
# REVISIT: Consider whether NOT to use the adjective if it's a prefix of the role_name
|
99
|
+
|
100
|
+
role_name = role.role_name
|
101
|
+
role_name = nil if role_name == ""
|
102
|
+
# debug "concept.name=#{preferred_role_ref.role.concept.name}, role_name=#{role_name.inspect}, preferred_role_name=#{preferred_role_ref.role.role_name.inspect}"
|
103
|
+
|
104
|
+
if (role.fact_type.all_role.size == 1)
|
105
|
+
# REVISIT: Guard against unary reading containing the illegal words "and" and "where".
|
106
|
+
role.fact_type.default_reading # Need whole reading for a unary.
|
107
|
+
elsif (role_name)
|
108
|
+
role_name
|
109
|
+
else
|
110
|
+
role_words << preferred_role_ref.leading_adjective if preferred_role_ref.leading_adjective != ""
|
111
|
+
role_words << preferred_role_ref.role.concept.name
|
112
|
+
role_words << preferred_role_ref.trailing_adjective if preferred_role_ref.trailing_adjective != ""
|
113
|
+
role_words.compact*"-"
|
114
|
+
end
|
115
|
+
}
|
116
|
+
|
117
|
+
# REVISIT: Consider emitting extra fact types here, instead of in entity_type_dump?
|
118
|
+
# Just beware that readings having the same players will be considered to be of the same fact type, even if they're not.
|
119
|
+
|
120
|
+
# Detect standard reference-mode scenarios
|
121
|
+
ft = identifying_facts[0]
|
122
|
+
fact_constraints = nil
|
123
|
+
if identifying_facts.size == 1 and
|
124
|
+
entity_role = ft.all_role[n = (ft.all_role[0].concept == entity_type ? 0 : 1)] and
|
125
|
+
value_role = ft.all_role[1-n] and
|
126
|
+
value_name = value_role.concept.name and
|
127
|
+
residual = value_name.gsub(%r{#{entity_role.concept.name}},'') and
|
128
|
+
residual != '' and
|
129
|
+
residual != value_name
|
130
|
+
|
131
|
+
# The EntityType is identified by its association with a single ValueType
|
132
|
+
# whose name is an extension (the residual) of the EntityType's name.
|
133
|
+
|
134
|
+
# Detect standard reference-mode readings:
|
135
|
+
forward_reading = reverse_reading = nil
|
136
|
+
ft.all_reading.each do |reading|
|
137
|
+
if reading.reading_text =~ /^\{(\d)\} has \{\d\}$/
|
138
|
+
if reading.role_sequence.all_role_ref[$1.to_i].role == entity_role
|
139
|
+
forward_reading = reading
|
140
|
+
else
|
141
|
+
reverse_reading = reading
|
142
|
+
end
|
143
|
+
elsif reading.reading_text =~ /^\{(\d)\} is of \{\d\}$/
|
144
|
+
if reading.role_sequence.all_role_ref[$1.to_i].role == value_role
|
145
|
+
reverse_reading = reading
|
146
|
+
else
|
147
|
+
forward_reading = reading
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
debug :mode, "------------------- Didn't find standard forward reading" unless forward_reading
|
153
|
+
debug :mode, "------------------- Didn't find standard reverse reading" unless reverse_reading
|
154
|
+
|
155
|
+
# If we didn't find at least one of the standard readings, don't use a refmode:
|
156
|
+
if (forward_reading || reverse_reading)
|
157
|
+
# Elide the constraints that would have been emitted on those readings.
|
158
|
+
# If there is a UC that's not in the standard form for a reference mode,
|
159
|
+
# we have to emit the standard reading anyhow.
|
160
|
+
fact_constraints = @presence_constraints_by_fact[ft]
|
161
|
+
fact_constraints.each do |pc|
|
162
|
+
if (pc.role_sequence.all_role_ref.size == 1 and pc.max_frequency == 1)
|
163
|
+
# It's a uniqueness constraint, and will be regenerated
|
164
|
+
@constraints_used[pc] = true
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
@fact_types_dumped[ft] = true
|
169
|
+
|
170
|
+
# Figure out whether any non-standard readings exist:
|
171
|
+
other_readings = ft.all_reading - [forward_reading] - [reverse_reading]
|
172
|
+
debug :mode, "--- other_readings.size now = #{other_readings.size}" if other_readings.size > 0
|
173
|
+
|
174
|
+
fact_text = other_readings.map do |reading|
|
175
|
+
expanded_reading(reading, fact_constraints, true)
|
176
|
+
end*",\n\t"
|
177
|
+
return keyword(" identified by its ") +
|
178
|
+
concept(residual) +
|
179
|
+
(fact_text != "" ? keyword(" where\n\t") + fact_text : "")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
identifying_facts.each{|f| @fact_types_dumped[f] = true }
|
184
|
+
@identifying_fact_text =
|
185
|
+
identifying_facts.map{|f|
|
186
|
+
fact_readings_with_constraints(f, fact_constraints)
|
187
|
+
}.flatten*",\n\t"
|
188
|
+
|
189
|
+
keyword(" identified by ") +
|
190
|
+
identifying_role_names.map{|n| concept n} * keyword(" and ") +
|
191
|
+
keyword(" where\n\t") +
|
192
|
+
@identifying_fact_text
|
193
|
+
end
|
194
|
+
|
195
|
+
def show_frequency role, constraint
|
196
|
+
# REVISIT: Need to also colorize the adjectives here:
|
197
|
+
[ constraint ? keyword(constraint.frequency) : nil, concept(role.concept.name) ]
|
198
|
+
end
|
199
|
+
|
200
|
+
def entity_type_banner
|
201
|
+
puts(keyword("/*\n * Entity Types\n */"))
|
202
|
+
end
|
203
|
+
|
204
|
+
def fact_readings(fact_type)
|
205
|
+
constrained_fact_readings = fact_readings_with_constraints(fact_type)
|
206
|
+
constrained_fact_readings*",\n\t"
|
207
|
+
end
|
208
|
+
|
209
|
+
def subtype_dump(o, supertypes, pi)
|
210
|
+
print "#{concept o.name} #{keyword "is a kind of"} #{ o.supertypes.map(&:name).map{|n| concept n}*keyword(", ") }"
|
211
|
+
if pi
|
212
|
+
print identified_by(o, pi)
|
213
|
+
end
|
214
|
+
# If there's a preferred_identifier for this subtype, identifying readings were emitted
|
215
|
+
if o.fact_type
|
216
|
+
print(
|
217
|
+
(pi ? "," : keyword(" where")) +
|
218
|
+
"\n\t" +
|
219
|
+
fact_readings(o.fact_type)
|
220
|
+
)
|
221
|
+
end
|
222
|
+
puts ";\n"
|
223
|
+
end
|
224
|
+
|
225
|
+
def non_subtype_dump(o, pi)
|
226
|
+
print "#{concept(o.name)} #{keyword "is"}" +
|
227
|
+
identified_by(o, pi)
|
228
|
+
print(keyword(" where\n\t") + fact_readings(o.fact_type)) if o.fact_type
|
229
|
+
puts ";\n"
|
230
|
+
end
|
231
|
+
|
232
|
+
def fact_type_dump(fact_type, name)
|
233
|
+
|
234
|
+
@identifying_fact_text = nil
|
235
|
+
if (o = fact_type.entity_type)
|
236
|
+
print "#{concept o.name} #{keyword "is"}"
|
237
|
+
if !o.all_type_inheritance_by_subtype.empty?
|
238
|
+
print(keyword(" a kind of ") + o.supertypes.map(&:name).map{|n| concept n}*", ")
|
239
|
+
end
|
240
|
+
|
241
|
+
# Alternate identification of objectified fact type?
|
242
|
+
primary_supertype = o.supertypes[0]
|
243
|
+
pi = fact_type.entity_type.preferred_identifier
|
244
|
+
if pi && primary_supertype && primary_supertype.preferred_identifier != pi
|
245
|
+
print identified_by(o, pi)
|
246
|
+
print ";\n"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
unless @identifying_fact_text
|
251
|
+
print(keyword(" where\n\t")) if o
|
252
|
+
puts(fact_readings(fact_type)+";")
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def fact_type_banner
|
257
|
+
puts keyword("/*\n * Fact Types\n */")
|
258
|
+
end
|
259
|
+
|
260
|
+
def constraint_banner
|
261
|
+
puts keyword("/*\n * Constraints:\n */")
|
262
|
+
end
|
263
|
+
|
264
|
+
def dump_presence_constraint(c)
|
265
|
+
roles = c.role_sequence.all_role_ref.map{|rr| rr.role }
|
266
|
+
|
267
|
+
# REVISIT: If only one role is covered and it's mandatory >=1 constraint, use SOME/THAT form:
|
268
|
+
# each Bug SOME Tester logged THAT Bug;
|
269
|
+
players = c.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}.uniq
|
270
|
+
|
271
|
+
fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
|
272
|
+
puts \
|
273
|
+
"#{keyword "each #{players.size > 1 ? "combination " : ""}"}"+
|
274
|
+
"#{players.map{|n| concept n}*", "} "+
|
275
|
+
"#{keyword "occurs #{c.frequency} time in"}\n\t"+
|
276
|
+
"#{fact_types.map{|ft| ft.default_reading([], nil)}*",\n\t"}" +
|
277
|
+
";"
|
278
|
+
end
|
279
|
+
|
280
|
+
def dump_set_constraint(c)
|
281
|
+
# REVISIT exclusion: every <player-list> must<?> either reading1, reading2, ...
|
282
|
+
|
283
|
+
# Each constraint involves two or more occurrences of one or more players.
|
284
|
+
# For each player, a subtype may be involved in the occurrences.
|
285
|
+
# Find the common supertype of each player.
|
286
|
+
scrs = c.all_set_comparison_roles
|
287
|
+
player_count = scrs[0].role_sequence.all_role_ref.size
|
288
|
+
role_seq_count = scrs.size
|
289
|
+
|
290
|
+
#raise "Can't verbalise constraint over many players and facts" if player_count > 1 and role_seq_count > 1
|
291
|
+
|
292
|
+
players_differ = [] # Record which players are also played by subclasses
|
293
|
+
players = (0...player_count).map do |pi|
|
294
|
+
# Find the common supertype of the players of the pi'th role in each sequence
|
295
|
+
concepts = scrs.map{|r| r.role_sequence.all_role_ref[pi].role.concept }
|
296
|
+
player, players_differ[pi] = common_supertype(concepts)
|
297
|
+
raise "Role sequences of #{c.class.basename} must have concepts matching #{c.name} in position #{pi}" unless player
|
298
|
+
player
|
299
|
+
end
|
300
|
+
#puts "#{c.class.basename} has players #{players.map{|p| p.name}*", "}"
|
301
|
+
|
302
|
+
if (SetEqualityConstraint === c)
|
303
|
+
# REVISIT: Need a proper approach to some/that and adjective disambiguation:
|
304
|
+
puts \
|
305
|
+
scrs.map{|scr|
|
306
|
+
scr.role_sequence.all_role_ref.map{|rr|
|
307
|
+
rr.role.fact_type.default_reading([], nil)
|
308
|
+
}*keyword(" and ")
|
309
|
+
} * keyword("\n\tif and only if\n\t") + ";"
|
310
|
+
return
|
311
|
+
end
|
312
|
+
|
313
|
+
mode = c.is_mandatory ? "exactly one" : "at most one"
|
314
|
+
puts "#{keyword "for each"} #{players.map{|p| concept p.name}*", "} #{keyword(mode + " of these holds")}:\n\t" +
|
315
|
+
(scrs.map do |scr|
|
316
|
+
constrained_roles = scr.role_sequence.all_role_ref.map{|rr| rr.role }
|
317
|
+
fact_types = constrained_roles.map{|r| r.fact_type }.uniq
|
318
|
+
|
319
|
+
fact_types.map do |fact_type|
|
320
|
+
# REVISIT: future: Use "THAT" and "SOME" only when:
|
321
|
+
# - the role player occurs twice in the reading, or
|
322
|
+
# - is a subclass of the constrained concept, or
|
323
|
+
reading = fact_type.preferred_reading
|
324
|
+
expand_constrained(reading, constrained_roles, players, players_differ)
|
325
|
+
end * keyword(" and ")
|
326
|
+
|
327
|
+
end*",\n\t"
|
328
|
+
)+';'
|
329
|
+
end
|
330
|
+
|
331
|
+
# Expand this reading using (in)definite articles where needed
|
332
|
+
# Handle any roles in constrained_roles specially.
|
333
|
+
def expand_constrained(reading, constrained_roles, players, players_differ)
|
334
|
+
frequency_constraints = reading.role_sequence.all_role_ref.map {|role_ref|
|
335
|
+
i = constrained_roles.index(role_ref.role)
|
336
|
+
if !i
|
337
|
+
v = [ "some", role_ref.role.concept.name]
|
338
|
+
elsif players_differ[i]
|
339
|
+
v = [ "that", players[i].name ] # Make sure to use the superclass name
|
340
|
+
else
|
341
|
+
if reading.fact_type.all_role.select{|r| r.concept == role_ref.role.concept }.size > 1
|
342
|
+
v = [ "that", role_ref.role.concept.name ]
|
343
|
+
else
|
344
|
+
v = [ "some", role_ref.role.concept.name ]
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
v[0] = keyword(v[0])
|
349
|
+
v[1] = concept(v[1])
|
350
|
+
v
|
351
|
+
}
|
352
|
+
frequency_constraints = [] unless frequency_constraints.detect{|fc| fc[0] =~ /some/ }
|
353
|
+
|
354
|
+
#$stderr.puts "fact_type roles (#{fact_type.all_role.map{|r| r.concept.name}*","}) default_reading '#{fact_type.preferred_reading.reading_text}' roles (#{fact_type.preferred_reading.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}*","}) #{frequency_constraints.inspect}"
|
355
|
+
|
356
|
+
# REVISIT: Make sure that we refer to the constrained players by their common supertype
|
357
|
+
|
358
|
+
reading.expand(frequency_constraints, nil)
|
359
|
+
end
|
360
|
+
|
361
|
+
def dump_subset_constraint(c)
|
362
|
+
# If the role players are identical and not duplicated, we can simply say "reading1 only if reading2"
|
363
|
+
subset_roles = c.subset_role_sequence.all_role_ref.map{|rr| rr.role}
|
364
|
+
superset_roles = c.superset_role_sequence.all_role_ref.map{|rr| rr.role}
|
365
|
+
|
366
|
+
subset_players = subset_roles.map(&:concept)
|
367
|
+
superset_players = superset_roles.map(&:concept)
|
368
|
+
|
369
|
+
subset_fact_types = c.subset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }.uniq
|
370
|
+
superset_fact_types = c.superset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }.uniq
|
371
|
+
|
372
|
+
# We need to ensure that if the player of any constrained role also exists
|
373
|
+
# as the player of a role that's not a constrained role, there are different
|
374
|
+
# adjectives or other qualifiers qualifier applied to distinguish that role.
|
375
|
+
fact_type_roles = (subset_fact_types+superset_fact_types).map{|ft| ft.all_role }.flatten
|
376
|
+
non_constrained_roles = fact_type_roles - subset_roles - superset_roles
|
377
|
+
if (r = non_constrained_roles.detect{|r| (subset_roles+superset_roles).include?(r) })
|
378
|
+
# REVISIT: Find a way to deal with this problem, should it arise.
|
379
|
+
|
380
|
+
# It would help, but not entirely fix it, to use SOME/THAT to identify the constrained roles.
|
381
|
+
# See ServiceDirector's DataStore<->Client fact types for example
|
382
|
+
# Use SOME on the subset, THAT on the superset.
|
383
|
+
raise "Critical ambiguity, #{r.concept.name} occurs both constrained and unconstrained in #{c.name}"
|
384
|
+
end
|
385
|
+
|
386
|
+
puts \
|
387
|
+
"#{subset_fact_types.map{|ft| ft.default_reading([], nil)}*" and "}" +
|
388
|
+
"\n\t#{keyword "only if"} " +
|
389
|
+
"#{superset_fact_types.map{|ft| ft.default_reading([], nil)}*" and "}" +
|
390
|
+
";"
|
391
|
+
end
|
392
|
+
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#
|
2
|
+
# Generate text output for ActiveFacts vocabularies.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2007 Clifford Heath. Read the LICENSE file.
|
5
|
+
# Author: Clifford Heath.
|
6
|
+
#
|
7
|
+
module ActiveFacts
|
8
|
+
module Generate
|
9
|
+
class NULL
|
10
|
+
def initialize(vocabulary)
|
11
|
+
@vocabulary = vocabulary
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate(out = $>)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,557 @@
|
|
1
|
+
#
|
2
|
+
# Generator superclass for ActiveFacts vocabularies that performs sequencing to avoid forward references.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
5
|
+
#
|
6
|
+
require 'activefacts/api'
|
7
|
+
|
8
|
+
module ActiveFacts
|
9
|
+
|
10
|
+
class OrderedDumper #:nodoc:
|
11
|
+
include Metamodel
|
12
|
+
|
13
|
+
def initialize(vocabulary, *options)
|
14
|
+
@vocabulary = vocabulary
|
15
|
+
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
16
|
+
options.each{|option| set_option(option) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_option(option)
|
20
|
+
end
|
21
|
+
|
22
|
+
def puts(*a)
|
23
|
+
@out.puts *a
|
24
|
+
end
|
25
|
+
|
26
|
+
def print(*a)
|
27
|
+
@out.print *a
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate(out = $>)
|
31
|
+
@out = out
|
32
|
+
vocabulary_start(@vocabulary)
|
33
|
+
|
34
|
+
build_indices
|
35
|
+
@concept_types_dumped = {}
|
36
|
+
@fact_types_dumped = {}
|
37
|
+
value_types_dump()
|
38
|
+
entity_types_dump()
|
39
|
+
fact_types_dump()
|
40
|
+
constraints_dump(@constraints_used)
|
41
|
+
vocabulary_end
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_indices
|
45
|
+
@presence_constraints_by_fact = Hash.new{ |h, k| h[k] = [] }
|
46
|
+
@ring_constraints_by_fact = Hash.new{ |h, k| h[k] = [] }
|
47
|
+
|
48
|
+
@vocabulary.all_constraint.each { |c|
|
49
|
+
case c
|
50
|
+
when PresenceConstraint
|
51
|
+
fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq # All fact types spanned by this constraint
|
52
|
+
if fact_types.size == 1 # There's only one, save it:
|
53
|
+
# debug "Single-fact constraint on #{fact_types[0].fact_type_id}: #{c.name}"
|
54
|
+
(@presence_constraints_by_fact[fact_types[0]] ||= []) << c
|
55
|
+
end
|
56
|
+
when RingConstraint
|
57
|
+
(@ring_constraints_by_fact[c.role.fact_type] ||= []) << c
|
58
|
+
else
|
59
|
+
# debug "Found unhandled constraint #{c.class} #{c.name}"
|
60
|
+
end
|
61
|
+
}
|
62
|
+
@constraints_used = {}
|
63
|
+
end
|
64
|
+
|
65
|
+
def value_types_dump
|
66
|
+
done_banner = false
|
67
|
+
@vocabulary.all_feature.sort_by{|o| o.name}.each{|o|
|
68
|
+
next unless ValueType === o
|
69
|
+
|
70
|
+
value_type_banner unless done_banner
|
71
|
+
done_banner = true
|
72
|
+
|
73
|
+
value_type_dump(o)
|
74
|
+
@concept_types_dumped[o] = true
|
75
|
+
}
|
76
|
+
value_type_end if done_banner
|
77
|
+
end
|
78
|
+
|
79
|
+
# Try to dump entity types in order of name, but we need
|
80
|
+
# to dump ETs before they're referenced in preferred ids
|
81
|
+
# if possible (it's not always, there may be loops!)
|
82
|
+
def entity_types_dump
|
83
|
+
# Build hash tables of precursors and followers to use:
|
84
|
+
precursors, followers = *build_entity_dependencies
|
85
|
+
|
86
|
+
done_banner = false
|
87
|
+
sorted = @vocabulary.all_feature.select{|o| EntityType === o and !o.fact_type }.sort_by{|o| o.name}
|
88
|
+
panic = nil
|
89
|
+
while true do
|
90
|
+
count_this_pass = 0
|
91
|
+
skipped_this_pass = 0
|
92
|
+
sorted.each{|o|
|
93
|
+
next if @concept_types_dumped[o] # Already done
|
94
|
+
|
95
|
+
# Can we do this yet?
|
96
|
+
if (o != panic and # We don't *have* to do it (panic mode)
|
97
|
+
(p = precursors[o]) and # There might be...
|
98
|
+
p.size > 0) # precursors - still blocked
|
99
|
+
skipped_this_pass += 1
|
100
|
+
next
|
101
|
+
end
|
102
|
+
|
103
|
+
entity_type_banner unless done_banner
|
104
|
+
done_banner = true
|
105
|
+
|
106
|
+
# We're going to emit o - remove it from precursors of others:
|
107
|
+
(followers[o]||[]).each{|f|
|
108
|
+
precursors[f] -= [o]
|
109
|
+
}
|
110
|
+
count_this_pass += 1
|
111
|
+
panic = nil
|
112
|
+
|
113
|
+
entity_type_dump(o)
|
114
|
+
released_fact_types_dump(o)
|
115
|
+
|
116
|
+
entity_type_group_end
|
117
|
+
}
|
118
|
+
|
119
|
+
# Check that we made progress if there's any to make:
|
120
|
+
if count_this_pass == 0 && skipped_this_pass > 0
|
121
|
+
if panic # We were already panicing... what to do now?
|
122
|
+
# This won't happen again unless the above code is changed to decide it can't dump "panic".
|
123
|
+
raise "Unresolvable cycle of forward references: " +
|
124
|
+
(bad = sorted.select{|o| EntityType === o && !@concept_types_dumped[o]}).map{|o| o.name }.inspect +
|
125
|
+
":\n\t" + bad.map{|o|
|
126
|
+
o.name +
|
127
|
+
": " +
|
128
|
+
precursors[o].map{|p| p.name}.uniq.inspect
|
129
|
+
} * "\n\t" + "\n"
|
130
|
+
else
|
131
|
+
# Find the object that has the most followers and no fwd-ref'd supertypes:
|
132
|
+
# This selection might be better if we allow PI roles to be fwd-ref'd...
|
133
|
+
panic = sorted.
|
134
|
+
select{|o| !@concept_types_dumped[o] }.
|
135
|
+
sort_by{|o|
|
136
|
+
f = followers[o] || [];
|
137
|
+
o.supertypes.detect{|s| !@concept_types_dumped[s] } ? 0 : -f.size
|
138
|
+
}[0]
|
139
|
+
# debug "Panic mode, selected #{panic.name} next"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
break if skipped_this_pass == 0 # All done.
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def entity_type_dump(o)
|
149
|
+
@concept_types_dumped[o] = true
|
150
|
+
pi = o.preferred_identifier
|
151
|
+
|
152
|
+
supers = o.supertypes
|
153
|
+
if (supers.size > 0)
|
154
|
+
# Ignore identification by a supertype:
|
155
|
+
pi = nil if pi && pi.role_sequence.all_role_ref[0].role.fact_type.is_a?(TypeInheritance)
|
156
|
+
subtype_dump(o, supers, pi)
|
157
|
+
else
|
158
|
+
non_subtype_dump(o, pi)
|
159
|
+
end
|
160
|
+
@constraints_used[pi] = true
|
161
|
+
end
|
162
|
+
|
163
|
+
def identified_by(o, pi)
|
164
|
+
# Different adjectives might be used for different readings.
|
165
|
+
# Here, we must find the role_ref containing the adjectives that we need for each identifier,
|
166
|
+
# which will be attached to the uniqueness constraint on this object in the binary FT that
|
167
|
+
# attaches that identifying role.
|
168
|
+
role_refs = pi.role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
|
169
|
+
|
170
|
+
# We need to get the adjectives for the roles from the identifying fact's preferred readings:
|
171
|
+
identifying_facts = role_refs.map{|rr| rr.role.fact_type }.uniq
|
172
|
+
preferred_readings = identifying_facts.inject({}){|reading_hash, fact_type|
|
173
|
+
pr = fact_type.preferred_reading
|
174
|
+
reading_hash[fact_type] = pr
|
175
|
+
reading_hash
|
176
|
+
}
|
177
|
+
#p identifying_facts.map{|f| f.preferred_reading }
|
178
|
+
|
179
|
+
identifying_roles = role_refs.map(&:role)
|
180
|
+
identification = identified_by_roles_and_facts(o, identifying_roles, identifying_facts, preferred_readings)
|
181
|
+
#identifying_facts.each{|f| @fact_types_dumped[f] = true }
|
182
|
+
|
183
|
+
identification
|
184
|
+
end
|
185
|
+
|
186
|
+
def fact_readings_with_constraints(fact_type, fact_constraints = nil)
|
187
|
+
define_role_names = true
|
188
|
+
fact_constraints ||= @presence_constraints_by_fact[fact_type]
|
189
|
+
readings = fact_type.all_reading_by_ordinal.inject([]) do |reading_array, reading|
|
190
|
+
reading_array << expanded_reading(reading, fact_constraints, define_role_names)
|
191
|
+
|
192
|
+
define_role_names = false # No need to define role names in subsequent readings
|
193
|
+
|
194
|
+
reading_array
|
195
|
+
end
|
196
|
+
|
197
|
+
readings
|
198
|
+
end
|
199
|
+
|
200
|
+
def expanded_reading(reading, fact_constraints, define_role_names)
|
201
|
+
# Find all role numbers in order of occurrence in this reading:
|
202
|
+
role_refs = reading.role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
|
203
|
+
role_numbers = reading.reading_text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
|
204
|
+
roles = role_numbers.map{|m| role_refs[m].role }
|
205
|
+
# debug "Considering #{reading.reading_text} having #{role_numbers.inspect}"
|
206
|
+
|
207
|
+
# Find the constraints that constrain frequency over each role we can verbalise:
|
208
|
+
frequency_constraints = []
|
209
|
+
roles.each do |role|
|
210
|
+
# Find a mandatory constraint that's *not* unique; this will need an extra reading
|
211
|
+
role_is_first_in = reading.fact_type.all_reading.detect{|r|
|
212
|
+
role == r.role_sequence.all_role_ref.sort_by{|role_ref|
|
213
|
+
role_ref.ordinal
|
214
|
+
}[0].role
|
215
|
+
}
|
216
|
+
|
217
|
+
if (role == roles.last) # First role of the reading?
|
218
|
+
# REVISIT: With a ternary, doing this on other than the last role can be ambiguous,
|
219
|
+
# in case both the 2nd and 3rd roles have frequencies. Think some more!
|
220
|
+
|
221
|
+
constraint = fact_constraints.find{|c| # Find a UC that spans all other Roles
|
222
|
+
# internal uniqueness constraints span all roles but one, the residual:
|
223
|
+
PresenceConstraint === c &&
|
224
|
+
!@constraints_used[c] && # Already verbalised
|
225
|
+
roles-c.role_sequence.all_role_ref.map(&:role) == [role]
|
226
|
+
}
|
227
|
+
# Index the frequency implied by the constraint under the role position in the reading
|
228
|
+
if constraint # Mark this constraint as "verbalised" so we don't do it again:
|
229
|
+
@constraints_used[constraint] = true
|
230
|
+
end
|
231
|
+
frequency_constraints << show_frequency(role, constraint)
|
232
|
+
else
|
233
|
+
frequency_constraints << show_frequency(role, nil)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
expanded = reading.expand(frequency_constraints, define_role_names)
|
238
|
+
|
239
|
+
if (ft_rings = @ring_constraints_by_fact[reading.fact_type]) &&
|
240
|
+
(ring = ft_rings.detect{|rc| !@constraints_used[rc]})
|
241
|
+
@constraints_used[ring] = true
|
242
|
+
append_ring_to_reading(expanded, ring)
|
243
|
+
end
|
244
|
+
expanded
|
245
|
+
end
|
246
|
+
|
247
|
+
def show_frequency role, constraint
|
248
|
+
constraint ? constraint.frequency : nil
|
249
|
+
end
|
250
|
+
|
251
|
+
def describe_fact_type(fact_type, highlight = nil)
|
252
|
+
(fact_type.entity_type ? fact_type.entity_type.name : "")+
|
253
|
+
describe_roles(fact_type.all_role, highlight)
|
254
|
+
end
|
255
|
+
|
256
|
+
def describe_roles(roles, highlight = nil)
|
257
|
+
"("+
|
258
|
+
roles.map{|role| role.concept.name + (role == highlight ? "*" : "")}*", "+
|
259
|
+
")"
|
260
|
+
end
|
261
|
+
|
262
|
+
def describe_role_sequence(role_sequence)
|
263
|
+
"("+
|
264
|
+
role_sequence.all_role_ref.map{|role_ref| role_ref.role.concept.name }*", "+
|
265
|
+
")"
|
266
|
+
end
|
267
|
+
|
268
|
+
# This returns an array of two hash tables each keyed by an EntityType.
|
269
|
+
# The values of each hash entry are the precursors and followers (respectively) of that entity.
|
270
|
+
def build_entity_dependencies
|
271
|
+
@vocabulary.all_feature.inject([{},{}]) { |a, o|
|
272
|
+
if EntityType === o && !o.fact_type
|
273
|
+
precursor = a[0]
|
274
|
+
follower = a[1]
|
275
|
+
blocked = false
|
276
|
+
pi = o.preferred_identifier
|
277
|
+
if pi
|
278
|
+
pi.role_sequence.all_role_ref.each{|rr|
|
279
|
+
role = rr.role
|
280
|
+
player = role.concept
|
281
|
+
next unless EntityType === player
|
282
|
+
# player is a precursor of o
|
283
|
+
(precursor[o] ||= []) << player if (player != o)
|
284
|
+
(follower[player] ||= []) << o if (player != o)
|
285
|
+
}
|
286
|
+
end
|
287
|
+
# Supertypes are precursors too:
|
288
|
+
subtyping = o.all_type_inheritance_by_supertype
|
289
|
+
next a if subtyping.size == 0
|
290
|
+
subtyping.each{|ti|
|
291
|
+
# debug ti.class.roles.verbalise; debug "all_type_inheritance_by_supertype"; exit
|
292
|
+
s = ti.subtype
|
293
|
+
(precursor[s] ||= []) << o
|
294
|
+
(follower[o] ||= []) << s
|
295
|
+
}
|
296
|
+
end
|
297
|
+
a
|
298
|
+
}
|
299
|
+
end
|
300
|
+
|
301
|
+
# Dump all fact types for which all precursors (of which "o" is one) have been emitted:
|
302
|
+
def released_fact_types_dump(o)
|
303
|
+
roles = o.all_role
|
304
|
+
begin
|
305
|
+
progress = false
|
306
|
+
roles.map(&:fact_type).uniq.select{|fact_type|
|
307
|
+
# The fact type hasn't already been dumped but all its role players have
|
308
|
+
!@fact_types_dumped[fact_type] &&
|
309
|
+
!fact_type.all_role.detect{|r| !@concept_types_dumped[r.concept] }
|
310
|
+
}.sort_by{|fact_type|
|
311
|
+
fact_type_key(fact_type)
|
312
|
+
}.each{|fact_type|
|
313
|
+
fact_type_dump_with_dependents(fact_type)
|
314
|
+
# Objectified Fact Types may release additional fact types
|
315
|
+
roles += fact_type.entity_type.all_role if fact_type.entity_type
|
316
|
+
progress = true
|
317
|
+
}
|
318
|
+
end while progress
|
319
|
+
end
|
320
|
+
|
321
|
+
def skip_fact_type(f)
|
322
|
+
# REVISIT: There might be constraints we have to merge into the nested entity or subtype.
|
323
|
+
# These will come up as un-handled constraints:
|
324
|
+
pcs = @presence_constraints_by_fact[f]
|
325
|
+
TypeInheritance === f ||
|
326
|
+
(pcs && pcs.size > 0 && !pcs.detect{|c| !@constraints_used[c] })
|
327
|
+
end
|
328
|
+
|
329
|
+
# Dump one fact type.
|
330
|
+
# Include as many as possible internal constraints in the fact type readings.
|
331
|
+
def fact_type_dump_with_dependents(fact_type)
|
332
|
+
@fact_types_dumped[fact_type] = true
|
333
|
+
# debug "Trying to dump FT again" if @fact_types_dumped[fact_type]
|
334
|
+
return if skip_fact_type(fact_type)
|
335
|
+
|
336
|
+
if (et = fact_type.entity_type) &&
|
337
|
+
(pi = et.preferred_identifier) &&
|
338
|
+
pi.role_sequence.all_role_ref[0].role.fact_type != fact_type
|
339
|
+
# debug "Dumping objectified FT #{et.name} as an entity, non-fact PI"
|
340
|
+
entity_type_dump(et)
|
341
|
+
released_fact_types_dump(et)
|
342
|
+
return
|
343
|
+
end
|
344
|
+
|
345
|
+
fact_constraints = @presence_constraints_by_fact[fact_type]
|
346
|
+
|
347
|
+
# debug "for fact type #{fact_type.to_s}, considering\n\t#{fact_constraints.map(&:to_s)*",\n\t"}"
|
348
|
+
# debug "#{fact_type.name} has readings:\n\t#{fact_type.readings.map(&:name)*"\n\t"}"
|
349
|
+
# debug "Dumping #{fact_type.fact_type_id} as a fact type"
|
350
|
+
|
351
|
+
# Fact types that aren't nested have no names
|
352
|
+
name = fact_type.entity_type && fact_type.entity_type.name
|
353
|
+
|
354
|
+
fact_type_dump(fact_type, name)
|
355
|
+
|
356
|
+
# REVISIT: Go through the residual constraints and re-process appropriate readings to show them
|
357
|
+
|
358
|
+
@fact_types_dumped[fact_type] = true
|
359
|
+
@concept_types_dumped[fact_type.entity_type] = true if fact_type.entity_type
|
360
|
+
end
|
361
|
+
|
362
|
+
# Arrange for objectified fact types to appear in order of name, after other fact types.
|
363
|
+
# Facts are ordered alphabetically by the names of their role players,
|
364
|
+
# then by preferred_reading (subtyping fact types have no preferred_reading).
|
365
|
+
def fact_type_key(fact_type)
|
366
|
+
role_names =
|
367
|
+
if (pr = fact_type.preferred_reading)
|
368
|
+
pr.role_sequence.
|
369
|
+
all_role_ref.
|
370
|
+
sort_by{|role_ref| role_ref.ordinal}.
|
371
|
+
map{|role_ref| [ role_ref.leading_adjective, role_ref.role.concept.name, role_ref.trailing_adjective ].compact*"-" } +
|
372
|
+
[pr.reading_text]
|
373
|
+
else
|
374
|
+
fact_type.all_role.map{|role| role.concept.name }
|
375
|
+
end
|
376
|
+
|
377
|
+
(fact_type.entity_type ? [fact_type.entity_type.name] : [""]) + role_names
|
378
|
+
end
|
379
|
+
|
380
|
+
def role_ref_key(role_ref)
|
381
|
+
[ role_ref.leading_adjective, role_ref.role.concept.name, role_ref.trailing_adjective ].compact*"-"
|
382
|
+
end
|
383
|
+
|
384
|
+
# Dump fact types.
|
385
|
+
def fact_types_dump
|
386
|
+
# REVISIT: Uniqueness on the LHS of a binary can be coded using "distinct"
|
387
|
+
|
388
|
+
# The only fact types that can be remaining are those involving only value types,
|
389
|
+
# since we dumped every fact type as soon as all relevant entities were dumped.
|
390
|
+
# Iterate over all fact types of all value types, looking for these strays.
|
391
|
+
|
392
|
+
done_banner = false
|
393
|
+
fact_collection = @vocabulary.constellation.FactType
|
394
|
+
fact_collection.keys.select{|fact_id|
|
395
|
+
fact_type = fact_collection[fact_id]
|
396
|
+
!(TypeInheritance === fact_type) and
|
397
|
+
!@fact_types_dumped[fact_type] and
|
398
|
+
!skip_fact_type(fact_type) and
|
399
|
+
!fact_type.all_role.detect{|r| EntityType === r.concept }
|
400
|
+
}.sort_by{|fact_id|
|
401
|
+
fact_type = fact_collection[fact_id]
|
402
|
+
fact_type_key(fact_type)
|
403
|
+
}.each{|fact_id|
|
404
|
+
fact_type = fact_collection[fact_id]
|
405
|
+
|
406
|
+
fact_type_banner unless done_banner
|
407
|
+
done_banner = true
|
408
|
+
fact_type_dump_with_dependents(fact_type)
|
409
|
+
}
|
410
|
+
|
411
|
+
# REVISIT: Find out why some fact types are missed during entity dumping:
|
412
|
+
@vocabulary.constellation.FactType.values.select{|fact_type|
|
413
|
+
!(TypeInheritance === fact_type)
|
414
|
+
}.sort_by{|fact_type|
|
415
|
+
fact_type_key(fact_type)
|
416
|
+
}.each{|fact_type|
|
417
|
+
next if @fact_types_dumped[fact_type]
|
418
|
+
# debug "Not dumped #{fact_type.verbalise}(#{fact_type.all_role.map{|r| r.concept.name}*", "})"
|
419
|
+
fact_type_banner unless done_banner
|
420
|
+
done_banner = true
|
421
|
+
fact_type_dump_with_dependents(fact_type)
|
422
|
+
}
|
423
|
+
|
424
|
+
fact_type_end if done_banner
|
425
|
+
# unused = constraints - @constraints_used.keys
|
426
|
+
# debug "residual constraints are\n\t#{unused.map(&:to_s)*",\n\t"}"
|
427
|
+
|
428
|
+
@constraints_used
|
429
|
+
end
|
430
|
+
|
431
|
+
def fact_instances_dump
|
432
|
+
@vocabulary.fact_types.each{|f|
|
433
|
+
# Dump the instances:
|
434
|
+
f.facts.each{|i|
|
435
|
+
raise "REVISIT: Not dumping fact instances"
|
436
|
+
debug "\t\t"+i.to_s
|
437
|
+
}
|
438
|
+
}
|
439
|
+
end
|
440
|
+
|
441
|
+
def constraint_sort_key(c)
|
442
|
+
case c
|
443
|
+
when RingConstraint
|
444
|
+
[1, c.ring_type, c.role.concept.name, c.other_role.concept.name, c.name||""]
|
445
|
+
when SetComparisonConstraint
|
446
|
+
[2, c.all_set_comparison_roles.map{|scrs| scrs.role_sequence.all_role_ref.map{|rr| role_ref_key(rr)}}, c.name||""]
|
447
|
+
when SubsetConstraint
|
448
|
+
[3, [c.superset_role_sequence, c.subset_role_sequence].map{|rs| rs.all_role_ref.map{|rr| role_ref_key(rr)}}, c.name||""]
|
449
|
+
when PresenceConstraint
|
450
|
+
[4, c.role_sequence.all_role_ref.map{|rr| role_ref_key(rr)}, c.name||""]
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
def constraints_dump(except = {})
|
455
|
+
heading = false
|
456
|
+
@vocabulary.all_constraint.reject{|c| except[c]}.sort_by{ |c| constraint_sort_key(c) }.each do|c|
|
457
|
+
# Skip some PresenceConstraints:
|
458
|
+
if PresenceConstraint === c
|
459
|
+
# Skip uniqueness constraints that cover all roles of a fact type, they're implicit
|
460
|
+
role_refs = c.role_sequence.all_role_ref
|
461
|
+
if role_refs.size == 0
|
462
|
+
constraint_banner unless heading
|
463
|
+
heading = true
|
464
|
+
puts "PresenceConstraint without roles!"
|
465
|
+
next
|
466
|
+
end
|
467
|
+
fact_type0 = role_refs[0].role.fact_type
|
468
|
+
next if c.max_frequency == 1 && # Uniqueness
|
469
|
+
role_refs.size == fact_type0.all_role.size && # Same number of roles
|
470
|
+
fact_type0.all_role.all?{|r| role_refs.map(&:role).include? r} # All present
|
471
|
+
|
472
|
+
# Skip internal PresenceConstraints over TypeInheritances:
|
473
|
+
next if TypeInheritance === fact_type0 &&
|
474
|
+
!c.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type != fact_type0 }
|
475
|
+
end
|
476
|
+
|
477
|
+
constraint_banner unless heading
|
478
|
+
heading = true
|
479
|
+
|
480
|
+
# Skip presence constraints on value types:
|
481
|
+
# next if ActiveFacts::PresenceConstraint === c &&
|
482
|
+
# ActiveFacts::ValueType === c.concept
|
483
|
+
constraint_dump(c)
|
484
|
+
end
|
485
|
+
constraint_end if heading
|
486
|
+
end
|
487
|
+
|
488
|
+
def vocabulary_start(vocabulary)
|
489
|
+
debug "Should override vocabulary_start"
|
490
|
+
end
|
491
|
+
|
492
|
+
def vocabulary_end
|
493
|
+
debug "Should override vocabulary_end"
|
494
|
+
end
|
495
|
+
|
496
|
+
def value_type_banner
|
497
|
+
debug "Should override value_type_banner"
|
498
|
+
end
|
499
|
+
|
500
|
+
def value_type_end
|
501
|
+
debug "Should override value_type_end"
|
502
|
+
end
|
503
|
+
|
504
|
+
def value_type_dump(o)
|
505
|
+
debug "Should override value_type_dump"
|
506
|
+
end
|
507
|
+
|
508
|
+
def entity_type_banner
|
509
|
+
debug "Should override entity_type_banner"
|
510
|
+
end
|
511
|
+
|
512
|
+
def entity_type_group_end
|
513
|
+
debug "Should override entity_type_group_end"
|
514
|
+
end
|
515
|
+
|
516
|
+
def non_subtype_dump(o, pi)
|
517
|
+
debug "Should override non_subtype_dump"
|
518
|
+
end
|
519
|
+
|
520
|
+
def subtype_dump(o, supertypes, pi = nil)
|
521
|
+
debug "Should override subtype_dump"
|
522
|
+
end
|
523
|
+
|
524
|
+
def append_ring_to_reading(reading, ring)
|
525
|
+
debug "Should override append_ring_to_reading"
|
526
|
+
end
|
527
|
+
|
528
|
+
def fact_type_banner
|
529
|
+
debug "Should override fact_type_banner"
|
530
|
+
end
|
531
|
+
|
532
|
+
def fact_type_end
|
533
|
+
debug "Should override fact_type_end"
|
534
|
+
end
|
535
|
+
|
536
|
+
def fact_type_dump(fact_type, name)
|
537
|
+
debug "Should override fact_type_dump"
|
538
|
+
end
|
539
|
+
|
540
|
+
def constraint_banner
|
541
|
+
debug "Should override constraint_banner"
|
542
|
+
end
|
543
|
+
|
544
|
+
def constraint_end
|
545
|
+
debug "Should override constraint_end"
|
546
|
+
end
|
547
|
+
|
548
|
+
def constraint_dump(c)
|
549
|
+
debug "Should override constraint_dump"
|
550
|
+
end
|
551
|
+
|
552
|
+
end
|
553
|
+
|
554
|
+
def dump(vocabulary, out = $>)
|
555
|
+
OrderedDumper.new(vocabulary).dump(out)
|
556
|
+
end
|
557
|
+
end
|