activefacts 0.8.6 → 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/Manifest.txt +33 -2
  2. data/README.rdoc +30 -36
  3. data/Rakefile +16 -20
  4. data/bin/afgen +17 -11
  5. data/bin/cql +313 -36
  6. data/download.html +43 -19
  7. data/examples/CQL/Address.cql +15 -15
  8. data/examples/CQL/Blog.cql +8 -8
  9. data/examples/CQL/CompanyDirectorEmployee.cql +6 -5
  10. data/examples/CQL/Death.cql +3 -3
  11. data/examples/CQL/Diplomacy.cql +48 -0
  12. data/examples/CQL/Genealogy.cql +41 -41
  13. data/examples/CQL/Insurance.cql +311 -0
  14. data/examples/CQL/JoinEquality.cql +35 -0
  15. data/examples/CQL/Marriage.cql +1 -1
  16. data/examples/CQL/Metamodel.cql +290 -185
  17. data/examples/CQL/MetamodelNext.cql +420 -0
  18. data/examples/CQL/Monogamy.cql +24 -0
  19. data/examples/CQL/MonthInSeason.cql +27 -0
  20. data/examples/CQL/Moon.cql +23 -0
  21. data/examples/CQL/MultiInheritance.cql +4 -4
  22. data/examples/CQL/NonRoleId.cql +14 -0
  23. data/examples/CQL/OddIdentifier.cql +18 -0
  24. data/examples/CQL/OilSupply.cql +24 -24
  25. data/examples/CQL/OneToOnes.cql +17 -0
  26. data/examples/CQL/Orienteering.cql +55 -55
  27. data/examples/CQL/OrienteeringER.cql +58 -0
  28. data/examples/CQL/PersonPlaysGame.cql +2 -2
  29. data/examples/CQL/RedundantDependency.cql +34 -0
  30. data/examples/CQL/SchoolActivities.cql +5 -5
  31. data/examples/CQL/SeparateSubtype.cql +28 -0
  32. data/examples/CQL/ServiceDirector.cql +283 -0
  33. data/examples/CQL/SimplestUnary.cql +2 -2
  34. data/examples/CQL/SubtypePI.cql +11 -11
  35. data/examples/CQL/Supervision.cql +38 -0
  36. data/examples/CQL/Tests.Test5.Load.cql +38 -0
  37. data/examples/CQL/WaiterTips.cql +33 -0
  38. data/examples/CQL/Warehousing.cql +55 -53
  39. data/examples/CQL/WindowInRoomInBldg.cql +9 -9
  40. data/examples/CQL/unit.cql +433 -544
  41. data/examples/index.html +314 -170
  42. data/examples/intro.html +6 -176
  43. data/examples/local.css +8 -4
  44. data/index.html +40 -25
  45. data/lib/activefacts/api/concept.rb +2 -2
  46. data/lib/activefacts/api/constellation.rb +4 -4
  47. data/lib/activefacts/api/instance.rb +2 -2
  48. data/lib/activefacts/api/instance_index.rb +4 -0
  49. data/lib/activefacts/api/numeric.rb +3 -1
  50. data/lib/activefacts/api/role.rb +1 -1
  51. data/lib/activefacts/api/standard_types.rb +23 -16
  52. data/lib/activefacts/api/support.rb +3 -1
  53. data/lib/activefacts/api/vocabulary.rb +4 -0
  54. data/lib/activefacts/cql/CQLParser.treetop +87 -39
  55. data/lib/activefacts/cql/Concepts.treetop +95 -69
  56. data/lib/activefacts/cql/Context.treetop +11 -2
  57. data/lib/activefacts/cql/Expressions.treetop +23 -59
  58. data/lib/activefacts/cql/FactTypes.treetop +141 -95
  59. data/lib/activefacts/cql/Language/English.treetop +33 -21
  60. data/lib/activefacts/cql/LexicalRules.treetop +6 -1
  61. data/lib/activefacts/cql/Terms.treetop +75 -26
  62. data/lib/activefacts/cql/ValueTypes.treetop +52 -54
  63. data/lib/activefacts/cql/compiler.rb +46 -1691
  64. data/lib/activefacts/cql/compiler/constraint.rb +602 -0
  65. data/lib/activefacts/cql/compiler/entity_type.rb +425 -0
  66. data/lib/activefacts/cql/compiler/fact.rb +300 -0
  67. data/lib/activefacts/cql/compiler/fact_type.rb +230 -0
  68. data/lib/activefacts/cql/compiler/reading.rb +832 -0
  69. data/lib/activefacts/cql/compiler/shared.rb +109 -0
  70. data/lib/activefacts/cql/compiler/value_type.rb +104 -0
  71. data/lib/activefacts/cql/parser.rb +132 -81
  72. data/lib/activefacts/generate/cql.rb +397 -274
  73. data/lib/activefacts/generate/oo.rb +13 -12
  74. data/lib/activefacts/generate/ordered.rb +107 -117
  75. data/lib/activefacts/generate/ruby.rb +34 -38
  76. data/lib/activefacts/generate/sql/mysql.rb +62 -45
  77. data/lib/activefacts/generate/sql/server.rb +59 -42
  78. data/lib/activefacts/input/cql.rb +6 -3
  79. data/lib/activefacts/input/orm.rb +991 -557
  80. data/lib/activefacts/persistence/columns.rb +16 -12
  81. data/lib/activefacts/persistence/foreignkey.rb +7 -4
  82. data/lib/activefacts/persistence/index.rb +3 -4
  83. data/lib/activefacts/persistence/reference.rb +5 -2
  84. data/lib/activefacts/support.rb +20 -14
  85. data/lib/activefacts/version.rb +1 -1
  86. data/lib/activefacts/vocabulary.rb +1 -0
  87. data/lib/activefacts/vocabulary/extensions.rb +328 -44
  88. data/lib/activefacts/vocabulary/metamodel.rb +145 -20
  89. data/lib/activefacts/vocabulary/verbaliser.rb +621 -0
  90. data/spec/absorption_spec.rb +4 -4
  91. data/spec/api/value_type.rb +1 -1
  92. data/spec/cql/context_spec.rb +45 -22
  93. data/spec/cql/deontic_spec.rb +88 -0
  94. data/spec/cql/matching_spec.rb +517 -0
  95. data/spec/cql/samples_spec.rb +88 -31
  96. data/spec/cql/unit_spec.rb +58 -37
  97. data/spec/cql_cql_spec.rb +12 -7
  98. data/spec/cql_mysql_spec.rb +3 -7
  99. data/spec/cql_parse_spec.rb +0 -4
  100. data/spec/cql_ruby_spec.rb +1 -4
  101. data/spec/cql_sql_spec.rb +5 -18
  102. data/spec/cql_symbol_tables_spec.rb +3 -0
  103. data/spec/cqldump_spec.rb +0 -2
  104. data/spec/helpers/array_matcher.rb +35 -0
  105. data/spec/helpers/ctrl_c_support.rb +52 -0
  106. data/spec/helpers/diff_matcher.rb +38 -0
  107. data/spec/helpers/file_matcher.rb +5 -3
  108. data/spec/helpers/string_matcher.rb +39 -0
  109. data/spec/helpers/test_parser.rb +13 -0
  110. data/spec/norma_cql_spec.rb +13 -5
  111. data/spec/norma_ruby_spec.rb +11 -3
  112. data/spec/{absorption_ruby_spec.rb → norma_ruby_sql_spec.rb} +37 -32
  113. data/spec/norma_sql_spec.rb +11 -5
  114. data/spec/norma_tables_spec.rb +33 -29
  115. data/spec/spec_helper.rb +4 -1
  116. data/status.html +92 -23
  117. metadata +102 -36
  118. data/lib/activefacts/generate/cql/html.rb +0 -403
