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
@@ -21,6 +21,42 @@ module ActiveFacts
21
21
  def vocabulary_end
22
22
  end
23
23
 
24
+ def units_banner
25
+ puts "/*\n * Units\n */"
26
+ end
27
+
28
+ def unit_dump unit
29
+ if !unit.ephemera_url
30
+ if unit.coefficient
31
+ # REVISIT: Use a smarter algorithm to switch to exponential form when there'd be lots of zeroes.
32
+ print unit.coefficient.numerator.to_s('F')
33
+ if d = unit.coefficient.denominator and d != 1
34
+ print "/#{d}"
35
+ end
36
+ print ' '
37
+ else
38
+ print '1 '
39
+ end
40
+ end
41
+
42
+ print(unit.
43
+ all_derivation_as_derived_unit.
44
+ # REVISIT: Sort base units
45
+ # REVISIT: convert negative powers to division?
46
+ map do |der|
47
+ base = der.base_unit
48
+ "#{base.name}#{der.exponent and der.exponent != 1 ? "^#{der.exponent}" : ''} "
49
+ end*''
50
+ )
51
+ if o = unit.offset and o != 0
52
+ print "+ #{o.to_s('F')} "
53
+ end
54
+ print "converts to #{unit.name}#{unit.plural_name ? '/'+unit.plural_name : ''}"
55
+ print " approximately" if unit.coefficient and !unit.coefficient.is_precise
56
+ print " ephemera #{unit.ephemera_url}" if unit.ephemera_url
57
+ puts ";"
58
+ end
59
+
24
60
  def value_type_banner
25
61
  puts "/*\n * Value Types\n */"
26
62
  end
@@ -30,23 +66,49 @@ module ActiveFacts
30
66
  end
31
67
 
32
68
  def value_type_dump(o)
33
- return unless o.supertype # An imported type
34
-
35
- # REVISIT: A ValueType that is only used as a reference mode need not be emitted here. We haven't detected this situation yet however...
36
-
37
- if o.name == o.supertype.name
38
- # In ActiveFacts, parameterising a ValueType will create a new ValueType
39
- # throw Can't handle parameterized value type of same name as its ValueType" if ...
69
+ # Ignore Value Types that don't do anything:
70
+ return if
71
+ !o.supertype &&
72
+ o.all_role.size == 0 &&
73
+ !o.is_independent &&
74
+ !o.value_constraint &&
75
+ o.all_context_note.size == 0 &&
76
+ o.all_instance.size == 0
77
+ # No need to dump it if the only thing it does is be a supertype; it'll be created automatically
78
+ # return if o.all_value_type_as_supertype.size == 0
79
+
80
+ =begin
81
+ # Leave this out, pending a proper on-demand system for dumping VT's
82
+ # A ValueType that is only used as a reference mode need not be emitted here.
83
+ if o.all_value_type_as_supertype.size == 0 &&
84
+ !o.all_role.
85
+ detect do |role|
86
+ (other_roles = role.fact_type.all_role.to_a-[role]).size != 1 || # Not a role in a binary FT
87
+ !(concept = other_roles[0].concept).is_a?(ActiveFacts::Metamodel::EntityType) || # Counterpart is not an ET
88
+ (pi = concept.preferred_identifier).role_sequence.all_role_ref.size != 1 || # Entity PI has > 1 roles
89
+ pi.role_sequence.all_role_ref.single.role != role # This isn't the identifying role
90
+ end
91
+ puts "About to skip #{o.name}"
92
+ debugger
93
+ return
40
94
  end
41
95
 
96
+ # We'll dump the subtypes before any roles, so we don't need to dump this here.
97
+ # ... except that isn't true, we won't do that so we can't skip it now
98
+ #return if
99
+ # o.all_value_type_as_supertype.size != 0 && # We have subtypes
100
+ # o.all_role.size != 0
101
+ =end
102
+
42
103
  parameters =
43
104
  [ o.length != 0 || o.scale != 0 ? o.length : nil,
44
105
  o.scale != 0 ? o.scale : nil
45
106
  ].compact
46
107
  parameters = parameters.length > 0 ? "("+parameters.join(",")+")" : ""
47
108
 
48
- puts "#{o.name} is written as #{o.supertype.name}#{ parameters }#{
49
- o.value_restriction && " "+o.value_restriction.describe
109
+ puts "#{o.name} is written as #{(o.supertype || o).name}#{ parameters }#{
110
+ o.value_constraint && " "+o.value_constraint.describe
111
+ }#{o.is_independent ? ' [independent]' : ''
50
112
  };"
51
113
  end
52
114
 
@@ -66,119 +128,144 @@ module ActiveFacts
66
128
  "]"
67
129
  end
68
130
 
