activefacts 0.6.0

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