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.
Files changed (84) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +83 -0
  3. data/README.rdoc +81 -0
  4. data/Rakefile +41 -0
  5. data/bin/afgen +46 -0
  6. data/bin/cql +52 -0
  7. data/examples/CQL/Address.cql +46 -0
  8. data/examples/CQL/Blog.cql +54 -0
  9. data/examples/CQL/CompanyDirectorEmployee.cql +51 -0
  10. data/examples/CQL/Death.cql +16 -0
  11. data/examples/CQL/Genealogy.cql +95 -0
  12. data/examples/CQL/Marriage.cql +18 -0
  13. data/examples/CQL/Metamodel.cql +238 -0
  14. data/examples/CQL/MultiInheritance.cql +19 -0
  15. data/examples/CQL/OilSupply.cql +47 -0
  16. data/examples/CQL/Orienteering.cql +108 -0
  17. data/examples/CQL/PersonPlaysGame.cql +17 -0
  18. data/examples/CQL/SchoolActivities.cql +31 -0
  19. data/examples/CQL/SimplestUnary.cql +12 -0
  20. data/examples/CQL/SubtypePI.cql +32 -0
  21. data/examples/CQL/Warehousing.cql +99 -0
  22. data/examples/CQL/WindowInRoomInBldg.cql +22 -0
  23. data/lib/activefacts.rb +10 -0
  24. data/lib/activefacts/api.rb +25 -0
  25. data/lib/activefacts/api/concept.rb +384 -0
  26. data/lib/activefacts/api/constellation.rb +106 -0
  27. data/lib/activefacts/api/entity.rb +239 -0
  28. data/lib/activefacts/api/instance.rb +54 -0
  29. data/lib/activefacts/api/numeric.rb +158 -0
  30. data/lib/activefacts/api/role.rb +94 -0
  31. data/lib/activefacts/api/standard_types.rb +67 -0
  32. data/lib/activefacts/api/support.rb +59 -0
  33. data/lib/activefacts/api/value.rb +122 -0
  34. data/lib/activefacts/api/vocabulary.rb +120 -0
  35. data/lib/activefacts/cql.rb +31 -0
  36. data/lib/activefacts/cql/CQLParser.treetop +104 -0
  37. data/lib/activefacts/cql/Concepts.treetop +112 -0
  38. data/lib/activefacts/cql/DataTypes.treetop +66 -0
  39. data/lib/activefacts/cql/Expressions.treetop +113 -0
  40. data/lib/activefacts/cql/FactTypes.treetop +185 -0
  41. data/lib/activefacts/cql/Language/English.treetop +92 -0
  42. data/lib/activefacts/cql/LexicalRules.treetop +169 -0
  43. data/lib/activefacts/cql/Rakefile +6 -0
  44. data/lib/activefacts/cql/parser.rb +88 -0
  45. data/lib/activefacts/generate/absorption.rb +87 -0
  46. data/lib/activefacts/generate/cql.rb +441 -0
  47. data/lib/activefacts/generate/cql/html.rb +397 -0
  48. data/lib/activefacts/generate/null.rb +19 -0
  49. data/lib/activefacts/generate/ordered.rb +557 -0
  50. data/lib/activefacts/generate/ruby.rb +326 -0
  51. data/lib/activefacts/generate/sql/server.rb +164 -0
  52. data/lib/activefacts/generate/text.rb +21 -0
  53. data/lib/activefacts/input/cql.rb +1268 -0
  54. data/lib/activefacts/input/orm.rb +926 -0
  55. data/lib/activefacts/persistence.rb +1 -0
  56. data/lib/activefacts/persistence/composition.rb +653 -0
  57. data/lib/activefacts/support.rb +51 -0
  58. data/lib/activefacts/version.rb +3 -0
  59. data/lib/activefacts/vocabulary.rb +6 -0
  60. data/lib/activefacts/vocabulary/extensions.rb +343 -0
  61. data/lib/activefacts/vocabulary/metamodel.rb +303 -0
  62. data/script/txt2html +71 -0
  63. data/spec/absorption_spec.rb +95 -0
  64. data/spec/api/autocounter.rb +82 -0
  65. data/spec/api/constellation.rb +130 -0
  66. data/spec/api/entity_type.rb +101 -0
  67. data/spec/api/instance.rb +428 -0
  68. data/spec/api/roles.rb +122 -0
  69. data/spec/api/value_type.rb +112 -0
  70. data/spec/api_spec.rb +14 -0
  71. data/spec/cql_cql_spec.rb +58 -0
  72. data/spec/cql_parse_spec.rb +31 -0
  73. data/spec/cql_ruby_spec.rb +60 -0
  74. data/spec/cql_sql_spec.rb +54 -0
  75. data/spec/cql_symbol_tables_spec.rb +259 -0
  76. data/spec/cql_unit_spec.rb +336 -0
  77. data/spec/cqldump_spec.rb +169 -0
  78. data/spec/norma_cql_spec.rb +48 -0
  79. data/spec/norma_ruby_spec.rb +50 -0
  80. data/spec/norma_sql_spec.rb +45 -0
  81. data/spec/norma_tables_spec.rb +94 -0
  82. data/spec/spec.opts +1 -0
  83. data/spec/spec_helper.rb +10 -0
  84. 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