69
- def identified_by_roles_and_facts(entity_type, identifying_roles, identifying_facts, preferred_readings)
70
- identifying_role_names = identifying_roles.map{|role|
71
- preferred_role_ref = preferred_readings[role.fact_type].role_sequence.all_role_ref.detect{|reading_rr|
72
- reading_rr.role == role
73
- }
74
- role_words = []
75
- # REVISIT: Consider whether NOT to use the adjective if it's a prefix of the role_name
76
-
77
- role_name = role.role_name
78
- role_name = nil if role_name == ""
79
- # debug "concept.name=#{preferred_role_ref.role.concept.name}, role_name=#{role_name.inspect}, preferred_role_name=#{preferred_role_ref.role.role_name.inspect}"
131
+ # If this entity_type is identified by a single value, return four relevant objects:
132
+ def value_role_identification(entity_type, identifying_facts)
133
+ external_identifying_facts = identifying_facts - [entity_type.fact_type]
134
+ fact_type = external_identifying_facts[0]
135
+ ftr = fact_type && fact_type.all_role.sort_by{|role| role.ordinal}
136
+ if external_identifying_facts.size == 1 and
137
+ entity_role = ftr[n = (ftr[0].concept == entity_type ? 0 : 1)] and
138
+ value_role = ftr[1-n] and
139
+ value_player = value_role.concept and
140
+ value_player.is_a?(ActiveFacts::Metamodel::ValueType) and
141
+ value_name = value_player.name and
142
+ value_residual = value_name.sub(%r{^#{entity_role.concept.name} ?},'') and
143
+ value_residual != '' and
144
+ value_residual != value_name
145
+ [fact_type, entity_role, value_role, value_residual]
146
+ else
147
+ []
148
+ end
149
+ end
80
150
 
81
- if (role.fact_type.all_role.size == 1)
82
- # REVISIT: Guard against unary reading containing the illegal words "and" and "where".
83
- role.fact_type.default_reading # Need whole reading for a unary.
84
- elsif (role_name)
85
- role_name
151
+ # This entity is identified by a single value, so find whether standard refmode readings were used
152
+ def detect_standard_refmode_readings fact_type, entity_role, value_role
153
+ forward_reading = reverse_reading = nil
154
+ fact_type.all_reading.each do |reading|
155
+ if reading.text =~ /^\{(\d)\} has \{\d\}$/
156
+ if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == entity_role
157
+ forward_reading = reading
86
158
  else
87
- role_words << preferred_role_ref.leading_adjective if preferred_role_ref.leading_adjective != ""
88
- role_words << preferred_role_ref.role.concept.name
89
- role_words << preferred_role_ref.trailing_adjective if preferred_role_ref.trailing_adjective != ""
90
- role_words.compact*"-"
159
+ reverse_reading = reading
91
160
  end
92
- }
93
-
94
- # REVISIT: Consider emitting extra fact types here, instead of in entity_type_dump?
95
- # Just beware that readings having the same players will be considered to be of the same fact type, even if they're not.
96
-
97
- # Detect standard reference-mode scenarios
98
- external_identifying_facts = identifying_facts - [entity_type.fact_type]
99
- ft = external_identifying_facts[0]
100
- fact_constraints = nil
101
- ftr = ft && ft.all_role.sort_by{|role| role.ordinal}
102
- if external_identifying_facts.size == 1 and
103
- entity_role = ftr[n = (ftr[0].concept == entity_type ? 0 : 1)] and
104
- value_role = ftr[1-n] and
105
- value_player = value_role.concept and
106
- value_player.is_a?(ActiveFacts::Metamodel::ValueType) and
107
- value_name = value_player.name and
108
- residual = value_name.gsub(%r{#{entity_role.concept.name}},'') and
109
- residual != '' and
110
- residual != value_name
111
-
112
- # The EntityType is identified by its association with a single ValueType
113
- # whose name is an extension (the residual) of the EntityType's name.
114
-
115
- # Detect standard reference-mode readings:
116
- forward_reading = reverse_reading = nil
117
- ft.all_reading.each do |reading|
118
- if reading.text =~ /^\{(\d)\} has \{\d\}$/
119
- if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == entity_role
120
- forward_reading = reading
121
- else
122
- reverse_reading = reading
123
- end
124
- elsif reading.text =~ /^\{(\d)\} is of \{\d\}$/
125
- if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == value_role
126
- reverse_reading = reading
127
- else
128
- forward_reading = reading
129
- end
161
+ elsif reading.text =~ /^\{(\d)\} is of \{\d\}$/
162
+ if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == value_role
163
+ reverse_reading = reading
164
+ else
165
+ forward_reading = reading
130
166
  end
131
167
  end
168
+ end
169
+ debug :mode, "Didn't find standard forward reading" unless forward_reading
170
+ debug :mode, "Didn't find standard reverse reading" unless reverse_reading
171
+ [forward_reading, reverse_reading]
172
+ end
132
173
 
133
- debug :mode, "------------------- Didn't find standard forward reading" unless forward_reading
134
- debug :mode, "------------------- Didn't find standard reverse reading" unless reverse_reading
135
-
136
- # If we didn't find at least one of the standard readings, don't use a refmode:
137
- if (forward_reading || reverse_reading)
138
- # Elide the constraints that would have been emitted on those readings.
139
- # If there is a UC that's not in the standard form for a reference mode,
140
- # we have to emit the standard reading anyhow.
141
- fact_constraints = @presence_constraints_by_fact[ft]
142
- fact_constraints.each do |pc|
143
- if (pc.role_sequence.all_role_ref.size == 1 and pc.max_frequency == 1)
144
- # It's a uniqueness constraint, and will be regenerated
145
- @constraints_used[pc] = true
146
- end
147
- end
148
- fact_constraints += Array(@presence_constraints_by_fact[entity_type.fact_type])
149
-
150
- @fact_types_dumped[ft] = true
151
-
152
- # Figure out whether any non-standard readings exist:
153
- other_readings = ft.all_reading - [forward_reading] - [reverse_reading]
154
- debug :mode, "--- other_readings.size now = #{other_readings.size}" if other_readings.size > 0
155
-
156
- fact_text = (
157
- other_readings.map do |reading|
158
- expanded_reading(reading, fact_constraints, true)
159
- end +
160
- (entity_type.fact_type ?
161
- fact_readings_with_constraints(entity_type.fact_type, fact_constraints) : []
162
- )
163
- )*",\n\t"
164
-
165
- restriction = value_role.role_value_restriction || value_player.value_restriction
166
- # REVISIT: If both restrictions apply and differ, we can't use a reference mode
167
- restriction_text = restriction ? " "+restriction.describe : ""
168
- return " identified by its #{residual}#{restriction_text}#{mapping_pragma(entity_type)}" +
169
- (fact_text != "" ? " where\n\t" + fact_text : "")
174
+ # If this entity_type is identified by a reference mode, return the verbalisation
175
+ def identified_by_ref_mode(entity_type, identifying_facts)
176
+ fact_type, entity_role, value_role, value_residual =
177
+ *value_role_identification(entity_type, identifying_facts)
178
+ return nil unless fact_type
179
+
180
+ # This EntityType is identified by its association with a single ValueType
181
+ # whose name is an extension (the value_residual) of the EntityType's name.
182
+ # If we have at least one of the standard refmode readings, dump it that way,
183
+ # else exit and use the long-hand verbalisation instead.
184
+
185
+ forward_reading, reverse_reading =
186
+ *detect_standard_refmode_readings(fact_type, entity_role, value_role)
187
+ return nil unless (forward_reading || reverse_reading)
188
+
189
+ # We can't subscript reference modes.
190
+ # If an objectified fact type has a role played by its identifying player, go long-hand.
191
+ return nil if entity_type.fact_type and
192
+ entity_type.fact_type.all_role.detect{|role| role.concept == value_role.concept }
193
+
194
+ @fact_types_dumped[fact_type] = true # We've covered this fact type
195
+
196
+ # Elide the constraints that would have been emitted on the standard readings.
197
+ # If there is a UC that's not in the standard form for a reference mode,
198
+ # we have to emit the standard reading anyhow.
199
+ fact_constraints = @presence_constraints_by_fact[fact_type]
200
+ fact_constraints.each do |pc|
201
+ if (pc.role_sequence.all_role_ref.size == 1 and pc.max_frequency == 1)
202
+ # It's a uniqueness constraint, and will be regenerated
203
+ @constraints_used[pc] = true
170
204
  end
171
205
  end
172
206
 
173
- identifying_facts.each{|f| @fact_types_dumped[f] = true }
174
- @identifying_fact_text =
207
+ # Figure out which non-standard readings exist, if any:
208
+ nonstandard_readings = fact_type.all_reading - [forward_reading, reverse_reading]
209
+ debug :mode, "--- nonstandard_readings.size now = #{nonstandard_readings.size}" if nonstandard_readings.size > 0
210
+
211
+ verbaliser = ActiveFacts::Metamodel::Verbaliser.new
212
+
213
+ # The verbaliser needs to have a Player for the roles of entity_type, so it doesn't get subscripted.
214
+ entity_roles =
215
+ nonstandard_readings.map{|r| r.role_sequence.all_role_ref.detect{|rr| rr.role.concept == entity_type}}.compact
216
+ verbaliser.role_refs_have_same_player entity_roles
217
+
218
+ verbaliser.alternate_readings nonstandard_readings
219
+ if entity_type.fact_type
220
+ verbaliser.alternate_readings entity_type.fact_type.all_reading
221
+ end
222
+
223
+ verbaliser.create_subscripts # Ok, the Verbaliser is ready to fly
224
+
225
+ fact_readings =
226
+ nonstandard_readings.map { |reading| expanded_reading(verbaliser, reading, fact_constraints, true) }
227
+ fact_readings +=
228
+ fact_readings_with_constraints(verbaliser, entity_type.fact_type) if entity_type.fact_type
229
+
230
+ # If we emitted a reading for the refmode, it'll include any role_value_constraint already
231
+ if nonstandard_readings.size == 0 and c = value_role.role_value_constraint
232
+ constraint_text = " "+c.describe
233
+ end
234
+ return " identified by its #{value_residual}#{constraint_text}#{mapping_pragma(entity_type)}" +
235
+ (fact_readings.size > 0 ? " where\n\t" : "") +
236
+ fact_readings*",\n\t"
237
+ end
238
+
239
+ def identified_by_roles_and_facts(entity_type, identifying_role_refs, identifying_facts)
240
+ # Detect standard reference-mode scenarios:
241
+ if srm = identified_by_ref_mode(entity_type, identifying_facts)
242
+ return srm
243
+ end
244
+
245
+ verbaliser = ActiveFacts::Metamodel::Verbaliser.new
246
+
247
+ # Announce all the identifying fact roles to the verbaliser so it can decide on any necessary subscripting.
248
+ # The verbaliser needs to have a Player for the roles of entity_type, so it doesn't get subscripted.
249
+ entity_roles =
250
+ identifying_facts.map{|ft| ft.preferred_reading.role_sequence.all_role_ref.detect{|rr| rr.role.concept == entity_type}}.compact
251
+ verbaliser.role_refs_have_same_player entity_roles
252
+ identifying_facts.each do |fact_type|
253
+ # The RoleRefs for corresponding roles across all readings are for the same player.
254
+ verbaliser.alternate_readings fact_type.all_reading
255
+ @fact_types_dumped[fact_type] = true
256
+ end
257
+ verbaliser.create_subscripts
258
+
259
+ irn = verbaliser.identifying_role_names identifying_role_refs
260
+
261
+ identifying_fact_text =
175
262
  identifying_facts.map{|f|
176
- fact_readings_with_constraints(f, fact_constraints)
263
+ fact_readings_with_constraints(verbaliser, f)
177
264
  }.flatten*",\n\t"
178
265
 
179
- " identified by #{ identifying_role_names*" and " }" +
266
+ " identified by #{ irn*" and " }" +
180
267
  mapping_pragma(entity_type) +
181
- " where\n\t"+@identifying_fact_text
268
+ " where\n\t"+identifying_fact_text
182
269
  end
183
270
 
184
271
  def entity_type_banner
@@ -189,53 +276,54 @@ module ActiveFacts
189
276
  puts "\n"
190
277
  end
191
278
 
192
- def fact_readings(fact_type)
193
- constrained_fact_readings = fact_readings_with_constraints(fact_type)
194
- constrained_fact_readings*",\n\t"
195
- end
196
-
197
279
  def subtype_dump(o, supertypes, pi)
198
280
  print "#{o.name} is a kind of #{ o.supertypes.map(&:name)*", " }"
199
281
  if pi
200
- print identified_by(o, pi)
201
- else
202
- print mapping_pragma(o)
282
+ puts identified_by(o, pi)+';'
283
+ return
203
284
  end
204
285
 
205
- # If there's a preferred_identifier for this subtype, identifying readings were emitted
206
- print((pi ? "," : " where") + "\n\t" + fact_readings(o.fact_type)) if o.fact_type
286
+ print mapping_pragma(o)
287
+
288
+ if o.fact_type
289
+ verbaliser = ActiveFacts::Metamodel::Verbaliser.new
290
+ # Announce all the objectified fact roles to the verbaliser so it can decide on any necessary subscripting.
291
+ # The RoleRefs for corresponding roles across all readings are for the same player.
292
+ verbaliser.alternate_readings o.fact_type.all_reading
293
+ verbaliser.create_subscripts
294
+
295
+ print " where\n\t" + fact_readings_with_constraints(verbaliser, o.fact_type)*",\n\t"
296
+ end
207
297
  puts ";\n"
208
298
  end
209
299
 
210
300
  def non_subtype_dump(o, pi)
211
- print "#{o.name} is" + identified_by(o, pi)
212
- # print(" where\n\t"+ fact_readings(o.fact_type)) if o.fact_type
213
- puts ";\n"
301
+ puts "#{o.name} is" + identified_by(o, pi) + ';'
214
302
  end
215
303
 
216
304
  def fact_type_dump(fact_type, name)
217
305
 
218
- @identifying_fact_text = nil
219
306
  if (o = fact_type.entity_type)
220
307
  print "#{o.name} is"
221
- if !o.all_type_inheritance_as_subtype.empty?
222
- print " a kind of #{ o.supertypes.map(&:name)*", " }"
223
- end
308
+ supertypes = o.supertypes
309
+ print " a kind of #{ supertypes.map(&:name)*", " }" unless supertypes.empty?
224
310
 
225
311
  # Alternate identification of objectified fact type?
226
- primary_supertype = o.supertypes[0]
312
+ primary_supertype = supertypes[0]
227
313
  pi = fact_type.entity_type.preferred_identifier
228
314
  if pi && primary_supertype && primary_supertype.preferred_identifier != pi
229
- print identified_by(o, pi)
230
- # REVISIT: This *has* to be wrong. When you fix it, remember mapping_pragmas!
231
- print ";\n"
315
+ puts identified_by(o, pi) + ';'
316
+ return
232
317
  end
318
+ print " where\n\t"
233
319
  end
234
320
 
235
- unless @identifying_fact_text
236
- print " where\n\t" if o
237
- puts(fact_readings(fact_type)+";")
238
- end
321
+ # There can be no roles of the objectified fact type in the readings, so no need to tell the Verbaliser anything special
322
+ verbaliser = ActiveFacts::Metamodel::Verbaliser.new
323
+ verbaliser.alternate_readings fact_type.all_reading
324
+ verbaliser.create_subscripts
325
+
326
+ puts(fact_readings_with_constraints(verbaliser, fact_type)*",\n\t"+";")
239
327
  end
240
328
 
241
329
  def fact_type_banner
@@ -263,184 +351,136 @@ module ActiveFacts
263
351
  end
264
352
 
265
353
  def dump_presence_constraint(c)
354
+ # Loose binding in PresenceConstraints is limited to explicit role players (in an occurs list)
355
+ # having no exact match, but having instead exactly one role of the same player in the readings.
356
+
357
+ verbaliser = ActiveFacts::Metamodel::Verbaliser.new
358
+ # For a mandatory constraint (min_frequency == 1, max == nil or 1) any subtyping join is over the proximate role player
359
+ # For all other presence constraints any subtyping join is over the counterpart player
360
+ role_proximity = c.min_frequency == 1 && [nil, 1].include?(c.max_frequency) ? :proximate : :counterpart
361
+ if role_proximity == :proximate
362
+ verbaliser.role_refs_are_subtype_joined(c.role_sequence)
363
+ else
364
+ join_over, joined_roles = ActiveFacts::Metamodel.join_roles_over(c.role_sequence.all_role_ref.map{|rr|rr.role}, role_proximity)
365
+ verbaliser.roles_have_same_player(joined_roles) if join_over
366
+ end
367
+
368
+ verbaliser.prepare_role_sequence(c.role_sequence)
369
+
370
+ expanded_readings = verbaliser.verbalise_over_role_sequence(c.role_sequence, nil, role_proximity)
266
371
  if c.min_frequency == 1 && c.max_frequency == nil and c.role_sequence.all_role_ref.size == 2
267
- # REVISIT: Implement the "either... or" syntax for a simple external mandatory constraint
268
- puts \
269
- "either #{
270
- c.role_sequence.all_role_ref.map { |rr|
271
- rr.role.fact_type.default_reading([], nil)
272
- }*" or "
273
- };"
372
+ puts "either #{expanded_readings*' or '};"
274
373
  else
275
- # REVISIT: If only one role is covered and it's mandatory >=1 constraint, use SOME/THAT form:
276
- # for each Bug SOME Tester logged THAT Bug;
277
374
  roles = c.role_sequence.all_role_ref.map{|rr| rr.role }
278
- players = c.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}.uniq
279
- fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
375
+ players = c.role_sequence.all_role_ref.map{|rr| verbaliser.subscripted_player(rr) }
376
+ players.uniq! if role_proximity == :proximate
377
+ min, max = c.min_frequency, c.max_frequency
378
+ pl = (min&&min>1)||(max&&max>1) ? 's' : ''
280
379
  puts \
281
- "each #{players.size > 1 ? "combination " : ""}#{players*", "} occurs #{c.frequency} time in\n\t"+
282
- "#{fact_types.map{|ft| ft.default_reading([], nil)}*",\n\t"}" +
283
- ";"
380
+ "each #{players.size > 1 ? "combination " : ""}#{players*", "} occurs #{c.frequency} time#{pl} in\n\t"+
381
+ "#{expanded_readings*",\n\t"};"
284
382
  end
285
383
  end
286
384
 
287
- # Find the common supertype of these concepts.
288
- # N.B. This will only work if all concepts are on the direct path to the deepest.
289
- def common_supertype(concepts)
290
- players_differ = false
291
- common =
292
- concepts[1..-1].inject(concepts[0]) do |supertype, concept|
293
- if !supertype || concept == supertype
294
- concept # Most common case
295
- elsif concept.supertypes_transitive.include?(supertype)
296
- players_differ = true
297
- supertype
298
- elsif supertype.supertypes_transitive.include?(concept)
299
- players_differ = true
300
- concept
301
- else
302
- return nil # No common supertype
303
- end
304
- end
305
- return common, players_differ
306
- end
307
-
308
- def dump_set_constraint(c)
309
- # REVISIT exclusion: every <player-list> must<?> either reading1, reading2, ...
310
-
311
- # Each constraint involves two or more occurrences of one or more players.
312
- # For each player, a subtype may be involved in the occurrences.
313
- # Find the common supertype of each player.
385
+ def dump_set_comparison_constraint(c)
314
386
  scrs = c.all_set_comparison_roles.sort_by{|scr| scr.ordinal}
315
- player_count = scrs[0].role_sequence.all_role_ref.size
316
- role_seq_count = scrs.size
317
-
318
- #raise "Can't verbalise constraint over many players and facts" if player_count > 1 and role_seq_count > 1
387
+ role_sequences = scrs.map{|scr|scr.role_sequence}
388
+ transposed_role_refs = scrs.map{|scr| scr.role_sequence.all_role_ref_in_order.to_a}.transpose
389
+ verbaliser = ActiveFacts::Metamodel::Verbaliser.new
390
+
391
+ # Tell the verbaliser all we know, so it can figure out which players to subscript:
392
+ transposed_role_refs.each { |role_refs| verbaliser.role_refs_are_subtype_joined role_refs }
393
+ role_sequences.each do |role_sequence|
394
+ verbaliser.prepare_role_sequence role_sequence
395
+ end
396
+ verbaliser.create_subscripts
319
397
 
320
- # puts "#{c.class.basename} has #{role_seq_count} scr's: #{scrs.map{|scr| "("+scr.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}*", "+")"}*", "}"
398
+ if role_sequences.detect{|scr| scr.all_role_ref.detect{|rr| rr.join_node}}
399
+ # This set constraint has an explicit join. Verbalise it.
321
400
 
322
- players_differ = [] # Record which players are also played by subclasses
323
- players = (0...player_count).map do |pindex|
324
- # Find the common supertype of the players of the pindex'th role in each sequence
325
- concepts = scrs.map do |r|
326
- r.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[pindex].role.concept
401
+ readings_list = role_sequences.
402
+ map do |rs|
403
+ verbaliser.verbalise_over_role_sequence(rs)
404
+ end
405
+ if c.is_a?(ActiveFacts::Metamodel::SetEqualityConstraint)
406
+ puts readings_list.join("\n\tif and only if\n\t") + ';'
407
+ return
408
+ end
409
+ if readings_list.size == 2 && c.is_mandatory # XOR constraint
410
+ puts "either " + readings_list.join(" or ") + " but not both;"
411
+ return
327
412
  end
328
- player, players_differ[pindex] = common_supertype(concepts)
329
- raise "Role sequences of #{c.class.basename} must have concepts matching #{concepts.map(&:name)*","} in position #{pindex}" unless player
330
- player
413
+ mode = c.is_mandatory ? "exactly one" : "at most one"
414
+ puts "for each #{players.map{|p| p.name}*", "} #{mode} of these holds:\n\t" +
415
+ readings_list.join(",\n\t") +
416
+ ';'
417
+ return
331
418
  end
332
- #puts "#{c.class.basename} has players #{players.map{|p| p.name}*", "}"
333
419
 
334
420
  if c.is_a?(ActiveFacts::Metamodel::SetEqualityConstraint)
335
- # REVISIT: Need a proper approach to some/that and adjective disambiguation:
336
421
  puts \
337
422
  scrs.map{|scr|
338
- scr.role_sequence.all_role_ref.map{|rr| rr.role.fact_type.default_reading([], nil) }*" and "
423
+ verbaliser.verbalise_over_role_sequence(scr.role_sequence)
339
424
  } * "\n\tif and only if\n\t" + ";"
340
425
  return
341
426
  end
342
427
 
428
+ # A constrained role may involve a subtyping join. We substitute the name of the supertype for all occurrences.
429
+ players = transposed_role_refs.map{|role_refs| common_supertype(role_refs.map{|rr| rr.role.concept})}
430
+ raise "Constraint must cover matching roles" if players.compact.size < players.size
431
+
432
+ readings_expanded = scrs.
433
+ map do |scr|
434
+ # verbaliser.verbalise_over_role_sequence(scr.role_sequence)
435
+ # REVISIT: verbalise_over_role_sequence cannot do what we need here, because of the
436
+ # possibility of subtyping joins in the constrained roles across the different scr's
437
+ # The following code uses "players" and "constrained_roles" to create substitutions.
438
+ # These should instead be passed to the verbaliser (one join node per index, role_refs for each).
439
+ fact_types_processed = {}
440
+ constrained_roles = scr.role_sequence.all_role_ref_in_order.map{|rr| rr.role}
441
+ join_over, joined_roles = *Metamodel.join_roles_over(constrained_roles)
442
+ constrained_roles.map do |constrained_role|
443
+ fact_type = constrained_role.fact_type
444
+ next nil if fact_types_processed[fact_type] # Don't emit the same fact type twice (in case of objectification join)
445
+ fact_types_processed[fact_type] = true
446
+ reading = fact_type.reading_preferably_starting_with_role(constrained_role)
447
+ expand_constrained(verbaliser, reading, constrained_roles, players)
448
+ end.compact * " and "
449
+ end
450
+
343
451
  if scrs.size == 2 && c.is_mandatory
344
- puts "either " +
345
- ( scrs.map do |scr|
346
- constrained_roles = scr.role_sequence.all_role_ref.map{|rr| rr.role }
347
- fact_types = constrained_roles.map{|r| r.fact_type }.uniq
348
-
349
- fact_types.map do |fact_type|
350
- # Choose a reading that starts with the input role (constrained role if none)
351
- reading = fact_type.all_reading.sort_by{|r| r.ordinal}.detect do |r|
352
- first_reading_role = r.role_sequence.all_role_ref.detect{|rr| rr.ordinal == 0}.role
353
- constrained_roles.include?(first_reading_role)
354
- end
355
- reading ||= fact_type.preferred_reading
356
- expand_constrained(reading, constrained_roles, players, players_differ)
357
- end * " and "
358
- end*" or "
359
- ) +
360
- " but not both;"
452
+ puts "either " + readings_expanded*" or " + " but not both;"
361
453
  else
362
454
  mode = c.is_mandatory ? "exactly one" : "at most one"
363
455
  puts "for each #{players.map{|p| p.name}*", "} #{mode} of these holds:\n\t" +
364
- (scrs.map do |scr|
365
- constrained_roles = scr.role_sequence.all_role_ref.map{|rr| rr.role }
366
- fact_types = constrained_roles.map{|r| r.fact_type }.uniq
367
-
368
- fact_types.map do |fact_type|
369
- # REVISIT: future: Use "THAT" and "SOME" only when:
370
- # - the role player occurs twice in the reading, or
371
- # - is a subclass of the constrained concept, or
372
- reading = fact_type.preferred_reading
373
- expand_constrained(reading, constrained_roles, players, players_differ)
374
- end * " and "
375
-
376
- end*",\n\t"
377
- )+';'
456
+ readings_expanded*",\n\t" + ';'
378
457
  end
379
458
  end
380
459
 
381
- # Expand this reading using (in)definite articles where needed
382
- # Handle any roles in constrained_roles specially.
383
- def expand_constrained(reading, constrained_roles, players, players_differ)
384
- frequency_constraints = reading.role_sequence.all_role_ref.map {|role_ref|
385
- i = constrained_roles.index(role_ref.role)
386
- if !i
387
- [ "some", role_ref.role.concept.name]
388
- elsif players_differ[i]
389
- [ "that", players[i].name ] # Make sure to use the superclass name
390
- else
391
- if reading.fact_type.all_role.select{|r| r.concept == role_ref.role.concept }.size > 1
392
- [ "that", role_ref.role.concept.name ]
393
- else
394
- [ "some", role_ref.role.concept.name ]
395
- end
396
- end
397
- }
398
- frequency_constraints = [] unless frequency_constraints.detect{|fc| fc[0] != "some" }
399
-
400
- #$stderr.puts "fact_type roles (#{fact_type.all_role.map{|r| r.concept.name}*","}) default_reading '#{fact_type.preferred_reading.text}' roles (#{fact_type.preferred_reading.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}*","}) #{frequency_constraints.inspect}"
401
-
402
- # REVISIT: Make sure that we refer to the constrained players by their common supertype
403
-
404
- reading.expand(frequency_constraints, nil)
405
- end
406
-
407
460
  def dump_subset_constraint(c)
408
461
  # If the role players are identical and not duplicated, we can simply say "reading1 only if reading2"
409
462
  subset_roles, subset_fact_types =
410
- c.subset_role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| [rr.role, rr.role.fact_type]}.transpose
463
+ c.subset_role_sequence.all_role_ref_in_order.map{|rr| [rr.role, rr.role.fact_type]}.transpose
411
464
  superset_roles, superset_fact_types =
412
- c.superset_role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| [rr.role, rr.role.fact_type]}.transpose
413
-
414
- subset_fact_types.uniq!
415
- superset_fact_types.uniq!
416
-
417
- subset_players = subset_roles.map(&:concept)
418
- superset_players = superset_roles.map(&:concept)
419
-
420
- # We need to ensure that if the player of any constrained role also exists
421
- # as the player of a role that's not a constrained role, there are different
422
- # adjectives or other qualifiers qualifier applied to distinguish that role.
423
- fact_type_roles = (subset_fact_types+superset_fact_types).map{|ft| ft.all_role }.flatten
424
- non_constrained_roles = fact_type_roles - subset_roles - superset_roles
425
- if (r = non_constrained_roles.detect{|r| (subset_roles+superset_roles).include?(r) })
426
- # REVISIT: Find a way to deal with this problem, should it arise.
427
-
428
- # It would help, but not entirely fix it, to use SOME/THAT to identify the constrained roles.
429
- # See ServiceDirector's DataStore<->Client fact types for example
430
- # Use SOME on the subset, THAT on the superset.
431
- raise "Critical ambiguity, #{r.concept.name} occurs both constrained and unconstrained in #{c.name}"
432
- end
465
+ c.superset_role_sequence.all_role_ref_in_order.map{|rr| [rr.role, rr.role.fact_type]}.transpose
466
+ transposed_role_refs = [c.subset_role_sequence, c.superset_role_sequence].map{|rs| rs.all_role_ref_in_order.to_a}.transpose
467
+
468
+ verbaliser = ActiveFacts::Metamodel::Verbaliser.new
469
+ transposed_role_refs.each { |role_refs| verbaliser.role_refs_are_subtype_joined role_refs }
470
+ verbaliser.prepare_role_sequence c.subset_role_sequence
471
+ verbaliser.prepare_role_sequence c.superset_role_sequence
472
+ verbaliser.create_subscripts
433
473
 