@@ -0,0 +1,425 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler < ActiveFacts::CQL::Parser
4
+
5
+ class ReferenceMode
6
+ attr_reader :name, :value_constraint, :parameters
7
+
8
+ def initialize name, value_constraint, parameters
9
+ @name = name
10
+ @value_constraint = value_constraint
11
+ @parameters = parameters
12
+ end
13
+ end
14
+
15
+ class EntityType < Concept
16
+ def initialize name, supertypes, identification, pragmas, readings
17
+ super name
18
+ @supertypes = supertypes
19
+ @identification = identification
20
+ @pragmas = pragmas
21
+ @readings = readings || []
22
+ end
23
+
24
+ def compile
25
+ @entity_type = @constellation.EntityType(@vocabulary, @name)
26
+ @entity_type.is_independent = true if (@pragmas.include? 'independent')
27
+
28
+ # REVISIT: CQL needs a way to indicate whether subtype migration can occur.
29
+ # For example by saying "Xyz is a role of Abc".
30
+ @supertypes.each_with_index do |supertype_name, i|
31
+ add_supertype(supertype_name, @identification || i > 0)
32
+ end
33
+
34
+ context = CompilationContext.new(@vocabulary)
35
+
36
+ # Identification may be via a mode (create it) or by forward-referenced entity types (allow those):
37
+ prepare_identifier context
38
+
39
+ # Create the fact types that define the identifying roles:
40
+ fact_types = create_identifying_fact_types context
41
+
42
+ # At this point, @identification is an array of RoleRefs and/or Readings (for unary fact types)
43
+ # Have to do this after creating the necessary fact types
44
+ complete_reference_mode_fact_type fact_types
45
+
46
+ # Find the roles to use if we have to create an identifying uniqueness constraint:
47
+ identifying_roles = bind_identifying_roles context
48
+
49
+ make_preferred_identifier_over_roles identifying_roles
50
+
51
+ @readings.each do |reading|
52
+ next unless reading.context_note
53
+ reading.context_note.compile(@constellation, @entity_type)
54
+ end
55
+
56
+ @entity_type
57
+ end
58
+
59
+ def prepare_identifier context
60
+ # Figure out the identification mode or roles, if any:
61
+ if @identification
62
+ if @identification.is_a? ReferenceMode
63
+ make_entity_type_refmode_valuetypes(name, @identification.name, @identification.parameters)
64
+ vt_name = @reference_mode_value_type.name
65
+ @identification = [Compiler::RoleRef.new(vt_name, nil, nil, nil, nil, nil, @identification.value_constraint, nil)]
66
+ else
67
+ context.allowed_forward_terms = legal_forward_references(@identification)
68
+ end
69
+ end
70
+ end
71
+
72
+ # Names used in the identifying roles list may be forward referenced:
73
+ def legal_forward_references(identification_roles)
74
+ identification_roles.map do |phrase|
75
+ phrase.is_a?(RoleRef) ? phrase.term : nil
76
+ end.compact.uniq
77
+ end
78
+
79
+ def bind_identifying_roles context
80
+ return unless @identification
81
+ @identification.map do |id|
82
+ if id.is_a?(RoleRef)
83
+ id.identify_player(context)
84
+ binding = id.bind(context)
85
+ roles = binding.refs.map{|r| r.role}.compact.uniq
86
+ raise "Looking for an occurrence of identifying role #{id.inspect}, but found #{roles.size == 0 ? "none" : roles.size}" if roles.size != 1
87
+ roles[0]
88
+ else
89
+ # id is a reading of a unary fact type.
90
+ id.identify_other_players context
91
+ id.bind_roles context
92
+ matching_reading =
93
+ @readings.detect { |reading| reading.phrases_match id.phrases }
94
+ raise "Unary identifying role 'id.inspect' is not found in the defined fact types" unless matching_reading
95
+ matching_reading.fact_type.all_role.single
96
+ end
97
+ end
98
+ end
99
+
100
+ def make_preferred_identifier_over_roles identifying_roles
101
+ return unless identifying_roles && identifying_roles.size > 0
102
+ role_sequence = @constellation.RoleSequence(:new)
103
+ identifying_roles.each_with_index do |identifying_role, index|
104
+ @constellation.RoleRef(role_sequence, index, :role => identifying_role)
105
+ end
106
+
107
+ # Find a uniqueness constraint as PI, or make one
108
+ pc = find_pc_over_roles(identifying_roles)
109
+ if (pc)
110
+ pc.is_preferred_identifier = true
111
+ pc.name = "#{@entity_type.name}PK" unless pc.name
112
+ debug "Existing PC #{pc.verbalise} is now PK for #{@entity_type.name} #{pc.class.roles.keys.map{|k|"#{k} => "+pc.send(k).verbalise}*", "}"
113
+ else
114
+ # Add a unique constraint over all identifying roles
115
+ pc = @constellation.PresenceConstraint(
116
+ :new,
117
+ :vocabulary => @vocabulary,
118
+ :name => "#{@entity_type.name}PK", # Is this a useful name?
119
+ :role_sequence => role_sequence,
120
+ :is_preferred_identifier => true,
121
+ :max_frequency => 1 # Unique
122
+ #:is_mandatory => true,
123
+ #:min_frequency => 1,
124
+ )
125
+ end
126
+ end
127
+
128
+ def find_pc_over_roles(roles)
129
+ return nil if roles.size == 0 # Safeguard; this would chuck an exception otherwise
130
+ roles[0].all_role_ref.each do |role_ref|
131
+ next if role_ref.role_sequence.all_role_ref.map(&:role) != roles
132
+ pc = role_ref.role_sequence.all_presence_constraint.single # Will return nil if there's more than one.
133
+ #puts "Existing PresenceConstraint matches those roles!" if pc
134
+ return pc if pc
135
+ end
136
+ nil
137
+ end
138
+
139
+ def create_identifying_fact_types context
140
+ fact_types = []
141
+ # Categorise the readings into fact types according to the roles they play.
142
+ @readings.each{ |reading| reading.identify_players_with_role_name(context) }
143
+ @readings.each{ |reading| reading.identify_other_players(context) }
144
+ @readings.inject({}) do |hash, reading|
145
+ players_key = reading.role_refs.map{|rr| rr.key.compact}.sort
146
+ (hash[players_key] ||= []) << reading
147
+ hash
148
+ end.each do |players_key, readings|
149
+ readings.each{ |reading| reading.bind_roles context } # Create the Compiler::Bindings
150
+
151
+ # REVISIT: Loose binding goes here; it might merge some Compiler#Roles
152
+
153
+ fact_type = create_identifying_fact_type(context, readings)
154
+ fact_types << fact_type if fact_type
155
+ objectify_existing_fact_type(fact_type) unless fact_type.all_role.detect{|r| r.concept == @entity_type}
156
+ end
157
+ fact_types
158
+ end
159
+
160
+ def create_identifying_fact_type context, readings
161
+ # Remove uninteresting assertions:
162
+ readings.reject!{|reading| reading.is_existential_type }
163
+ return nil unless readings.size > 0 # Nothing interesting was said.
164
+
165
+ # See if any fact type already exists (this ET cannot be a player, but might objectify it)
166
+ existing_readings = readings.select{ |reading| reading.match_existing_fact_type context }
167
+ any_matched = existing_readings.size > 0
168
+
169
+ operation = any_matched ? 'Objectifying' : 'Creating'
170
+ player_names = readings[0].role_refs.map{|rr| rr.key.compact*'-'}
171
+ debug :matching, "#{operation} fact type for #{readings.size} readings over (#{player_names*', '})" do
172
+ if any_matched # There's an existing fact type we must be objectifying
173
+ fact_type = objectify_existing_fact_type(existing_readings[0].fact_type)
174
+ end
175
+
176
+ unless fact_type
177
+ fact_type = readings[0].make_fact_type(@vocabulary)
178
+ readings[0].make_reading(@vocabulary, fact_type)
179
+ readings[0].make_embedded_constraints vocabulary
180
+ existing_readings = [readings[0]]
181
+ end
182
+
183
+ (readings - existing_readings).each do |reading|
184
+ reading.make_reading(@vocabulary, fact_type)
185
+ reading.make_embedded_constraints vocabulary
186
+ end
187
+
188
+ fact_type
189
+ end
190
+ end
191
+
192
+ def objectify_existing_fact_type fact_type
193
+ raise "#{@name} cannot objectify fact type '#{fact_type.entity_type.name}' that's already objectified" if fact_type.entity_type
194
+ raise "#{@name} must only objectify one fact type" if @fact_type
195
+ if fact_type.internal_presence_constraints.select{|pc| pc.max_frequency == 1}.size == 0
196
+ # If there's no existing uniqueness constraint over this fact type, make a spanning one.
197
+ @constellation.PresenceConstraint(
198
+ :new,
199
+ :vocabulary => @vocabulary,
200
+ :name => @entity_type.name+"UQ",
201
+ :role_sequence => fact_type.preferred_reading.role_sequence,
202
+ :is_preferred_identifier => false, # We only get here when there is a reference mode on the entity type
203
+ :max_frequency => 1
204
+ )
205
+ end
206
+
207
+ @fact_type = @entity_type.fact_type = fact_type
208
+ @entity_type.create_implicit_fact_types
209
+ @fact_type
210
+ end
211
+
212
+ def add_supertype(supertype_name, not_identifying)
213
+ debug :supertype, "Adding supertype #{supertype_name}" do
214
+ supertype = @constellation.EntityType(@vocabulary, supertype_name)
215
+
216
+ # Did we already know about this supertype?
217
+ return if @entity_type.all_type_inheritance_as_subtype.detect{|ti| ti.supertype == supertype}
218
+
219
+ # By default, the first supertype identifies this entity type
220
+ is_identifying_supertype = !not_identifying && @entity_type.all_type_inheritance_as_subtype.size == 0
221
+
222
+ inheritance_fact = @constellation.TypeInheritance(@entity_type, supertype, :fact_type_id => :new)
223
+
224
+ assimilations = @pragmas.select { |p| ['absorbed', 'separate', 'partitioned'].include? p}
225
+ raise "Conflicting assimilation pragmas #{assimilations*', '}" if assimilations.size > 1
226
+ inheritance_fact.assimilation = assimilations[0]
227
+
228
+ # Create a reading:
229
+ sub_role = @constellation.Role(inheritance_fact, 0, :concept => @entity_type)
230
+ super_role = @constellation.Role(inheritance_fact, 1, :concept => supertype)
231
+
232
+ rs = @constellation.RoleSequence(:new)
233
+ @constellation.RoleRef(rs, 0, :role => sub_role)
234
+ @constellation.RoleRef(rs, 1, :role => super_role)
235
+ @constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :text => "{0} is a kind of {1}")
236
+ # REVISIT: Can we get away with adding "{0} is a/n {1}" here?
237
+ # REVISIT: Can we deprecate the "is a subtype of" reading?
238
+ @constellation.Reading(inheritance_fact, 1, :role_sequence => rs, :text => "{0} is a subtype of {1}")
239
+
240
+ rs2 = @constellation.RoleSequence(:new)
241
+ @constellation.RoleRef(rs2, 0, :role => super_role)
242
+ @constellation.RoleRef(rs2, 1, :role => sub_role)
243
+ # Decide in which order to include is a/is an. Provide both, but in order.
244
+ n = 'aeiouh'.include?(sub_role.concept.name.downcase[0]) ? 1 : 0
245
+ @constellation.Reading(inheritance_fact, 2+n, :role_sequence => rs2, :text => "{0} is a {1}")
246
+ @constellation.Reading(inheritance_fact, 3-n, :role_sequence => rs2, :text => "{0} is an {1}")
247
+
248
+ if is_identifying_supertype
249
+ inheritance_fact.provides_identification = true
250
+ end
251
+
252
+ # Create uniqueness constraints over the subtyping fact type.
253
+ p1rs = @constellation.RoleSequence(:new)
254
+ @constellation.RoleRef(p1rs, 0).role = sub_role
255
+ pc1 = @constellation.PresenceConstraint(:new, :vocabulary => @vocabulary)
256
+ pc1.name = "#{@entity_type.name}MustHaveSupertype#{supertype.name}"
257
+ pc1.role_sequence = p1rs
258
+ pc1.is_mandatory = true # A subtype instance must have a supertype instance
259
+ pc1.min_frequency = 1
260
+ pc1.max_frequency = 1
261
+ pc1.is_preferred_identifier = false
262
+
263
+ p2rs = @constellation.RoleSequence(:new)
264
+ constellation.RoleRef(p2rs, 0).role = super_role
265
+ pc2 = constellation.PresenceConstraint(:new, :vocabulary => @vocabulary)
266
+ pc2.name = "#{supertype.name}MayBeA#{@entity_type.name}"
267
+ pc2.role_sequence = p2rs
268
+ pc2.is_mandatory = false
269
+ pc2.min_frequency = 0
270
+ pc2.max_frequency = 1
271
+ # The supertype role often identifies the subtype:
272
+ pc2.is_preferred_identifier = inheritance_fact.provides_identification
273
+ end
274
+ end
275
+
276
+ def make_entity_type_refmode_valuetypes(name, mode, parameters)
277
+ vt_name = "#{name}#{mode}"
278
+ vt = nil
279
+ debug :entity, "Preparing value type #{vt_name} for reference mode" do
280
+ # Find or Create an appropriate ValueType called '#{vt_name}', of the supertype '#{mode}'
281
+ unless vt = @constellation.Concept[[@vocabulary.identifying_role_values, vt_name]] or
282
+ vt = @constellation.Concept[[@vocabulary.identifying_role_values, vt_name = "#{name} #{mode}"]]
283
+ base_vt = @constellation.ValueType(@vocabulary, mode)
284
+ vt = @constellation.ValueType(@vocabulary, vt_name, :supertype => base_vt)
285
+ if parameters
286
+ length, scale = *parameters
287
+ vt.length = length if length
288
+ vt.scale = scale if scale
289
+ end
290
+ else
291
+ debug :entity, "Value type #{vt_name} already exists"
292
+ end
293
+ end
294
+
295
+ # REVISIT: If we do this, it gets emitted twice when we generate CQL.
296
+ # The generator should detect that the value_constraint is the same and not emit it.
297
+ #if (ranges = identification[:value_constraint])
298
+ # vt.value_constraint = value_constraint(ranges, identification[:enforcement])
299
+ #end
300
+ @reference_mode_value_type = vt
301
+ end
302
+
303
+ def complete_reference_mode_fact_type(fact_types)
304
+ return unless identifying_type = @reference_mode_value_type
305
+
306
+ # Find an existing fact type, if any:
307
+ entity_role = identifying_role = nil
308
+ fact_type = fact_types.detect do |ft|
309
+ identifying_role = ft.all_role.detect{|r| r.concept == identifying_type } and
310
+ entity_role = ft.all_role.detect{|r| r.concept == @entity_type }
311
+ end
312
+
313
+ # Create an identifying fact type if needed:
314
+ unless fact_type
315
+ fact_type = @constellation.FactType(:new)
316
+ fact_types << fact_type
317
+ entity_role = @constellation.Role(fact_type, 0, :concept => @entity_type)
318
+ identifying_role = @constellation.Role(fact_type, 1, :concept => identifying_type)
319
+ end
320
+ @identification[0].role = identifying_role
321
+
322
+ if (value_constraint = @identification[0].value_constraint)
323
+ # The value_constraint applies only to the value role, not to the underlying value type
324
+ # Decide whether this puts the value_constraint in the right place:
325
+ value_constraint.constellation = fact_type.constellation
326
+ identifying_role.role_value_constraint = value_constraint.compile
327
+ end
328
+
329
+ # Find all role sequences over the fact type's two roles
330
+ rss = entity_role.all_role_ref.select do |rr|
331
+ rr.role_sequence.all_role_ref.size == 2 &&
332
+ (rr.role_sequence.all_role_ref.to_a-[rr])[0].role == identifying_role
333
+ end.map{|rr| rr.role_sequence}
334
+
335
+ # Make a forward reading, if there is none already:
336
+ # Find or create RoleSequences for the forward and reverse readings:
337
+ rs01 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [entity_role, identifying_role] }[0]
338
+ if !rs01
339
+ rs01 = @constellation.RoleSequence(:new)
340
+ @constellation.RoleRef(rs01, 0, :role => entity_role)
341
+ @constellation.RoleRef(rs01, 1, :role => identifying_role)
342
+ end
343
+ if rs01.all_reading.empty?
344
+ @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence => rs01, :text => "{0} has {1}")
345
+ debug :mode, "Creating new forward reading '#{entity_role.concept.name} has #{identifying_type.name}'"
346
+ else
347
+ debug :mode, "Using existing forward reading"
348
+ end
349
+
350
+ # Make a reverse reading if none exists
351
+ rs10 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [identifying_role, entity_role] }[0]
352
+ if !rs10
353
+ rs10 = @constellation.RoleSequence(:new)
354
+ @constellation.RoleRef(rs10, 0, :role => identifying_role)
355
+ @constellation.RoleRef(rs10, 1, :role => entity_role)
356
+ end
357
+ if rs10.all_reading.empty?
358
+ @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence => rs10, :text => "{0} is of {1}")
359
+ debug :mode, "Creating new reverse reading '#{identifying_type.name} is of #{entity_role.concept.name}'"
360
+ else
361
+ debug :mode, "Using existing reverse reading"
362
+ end
363
+
364
+ # Entity must have one identifying instance. Find or create the role sequence, then create a PC if necessary
365
+ rs0 = entity_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}[0]
366
+ if rs0
367
+ rs0 = rs0.role_sequence
368
+ debug :mode, "Using existing EntityType role sequence"
369
+ else
370
+ rs0 = @constellation.RoleSequence(:new)
371
+ @constellation.RoleRef(rs0, 0, :role => entity_role)
372
+ debug :mode, "Creating new EntityType role sequence"
373
+ end
374
+ if (rs0.all_presence_constraint.size == 0)
375
+ constraint = @constellation.PresenceConstraint(
376
+ :new,
377
+ :name => '',
378
+ :vocabulary => @vocabulary,
379
+ :role_sequence => rs0,
380
+ :min_frequency => 1,
381
+ :max_frequency => 1,
382
+ :is_preferred_identifier => false,
383
+ :is_mandatory => true
384
+ )
385
+ debug :mode, "Creating new EntityType PresenceConstraint"
386
+ else
387
+ debug :mode, "Using existing EntityType PresenceConstraint"
388
+ end
389
+
390
+ # Value Type must have a value type. Find or create the role sequence, then create a PC if necessary
391
+ debug :mode, "identifying_role has #{identifying_role.all_role_ref.size} attached sequences"
392
+ debug :mode, "identifying_role has #{identifying_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}.size} unary sequences"
393
+ rs1 = identifying_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1 ? rr.role_sequence : nil }.compact[0]
394
+ if (!rs1)
395
+ rs1 = @constellation.RoleSequence(:new)
396
+ @constellation.RoleRef(rs1, 0, :role => identifying_role)
397
+ debug :mode, "Creating new ValueType role sequence"
398
+ else
399
+ rs1 = rs1.role_sequence
400
+ debug :mode, "Using existing ValueType role sequence"
401
+ end
402
+ if (rs1.all_presence_constraint.size == 0)
403
+ constraint = @constellation.PresenceConstraint(
404
+ :new,
405
+ :name => '',
406
+ :vocabulary => @vocabulary,
407
+ :role_sequence => rs1,
408
+ :min_frequency => 0,
409
+ :max_frequency => 1,
410
+ :is_preferred_identifier => true,
411
+ :is_mandatory => false
412
+ )
413
+ debug :mode, "Creating new ValueType PresenceConstraint"
414
+ else
415
+ debug :mode, "Marking existing ValueType PresenceConstraint as preferred"
416
+ rs1.all_presence_constraint[0].is_preferred_identifier = true
417
+ end
418
+ end
419
+
420
+ end
421
+
422
+ end
423
+ end
424
+ end
425
+
@@ -0,0 +1,300 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler < ActiveFacts::CQL::Parser
4
+
5
+ class Fact < Definition
6
+ def initialize readings, population_name = ''
7
+ @readings = readings
8
+ @population_name = population_name
9
+ end
10
+
11
+ def compile
12
+ @population = @constellation.Population(@vocabulary, @population_name)
13
+
14
+ @context = CompilationContext.new(@vocabulary)
15
+ @readings.each{ |reading| reading.identify_players_with_role_name(@context) }
16
+ @readings.each{ |reading| reading.identify_other_players(@context) }
17
+ @readings.each{ |reading| reading.bind_roles @context }
18
+
19
+ # Figure out the simple existential facts and find fact types:
20
+ @bound_instances = {} # Instances indexed by binding
21
+ @bound_fact_types = []
22
+ @bound_facts = []
23
+ @unbound_readings = @readings.
24
+ map do |reading|
25
+ bind_literal_or_fact_type reading
26
+ end.
27
+ compact
28
+
29
+ # Because the fact types may include forward references, we must
30
+ # process the list repeatedly until we make no further progress.
31
+ @pass = 0 # Repeat until we make no more progress:
32
+ true while bind_more_facts
33
+
34
+ # Any remaining unbound facts are a problem we can bitch about:
35
+ complain_incomplete unless @unbound_readings.empty?
36
+
37
+ @bound_facts.uniq # N.B. this includes Instance objects (existential facts)
38
+ end
39
+
40
+ def bind_literal_or_fact_type reading
41
+ # Every bound word (term) in the phrases must have a literal
42
+ # OR be bound to an entity type identified by the phrases
43
+
44
+ # Any clause that has one binding and no other word is
45
+ # either a value instance or a simply-identified entity.
46
+ reading.role_refs.map do |role_ref|
47
+ next role_ref unless l = role_ref.literal
48
+ player = role_ref.binding.player
49
+ # raise "A literal may not be an objectification" if role_ref.role_ref.objectification_join
50
+ # raise "Not processing facts involving objectification joins yet" if role_ref.role_ref
51
+ debug :instance, "Making #{player.class.basename} #{player.name} using #{l.inspect}" do
52
+ @bound_instances[role_ref.binding] =
53
+ instance_identified_by_literal player, l
54
+ end
55
+ role_ref
56
+ end
57
+
58
+ if reading.phrases.size == 1 && (role_ref = reading.phrases[0]).is_a?(Compiler::RoleRef)
59
+ # This is an existential fact (like "Name 'foo'", or "Company 'Microsoft'")
60
+ # @bound_instances[role_ref.binding]
61
+ nil # Nothing to see here, move along
62
+ else
63
+ @bound_fact_types << reading.match_existing_fact_type(@context)
64
+ reading
65
+ end
66
+ end
67
+
68
+ # Take one pass through the @unbound_readings, processing (and removing) any that have all pre-requisites
69
+ def bind_more_facts
70
+ @pass += 1
71
+
72
+ progress = false
73
+ debug :instance, "Pass #{@pass} with #{@unbound_readings.size} readings to consider" do
74
+ @unbound_readings.map! do |reading|
75
+ # See if we can create the fact instance for this reading yet
76
+
77
+ # Find the roles of this reading that do not yet have an entry in @bound_instances:
78
+ bare_roles = reading.role_refs.
79
+ select do |role_ref|
80
+ !role_ref.literal && !@bound_instances[role_ref.binding]
81
+ end
82
+
83
+ debug :instance, "Considering '#{reading.fact_type.preferred_reading.expand}' with bare roles: #{bare_roles.map{|role_ref| role_ref.player.name}*", "} "
84
+
85
+ if bare_roles.size == 0
86
+ reading = nil if bind_complete_fact reading
87
+ elsif bare_roles.size == 1 &&
88
+ (binding = bare_roles[0].binding) &&
89
+ (et = binding.player).is_a?(ActiveFacts::Metamodel::EntityType) &&
90
+ et.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type == reading.fact_type}
91
+
92
+ reading = nil if bind_entity_if_identifier_ready reading, et, binding
93
+ end
94
+ progress = true unless reading
95
+ reading
96
+ end
97
+ @unbound_readings.compact!
98
+ end # debug
99
+ progress
100
+ end
101
+
102
+ def bind_complete_fact reading
103
+ debug :instance, "All bindings in '#{reading.fact_type.preferred_reading.expand}' contain instances; create the fact type"
104
+ instances = reading.role_refs.map{|rr| @bound_instances[rr.binding]}
105
+ debug :instance, "Instances are #{instances.map{|i| "#{i.concept.name} #{i.value.inspect}"}*", "}"
106
+
107
+ # Check that this fact doesn't already exist
108
+ fact = reading.fact_type.all_fact.detect{|f|
109
+ # Get the role values of this fact in the order of the reading we just bound
110
+ role_values_in_reading_order = f.all_role_value.sort_by do |rv|
111
+ reading.reading.role_sequence.all_role_ref.detect{|rr| rr.role == rv.role}.ordinal
112
+ end
113
+ # If all this fact's role values are played by the bound instances, it's the same fact
114
+ !role_values_in_reading_order.zip(instances).detect{|rv, i| rv.instance != i }
115
+ }
116
+ unless fact
117
+ fact = @constellation.Fact(:new, :fact_type => reading.fact_type, :population => @population)
118
+ @bound_facts << fact
119
+ instance =
120
+ @constellation.Instance(:new, :concept => reading.fact_type.entity_type, :fact => fact, :population => @population)
121
+ @bound_facts << instance
122
+ reading.reading.role_sequence.all_role_ref.zip(instances).each do |rr, instance|
123
+ debug :instance, "New fact has #{instance.concept.name} role #{instance.value.inspect}"
124
+ # REVISIT: Any residual adjectives after the fact type matching are lost here.
125
+ @constellation.RoleValue(:fact => fact, :instance => instance, :role => rr.role, :population => @population)
126
+ end
127
+ else
128
+ debug :instance, "Found existing fact type instance"
129
+ end
130
+ true
131
+ end
132
+
133
+ # If we have one bare role (no literal or instance) played by an entity type,
134
+ # and the bound fact type participates in the identifier, we might now be able
135
+ # to create the entity instance.
136
+ def bind_entity_if_identifier_ready reading, entity_type, binding
137
+ # Check this instance doesn't already exist already:
138
+ identifying_binding = (reading.role_refs.map{|rr| rr.binding}-[binding])[0]
139
+ identifying_instance = @bound_instances[identifying_binding]
140
+ preferred_identifier = entity_type.preferred_identifier
141
+
142
+ debug :instance, "This clause associates a new #{binding.player.name} with a #{identifying_binding.player.name}#{identifying_instance ? " which exists" : ""}"
143
+
144
+ identifying_role_ref = preferred_identifier.role_sequence.all_role_ref.detect { |rr|
145
+ rr.role.fact_type == reading.fact_type && rr.role.concept == identifying_binding.player
146
+ }
147
+ unless identifying_role_ref
148
+ # This shold never happen; we already bound all role_refs
149
+ debug :instance, "Failed to find a #{identifying_instance.concept.name}"
150
+ return false # We can't do this yet
151
+ end
152
+ role_value = identifying_instance.all_role_value.detect do |rv|
153
+ rv.fact.fact_type == identifying_role_ref.role.fact_type
154
+ end
155
+ if role_value
156
+ instance = (role_value.fact.all_role_value.to_a-[role_value])[0].instance
157
+ debug :instance, "Found an existing instance (of #{instance.concept.name}) from a previous definition"
158
+ @bound_instances[binding] = instance
159
+ return true # Done with this reading
160
+ end
161
+
162
+ pi_role_refs = preferred_identifier.role_sequence.all_role_ref
163
+ # For each pi role, we have to find the fact clause, which contains the binding we need.
164
+ # Then we have to create an instance of each fact
165
+ identifiers =
166
+ pi_role_refs.map do |rr|
167
+ fact_a = @readings.detect{|reading| rr.role.fact_type == reading.fact_type}
168
+ identifying_role_ref = fact_a.role_refs.select{|role_ref| role_ref.binding != binding}[0]
169
+ identifying_binding = identifying_role_ref ? identifying_role_ref.binding : nil
170
+ identifying_instance = @bound_instances[identifying_binding]
171
+
172
+ [rr, fact_a, identifying_binding, identifying_instance]
173
+ end
174
+ if identifiers.detect{ |i| !i[3] } # Not all required facts are bound yet
175
+ debug :instance, "Can't go through with creating #{binding.player.name}; not all the identifying facts are in"
176
+ return false
177
+ end
178
+
179
+ debug :instance, "Going ahead with creating #{binding.player.name} using #{identifiers.size} roles"
180
+ instance = @constellation.Instance(:new, :concept => entity_type, :population => @population)
181
+ @bound_instances[binding] = instance
182
+ @bound_facts << instance
183
+ identifiers.each do |rr, fact_a, identifying_binding, identifying_instance|
184
+ # This reading provides the identifying literal for the entity_type
185
+ id_fact = @constellation.Fact(:new, :fact_type => rr.role.fact_type, :population => @population)
186
+ @bound_facts << id_fact
187
+ role = (rr.role.fact_type.all_role.to_a-[rr.role])[0]
188
+ @constellation.RoleValue(:instance => instance, :fact => id_fact, :population => @population, :role => role)
189
+ @constellation.RoleValue(:instance => identifying_instance, :fact => id_fact, :role => rr.role, :population => @population)
190
+ end
191
+
192
+ true # Done with this reading
193
+ end
194
+
195
+ def instance_identified_by_literal concept, literal
196
+ if concept.is_a?(ActiveFacts::Metamodel::EntityType)
197
+ entity_identified_by_literal concept, literal
198
+ else
199
+ debug :instance, "Making ValueType #{concept.name} #{literal.inspect} #{@population.name.size>0 ? " in "+@population.name.inspect : ''}" do
200
+
201
+ is_a_string = String === literal
202
+ instance = @constellation.Instance.detect do |key, i|
203
+ # REVISIT: And same unit
204
+ i.population == @population &&
205
+ i.value &&
206
+ i.value.literal == literal &&
207
+ i.value.is_a_string == is_a_string
208
+ end
209
+ #instance = concept.all_instance.detect { |instance|
210
+ # instance.population == @population && instance.value == literal
211
+ #}
212
+ debug :instance, "This #{concept.name} value already exists" if instance
213
+ unless instance
214
+ instance = @constellation.Instance(
215
+ :new,
216
+ :concept => concept,
217
+ :population => @population,
218
+ :value => [literal.to_s, is_a_string, nil]
219
+ )
220
+ @bound_facts << instance
221
+ end
222
+ instance
223
+ end
224
+ end
225
+ end
226
+
227
+ def entity_identified_by_literal concept, literal
228
+ # A literal that identifies an entity type means the entity type has only one identifying role
229
+ # That role is played either by a value type, or by another similarly single-identified entity type
230
+ debug "Making EntityType #{concept.name} identified by '#{literal}' #{@population.name.size>0 ? " in "+@population.name.inspect : ''}" do
231
+ identifying_role_refs = concept.preferred_identifier.role_sequence.all_role_ref
232
+ raise "Single literal cannot satisfy multiple identifying roles for #{concept.name}" if identifying_role_refs.size > 1
233
+ role = identifying_role_refs.single.role
234
+ identifying_instance = instance_identified_by_literal role.concept, literal
235
+ existing_instance = nil
236
+ instance_rv = identifying_instance.all_role_value.detect { |rv|
237
+ next false unless rv.population == @population # Not this population
238
+ next false unless rv.fact.fact_type == role.fact_type # Not this fact type
239
+ other_role_value = (rv.fact.all_role_value-[rv])[0]
240
+ existing_instance = other_role_value.instance
241
+ other_role_value.instance.concept == concept # Is it this concept?
242
+ }
243
+ if instance_rv
244
+ instance = existing_instance
245
+ debug :instance, "This #{concept.name} entity already exists"
246
+ else
247
+ fact = @constellation.Fact(:new, :fact_type => role.fact_type, :population => @population)
248
+ @bound_facts << fact
249
+ instance = @constellation.Instance(:new, :concept => concept, :population => @population)
250
+ @bound_facts << instance
251
+ # The identifying fact type has two roles; create both role instances:
252
+ @constellation.RoleValue(:instance => identifying_instance, :fact => fact, :population => @population, :role => role)
253
+ @constellation.RoleValue(:instance => instance, :fact => fact, :population => @population, :role => (role.fact_type.all_role-[role])[0])
254
+ end
255
+ instance
256
+ end
257
+ end
258
+
259
+ def complain_incomplete
260
+ if @unbound_readings.size > 0
261
+ # Provide a readable description of the problem here, by showing each binding with no instance
262
+ missing_bindings = @unbound_readings.
263
+ map do |reading|
264
+ reading.role_refs.
265
+ select do |rr|
266
+ !@bound_instances[rr.binding]
267
+ end.
268
+ map do |role_ref|
269
+ role_ref.binding
270
+ end
271
+ end.
272
+ flatten.
273
+ uniq
274
+
275
+ raise "Not enough facts are given to identify #{
276
+ missing_bindings.
277
+ sort_by{|b| b.key}.
278
+ map do |b|
279
+ player_identifier =
280
+ if b.player.is_a?(ActiveFacts::Metamodel::EntityType)
281
+ "lacking " +
282
+ b.player.preferred_identifier.role_sequence.all_role_ref.map do |rr|
283
+ [ rr.leading_adjective, rr.role.role_name || rr.role.concept.name, rr.trailing_adjective ].compact*" "
284
+ end*", "
285
+ else
286
+ "needs a value"
287
+ end
288
+ [
289
+ b.refs[0].leading_adjective, b.player.name, b.refs[0].trailing_adjective
290
+ ].compact*" " +
291
+ " (#{player_identifier})"
292
+ end*" or "
293
+ }"
294
+ end
295
+ end
296
+
297
+ end
298
+ end
299
+ end
300
+ end