434
474
  puts \
435
- "#{subset_fact_types.map{|ft| ft.default_reading([], nil)}*" and "}" +
475
+ verbaliser.verbalise_over_role_sequence(c.subset_role_sequence) +
436
476
  "\n\tonly if " +
437
- "#{superset_fact_types.map{|ft| ft.default_reading([], nil)}*" and "}" +
477
+ verbaliser.verbalise_over_role_sequence(c.superset_role_sequence) +
438
478
  ";"
439
479
  end
440
480
 
441
481
  def dump_ring_constraint(c)
442
482
  # At present, no ring constraint can be missed to be handled in this pass
443
- puts "// #{c.ring_type} ring over #{c.role.fact_type.default_reading([], nil)}"
483
+ puts "// #{c.ring_type} ring over #{c.role.fact_type.default_reading}"
444
484
  end
445
485
 
446
486
  def constraint_dump(c)
@@ -450,13 +490,96 @@ module ActiveFacts
450
490
  when ActiveFacts::Metamodel::RingConstraint
451
491
  dump_ring_constraint(c)
452
492
  when ActiveFacts::Metamodel::SetComparisonConstraint # includes SetExclusionConstraint, SetEqualityConstraint
453
- dump_set_constraint(c)
493
+ dump_set_comparison_constraint(c)
454
494
  when ActiveFacts::Metamodel::SubsetConstraint
455
495
  dump_subset_constraint(c)
456
496
  else
457
497
  "#{c.class.basename} #{c.name}: unhandled constraint type"
458
498
  end
459
499
  end
500
+
501
+ # Find the common supertype of these concepts.
502
+ def common_supertype(concepts)
503
+ common = concepts[0].supertypes_transitive
504
+ concepts[1..-1].each do |concept|
505
+ common &= concept.supertypes_transitive
506
+ end
507
+ common[0]
508
+ end
509
+
510
+ #============================================================
511
+ # Verbalisation functions for fact type and entity type definitions
512
+ #============================================================
513
+
514
+ def fact_readings_with_constraints(verbaliser, fact_type)
515
+ fact_constraints = @presence_constraints_by_fact[fact_type]
516
+ readings = []
517
+ define_role_names = true
518
+ fact_type.all_reading_by_ordinal.each do |reading|
519
+ readings << expanded_reading(verbaliser, reading, fact_constraints, define_role_names)
520
+ define_role_names = false # No need to define role names in subsequent readings
521
+ end
522
+ readings
523
+ end
524
+
525
+ def expanded_reading(verbaliser, reading, fact_constraints, define_role_names)
526
+ # Arrange the roles in order they occur in this reading:
527
+ role_refs = reading.role_sequence.all_role_ref_in_order
528
+ role_numbers = reading.text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
529
+ roles = role_numbers.map{|m| role_refs[m].role }
530
+
531
+ # Find the constraints that constrain frequency over each role we can verbalise:
532
+ frequency_constraints = []
533
+ value_constraints = []
534
+ roles.each do |role|
535
+ value_constraints <<
536
+ if vc = role.role_value_constraint and !@constraints_used[vc]
537
+ @constraints_used[vc] = true
538
+ vc.describe
539
+ else
540
+ nil
541
+ end
542
+
543
+ frequency_constraints <<
544
+ if (role == roles.last) # On the last role of the reading, emit any presence constraint
545
+ constraint = fact_constraints.
546
+ detect do |c| # Find a UC that spans all other Roles
547
+ c.is_a?(ActiveFacts::Metamodel::PresenceConstraint) &&
548
+ !@constraints_used[c] && # Already verbalised
549
+ roles-c.role_sequence.all_role_ref.map(&:role) == [role]
550
+ end
551
+ @constraints_used[constraint] = true if constraint
552
+ constraint && constraint.frequency
553
+ else
554
+ nil
555
+ end
556
+ end
557
+
558
+ expanded = verbaliser.expand_reading(reading, frequency_constraints, define_role_names, value_constraints)
559
+
560
+ if (ft_rings = @ring_constraints_by_fact[reading.fact_type]) &&
561
+ (ring = ft_rings.detect{|rc| !@constraints_used[rc]})
562
+ @constraints_used[ring] = true
563
+ append_ring_to_reading(expanded, ring)
564
+ end
565
+ expanded
566
+ end
567
+
568
+ # Expand this reading, substituting players[i].name for the each role in the i'th position in constrained_roles
569
+ def expand_constrained(verbaliser, reading, constrained_roles, players)
570
+ # Make sure that we refer to the constrained players by their common supertype (as passed in)
571
+ frequency_constraints = reading.role_sequence.all_role_ref.
572
+ map do |role_ref|
573
+ player = role_ref.role.concept
574
+ i = constrained_roles.index(role_ref.role)
575
+ player = players[i] if i
576
+ [ nil, player.name ]
577
+ end
578
+ frequency_constraints = [] unless frequency_constraints.detect{|fc| fc[0] != "some" }
579
+
580
+ verbaliser.expand_reading(reading, frequency_constraints)
581
+ end
582
+
460
583
  end
461
584
  end
462
585
  end