activefacts 0.7.3 → 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/Manifest.txt +24 -2
- data/Rakefile +25 -3
- data/bin/afgen +1 -1
- data/bin/cql +13 -2
- data/css/offline.css +3 -0
- data/css/orm2.css +24 -0
- data/css/print.css +8 -0
- data/css/style-print.css +357 -0
- data/css/style.css +387 -0
- data/download.html +85 -0
- data/examples/CQL/Address.cql +3 -3
- data/examples/CQL/Blog.cql +13 -14
- data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
- data/examples/CQL/Death.cql +3 -2
- data/examples/CQL/Genealogy.cql +13 -11
- data/examples/CQL/Marriage.cql +2 -2
- data/examples/CQL/Metamodel.cql +136 -93
- data/examples/CQL/MultiInheritance.cql +2 -2
- data/examples/CQL/OilSupply.cql +14 -10
- data/examples/CQL/Orienteering.cql +22 -19
- data/examples/CQL/PersonPlaysGame.cql +3 -2
- data/examples/CQL/SchoolActivities.cql +4 -2
- data/examples/CQL/SimplestUnary.cql +1 -1
- data/examples/CQL/SubtypePI.cql +6 -7
- data/examples/CQL/Warehousing.cql +16 -19
- data/examples/CQL/unit.cql +584 -0
- data/examples/index.html +276 -0
- data/examples/intro.html +497 -0
- data/examples/local.css +20 -0
- data/index.html +96 -0
- data/lib/activefacts/api/concept.rb +48 -46
- data/lib/activefacts/api/constellation.rb +43 -23
- data/lib/activefacts/api/entity.rb +2 -2
- data/lib/activefacts/api/instance.rb +6 -2
- data/lib/activefacts/api/instance_index.rb +5 -0
- data/lib/activefacts/api/value.rb +8 -2
- data/lib/activefacts/api/vocabulary.rb +15 -10
- data/lib/activefacts/cql/CQLParser.treetop +109 -88
- data/lib/activefacts/cql/Concepts.treetop +32 -10
- data/lib/activefacts/cql/Context.treetop +34 -0
- data/lib/activefacts/cql/Expressions.treetop +9 -9
- data/lib/activefacts/cql/FactTypes.treetop +30 -31
- data/lib/activefacts/cql/Language/English.treetop +50 -0
- data/lib/activefacts/cql/LexicalRules.treetop +2 -1
- data/lib/activefacts/cql/Terms.treetop +117 -0
- data/lib/activefacts/cql/ValueTypes.treetop +152 -0
- data/lib/activefacts/cql/compiler.rb +1718 -0
- data/lib/activefacts/cql/parser.rb +124 -57
- data/lib/activefacts/generate/absorption.rb +1 -1
- data/lib/activefacts/generate/cql.rb +111 -100
- data/lib/activefacts/generate/cql/html.rb +5 -5
- data/lib/activefacts/generate/oo.rb +3 -3
- data/lib/activefacts/generate/ordered.rb +51 -19
- data/lib/activefacts/generate/ruby.rb +10 -8
- data/lib/activefacts/generate/sql/mysql.rb +14 -10
- data/lib/activefacts/generate/sql/server.rb +29 -24
- data/lib/activefacts/input/cql.rb +9 -1264
- data/lib/activefacts/input/orm.rb +213 -200
- data/lib/activefacts/persistence/columns.rb +11 -10
- data/lib/activefacts/persistence/index.rb +15 -18
- data/lib/activefacts/persistence/reference.rb +17 -17
- data/lib/activefacts/persistence/tables.rb +50 -51
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +79 -8
- data/lib/activefacts/vocabulary/metamodel.rb +183 -114
- data/spec/absorption_ruby_spec.rb +99 -0
- data/spec/absorption_spec.rb +3 -4
- data/spec/api/constellation.rb +1 -1
- data/spec/api/entity_type.rb +3 -1
- data/spec/api/instance.rb +4 -2
- data/spec/api/roles.rb +8 -6
- data/spec/api_spec.rb +1 -2
- data/spec/cql/context_spec.rb +71 -0
- data/spec/cql/samples_spec.rb +154 -0
- data/spec/cql/unit_spec.rb +375 -0
- data/spec/cql_cql_spec.rb +31 -21
- data/spec/cql_mysql_spec.rb +70 -0
- data/spec/cql_parse_spec.rb +15 -9
- data/spec/cql_ruby_spec.rb +27 -13
- data/spec/cql_sql_spec.rb +42 -16
- data/spec/cql_symbol_tables_spec.rb +2 -3
- data/spec/cqldump_spec.rb +7 -7
- data/spec/helpers/file_matcher.rb +39 -0
- data/spec/norma_cql_spec.rb +20 -12
- data/spec/norma_ruby_spec.rb +6 -3
- data/spec/norma_sql_spec.rb +6 -3
- data/spec/norma_tables_spec.rb +6 -4
- data/spec/spec_helper.rb +27 -8
- data/status.html +69 -0
- data/why.html +60 -0
- metadata +34 -11
- data/lib/activefacts/cql/DataTypes.treetop +0 -81
- data/spec/cql_unit_spec.rb +0 -330
@@ -92,6 +92,7 @@ module ActiveFacts
|
|
92
92
|
# When Xyz is followed by XyzID, truncate that to just ID
|
93
93
|
debug :columns, "truncating repeated #{last_names.last} in #{names[0]}"
|
94
94
|
names[0] = names[0][last_names.last.size..-1]
|
95
|
+
names.shift if names[0] == ''
|
95
96
|
elsif last_names.last == names[0]
|
96
97
|
# Same, but where an underscore split up the words
|
97
98
|
debug :columns, "truncating repeated name in #{names.inspect}"
|
@@ -358,8 +359,8 @@ module ActiveFacts
|
|
358
359
|
# Make schema transformations like adding ValueType self-value columns (and later, Rails-friendly ID fields).
|
359
360
|
# Override this method to change the transformations
|
360
361
|
def finish_schema
|
361
|
-
|
362
|
-
|
362
|
+
all_concept.each do |concept|
|
363
|
+
concept.self_value_reference if concept.is_a?(ActiveFacts::Metamodel::ValueType) && concept.is_table
|
363
364
|
end
|
364
365
|
end
|
365
366
|
|
@@ -368,18 +369,18 @@ module ActiveFacts
|
|
368
369
|
finish_schema
|
369
370
|
|
370
371
|
debug :columns, "Populating all columns" do
|
371
|
-
|
372
|
-
next if !
|
373
|
-
debug :columns, "Populating columns for table #{
|
374
|
-
|
372
|
+
all_concept.each do |concept|
|
373
|
+
next if !concept.is_table
|
374
|
+
debug :columns, "Populating columns for table #{concept.name}" do
|
375
|
+
concept.populate_columns
|
375
376
|
end
|
376
377
|
end
|
377
378
|
end
|
378
379
|
debug :columns, "Finished columns" do
|
379
|
-
|
380
|
-
next if !
|
381
|
-
debug :columns, "Finished columns for table #{
|
382
|
-
|
380
|
+
all_concept.each do |concept|
|
381
|
+
next if !concept.is_table
|
382
|
+
debug :columns, "Finished columns for table #{concept.name}" do
|
383
|
+
concept.columns.each do |column|
|
383
384
|
debug :columns, "#{column}"
|
384
385
|
end
|
385
386
|
end
|
@@ -54,13 +54,13 @@ module ActiveFacts
|
|
54
54
|
end
|
55
55
|
|
56
56
|
# An array of the names of the columns this index covers
|
57
|
-
def column_names
|
58
|
-
columns.map{|column| column.name}
|
57
|
+
def column_names(joiner = "")
|
58
|
+
columns.map{|column| column.name(joiner)}
|
59
59
|
end
|
60
60
|
|
61
61
|
# An array of the names of the columns this index covers, with some lexical truncations.
|
62
|
-
def abbreviated_column_names
|
63
|
-
columns.map{|column| column.name.sub(/^#{over.name}/,'')}
|
62
|
+
def abbreviated_column_names(joiner = "")
|
63
|
+
columns.map{|column| column.name(joiner).sub(/^#{over.name}/,'')}
|
64
64
|
end
|
65
65
|
|
66
66
|
# The name of a view that can be created to enforce uniqueness over non-null key values
|
@@ -183,25 +183,22 @@ module ActiveFacts
|
|
183
183
|
class Vocabulary
|
184
184
|
def populate_all_indices #:nodoc:
|
185
185
|
debug :index, "Populating all concept indices" do
|
186
|
-
|
187
|
-
|
188
|
-
feature.clear_indices
|
186
|
+
all_concept.each do |concept|
|
187
|
+
concept.clear_indices
|
189
188
|
end
|
190
|
-
|
191
|
-
next unless
|
192
|
-
|
193
|
-
|
194
|
-
feature.populate_indices
|
189
|
+
all_concept.each do |concept|
|
190
|
+
next unless concept.is_table
|
191
|
+
debug :index, "Populating indices for #{concept.name}" do
|
192
|
+
concept.populate_indices
|
195
193
|
end
|
196
194
|
end
|
197
195
|
end
|
198
196
|
debug :index, "Finished concept indices" do
|
199
|
-
|
200
|
-
next unless
|
201
|
-
next unless
|
202
|
-
|
203
|
-
|
204
|
-
feature.indices.each do |index|
|
197
|
+
all_concept.each do |concept|
|
198
|
+
next unless concept.is_table
|
199
|
+
next unless concept.indices.size > 0
|
200
|
+
debug :index, "#{concept.name}:" do
|
201
|
+
concept.indices.each do |index|
|
205
202
|
debug :index, index
|
206
203
|
end
|
207
204
|
end
|
@@ -117,7 +117,7 @@ module ActiveFacts
|
|
117
117
|
if @to && @to.fact_type
|
118
118
|
@to.name.camelwords
|
119
119
|
else
|
120
|
-
@to_role.fact_type.preferred_reading.
|
120
|
+
@to_role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.camelwords
|
121
121
|
end
|
122
122
|
when @to && !@to_role # @to is an objectified fact type so @to_role is a phantom
|
123
123
|
@to.name.camelwords
|
@@ -260,7 +260,8 @@ module ActiveFacts
|
|
260
260
|
|
261
261
|
when :supertype # A subtype absorbs a reference to its supertype when separate, or all when partitioned
|
262
262
|
# REVISIT: Or when partitioned
|
263
|
-
|
263
|
+
raise hell unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
264
|
+
if role.fact_type.assimilation # assimilation == 'separate' or assimilation == 'partitioned'
|
264
265
|
debug :references, "supertype #{name} doesn't absorb a reference to separate subtype #{role.fact_type.subtype.name}"
|
265
266
|
else
|
266
267
|
r = ActiveFacts::Persistence::Reference.new(self, role)
|
@@ -270,7 +271,8 @@ module ActiveFacts
|
|
270
271
|
end
|
271
272
|
|
272
273
|
when :subtype # This object is a supertype, which can absorb the subtype unless that's independent
|
273
|
-
|
274
|
+
raise hell unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
275
|
+
if role.fact_type.assimilation
|
274
276
|
ActiveFacts::Persistence::Reference.new(self, role).tabulate
|
275
277
|
# If partitioned, the supertype is absorbed into *each* subtype; a reference to the supertype needs to know which
|
276
278
|
else
|
@@ -282,6 +284,7 @@ module ActiveFacts
|
|
282
284
|
|
283
285
|
# Decide which way the one-to-one is likely to go; it will be flipped later if necessary.
|
284
286
|
# Force the decision if just one is independent:
|
287
|
+
# REVISIT: Decide whether supertype assimilation can affect this
|
285
288
|
r.tabulate and return if is_independent and !r.to.is_independent
|
286
289
|
return if !is_independent and r.to.is_independent
|
287
290
|
|
@@ -335,25 +338,22 @@ module ActiveFacts
|
|
335
338
|
class Vocabulary
|
336
339
|
def populate_all_references #:nodoc:
|
337
340
|
debug :references, "Populating all concept references" do
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
feature.tentative = true # Uncertain
|
341
|
+
all_concept.each do |concept|
|
342
|
+
concept.clear_references
|
343
|
+
concept.is_table = nil # Undecided; force an attempt to decide
|
344
|
+
concept.tentative = true # Uncertain
|
343
345
|
end
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
feature.populate_references
|
346
|
+
all_concept.each do |concept|
|
347
|
+
debug :references, "Populating references for #{concept.name}" do
|
348
|
+
concept.populate_references
|
348
349
|
end
|
349
350
|
end
|
350
351
|
end
|
351
352
|
debug :references, "Finished concept references" do
|
352
|
-
|
353
|
-
next unless
|
354
|
-
|
355
|
-
|
356
|
-
feature.references_from.each do |ref|
|
353
|
+
all_concept.each do |concept|
|
354
|
+
next unless concept.references_from.size > 0
|
355
|
+
debug :references, "#{concept.name}:" do
|
356
|
+
concept.references_from.each do |ref|
|
357
357
|
debug :references, "#{ref}"
|
358
358
|
end
|
359
359
|
end
|
@@ -88,8 +88,7 @@ module ActiveFacts
|
|
88
88
|
# Subtypes are not a table unless partitioned or separate
|
89
89
|
# REVISIT: Support partitioned subtypes here
|
90
90
|
if (!supertypes.empty?)
|
91
|
-
|
92
|
-
return @is_table = false
|
91
|
+
return @is_table = all_supertype_inheritance.detect{|ti| ti.assimilation} != nil
|
93
92
|
end
|
94
93
|
|
95
94
|
# If the preferred_identifier includes an auto_assigned ValueType
|
@@ -169,7 +168,7 @@ module ActiveFacts
|
|
169
168
|
# 2) Decide which Concepts must be and must not be tables
|
170
169
|
# a. Concepts labelled is_independent are tables (See the is_table methods above)
|
171
170
|
# b. Entity types having no references to them must be tables
|
172
|
-
# c. subtypes are not tables unless marked
|
171
|
+
# c. subtypes are not tables unless marked with assimilation = separate or partitioned
|
173
172
|
# d. ValueTypes are never tables unless they can have references (to other ValueTypes)
|
174
173
|
# e. An EntityType having an identifying AutoInc field must be a table unless it has exactly one reference
|
175
174
|
# f. An EntityType whose only reference is through its single preferred_identifier role gets absorbed
|
@@ -182,19 +181,18 @@ module ActiveFacts
|
|
182
181
|
populate_all_references
|
183
182
|
|
184
183
|
debug :absorption, "Calculating relational composition" do
|
185
|
-
# Evaluate the possible independence of each concept, building an array of
|
184
|
+
# Evaluate the possible independence of each concept, building an array of concepts of indeterminate status:
|
186
185
|
undecided =
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
feature.tentative # Selection criterion
|
186
|
+
all_concept.select do |concept|
|
187
|
+
concept.is_table # Ask it whether it thinks it should be a table
|
188
|
+
concept.tentative # Selection criterion
|
191
189
|
end
|
192
190
|
|
193
191
|
if debug :absorption, "Generating tables, #{undecided.size} undecided"
|
194
|
-
(
|
195
|
-
next if ValueType ===
|
192
|
+
(all_concept-undecided).each {|concept|
|
193
|
+
next if ValueType === concept && !concept.is_table # Skip unremarkable cases
|
196
194
|
debug :absorption do
|
197
|
-
debug :absorption, "#{
|
195
|
+
debug :absorption, "#{concept.name} is #{concept.is_table ? "" : "not "}a table#{concept.tentative ? ", tentatively" : ""}"
|
198
196
|
end
|
199
197
|
}
|
200
198
|
end
|
@@ -205,80 +203,81 @@ module ActiveFacts
|
|
205
203
|
debug :absorption, "Starting composition pass #{pass} with #{undecided.size} undecided tables"
|
206
204
|
possible_flips = {} # A hash by table containing an array of references that can be flipped
|
207
205
|
finalised = # Make an array of things we finalised during this pass
|
208
|
-
undecided.select do |
|
209
|
-
debug :absorption, "Considering #{
|
210
|
-
debug :absorption, "refs to #{
|
211
|
-
debug :absorption, "refs from #{
|
206
|
+
undecided.select do |concept|
|
207
|
+
debug :absorption, "Considering #{concept.name}:" do
|
208
|
+
debug :absorption, "refs to #{concept.name} are from #{concept.references_to.map{|ref| ref.from.name}*", "}" if concept.references_to.size > 0
|
209
|
+
debug :absorption, "refs from #{concept.name} are to #{concept.references_from.map{|ref| ref.to.name rescue ref.fact_type.default_reading}*", "}" if concept.references_from.size > 0
|
212
210
|
|
213
211
|
# Always absorb an objectified unary into its role player:
|
214
|
-
if
|
215
|
-
debug :absorption, "Absorb objectified unary #{
|
216
|
-
|
217
|
-
next
|
212
|
+
if concept.fact_type && concept.fact_type.all_role.size == 1
|
213
|
+
debug :absorption, "Absorb objectified unary #{concept.name} into #{concept.fact_type.entity_type.name}"
|
214
|
+
concept.definitely_not_table
|
215
|
+
next concept
|
218
216
|
end
|
219
217
|
|
220
218
|
# If the PI contains one role only, played by an entity type that can absorb us, do that.
|
221
|
-
pi_roles =
|
219
|
+
pi_roles = concept.preferred_identifier.role_sequence.all_role_ref.map(&:role)
|
222
220
|
debug :absorption, "pi_roles are played by #{pi_roles.map{|role| role.concept.name}*", "}"
|
223
221
|
first_pi_role = pi_roles[0]
|
224
222
|
pi_ref = nil
|
225
223
|
if pi_roles.size == 1 and
|
226
|
-
|
224
|
+
concept.references_to.detect{|ref| pi_ref = ref if ref.from_role == first_pi_role && ref.from.is_a?(EntityType)}
|
227
225
|
|
228
|
-
debug :absorption, "#{
|
229
|
-
|
230
|
-
next
|
226
|
+
debug :absorption, "#{concept.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}"
|
227
|
+
concept.definitely_not_table
|
228
|
+
next concept
|
231
229
|
end
|
232
230
|
|
233
231
|
# If there's more than one absorption path and any functional dependencies that can't absorb us, it's a table
|
234
232
|
non_identifying_refs_from =
|
235
|
-
|
233
|
+
concept.references_from.reject{|ref|
|
236
234
|
pi_roles.include?(ref.to_role)
|
237
235
|
}
|
238
|
-
debug :absorption, "#{
|
236
|
+
debug :absorption, "#{concept.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
|
239
237
|
|
240
|
-
if
|
238
|
+
if concept.references_to.size > 1 and
|
241
239
|
non_identifying_refs_from.size > 0
|
242
|
-
debug :absorption, "#{
|
243
|
-
|
244
|
-
next
|
240
|
+
debug :absorption, "#{concept.name} has non-identifying functional dependencies so 3NF requires it be a table"
|
241
|
+
concept.definitely_table
|
242
|
+
next concept
|
245
243
|
end
|
246
244
|
|
247
245
|
absorption_paths =
|
248
246
|
(
|
249
247
|
non_identifying_refs_from.reject do |ref|
|
250
248
|
!ref.to or ref.to.absorbed_via == ref
|
251
|
-
end+
|
249
|
+
end+concept.references_to
|
252
250
|
).reject do |ref|
|
253
251
|
next true if !ref.to.is_table or
|
254
252
|
![:one_one, :supertype, :subtype].include?(ref.role_type)
|
255
253
|
|
256
|
-
#
|
254
|
+
# Don't absorb an object along a non-mandatory role (otherwise if it doesn't play that role, it can't exist either)
|
257
255
|
from_is_mandatory = !!ref.is_mandatory
|
258
256
|
to_is_mandatory = !ref.to_role || !!ref.to_role.is_mandatory
|
259
|
-
|
260
|
-
|
257
|
+
|
258
|
+
bad = !(ref.from == concept ? from_is_mandatory : to_is_mandatory)
|
259
|
+
debug :absorption, "Not absorbing #{concept.name} through non-mandatory #{ref}" if bad
|
261
260
|
bad
|
262
261
|
end
|
263
262
|
|
264
263
|
# If this object can be fully absorbed, do that (might require flipping some references)
|
265
264
|
if absorption_paths.size > 0
|
266
|
-
debug :absorption, "#{
|
265
|
+
debug :absorption, "#{concept.name} is fully absorbed through #{absorption_paths.inspect}"
|
267
266
|
absorption_paths.each do |ref|
|
268
|
-
debug :absorption, "flip #{ref} so #{
|
269
|
-
ref.flip if
|
267
|
+
debug :absorption, "flip #{ref} so #{concept.name} can be absorbed"
|
268
|
+
ref.flip if concept == ref.from
|
270
269
|
end
|
271
|
-
|
272
|
-
next
|
270
|
+
concept.definitely_not_table
|
271
|
+
next concept
|
273
272
|
end
|
274
273
|
|
275
274
|
if non_identifying_refs_from.size == 0
|
276
|
-
# and (!
|
275
|
+
# and (!concept.is_a?(EntityType) ||
|
277
276
|
# # REVISIT: The roles may be collectively but not individually mandatory.
|
278
|
-
#
|
279
|
-
debug :absorption, "#{
|
280
|
-
|
281
|
-
next
|
277
|
+
# concept.references_to.detect { |ref| !ref.from_role || ref.from_role.is_mandatory })
|
278
|
+
debug :absorption, "#{concept.name} is fully absorbed in #{concept.references_to.size} places: #{concept.references_to.map{|ref| ref.from.name}*", "}"
|
279
|
+
concept.definitely_not_table
|
280
|
+
next concept
|
282
281
|
end
|
283
282
|
|
284
283
|
false # Failed to decide about this entity_type this time around
|
@@ -291,18 +290,18 @@ module ActiveFacts
|
|
291
290
|
|
292
291
|
# A ValueType that isn't explicitly a table and isn't needed anywhere doesn't matter,
|
293
292
|
# unless it should absorb something else (another ValueType is all it could be):
|
294
|
-
|
295
|
-
if (!
|
296
|
-
debug :absorption, "Making #{
|
297
|
-
|
293
|
+
all_concept.each do |concept|
|
294
|
+
if (!concept.is_table and concept.references_to.size == 0 and concept.references_from.size > 0)
|
295
|
+
debug :absorption, "Making #{concept.name} a table; it has nowhere else to go and needs to absorb things"
|
296
|
+
concept.probably_table
|
298
297
|
end
|
299
298
|
end
|
300
299
|
|
301
300
|
# Now, evaluate all possibilities of the tentative assignments
|
302
301
|
# Incomplete. Apparently unnecessary as well... so far. We'll see.
|
303
302
|
if debug :absorption
|
304
|
-
undecided.each do |
|
305
|
-
debug :absorption, "Unable to decide independence of #{
|
303
|
+
undecided.each do |concept|
|
304
|
+
debug :absorption, "Unable to decide independence of #{concept.name}, going with #{concept.show_tabular}"
|
306
305
|
end
|
307
306
|
end
|
308
307
|
end
|
@@ -311,7 +310,7 @@ module ActiveFacts
|
|
311
310
|
populate_all_indices
|
312
311
|
|
313
312
|
@tables =
|
314
|
-
|
313
|
+
all_concept.
|
315
314
|
select { |f| f.is_table }.
|
316
315
|
sort_by { |table| table.name }
|
317
316
|
end
|
data/lib/activefacts/version.rb
CHANGED
@@ -13,7 +13,9 @@ module ActiveFacts
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def preferred_reading
|
16
|
-
all_reading_by_ordinal[0]
|
16
|
+
p = all_reading_by_ordinal[0]
|
17
|
+
raise "No reading for (#{all_role.map{|r| r.concept.name}*", "})" unless p
|
18
|
+
p
|
17
19
|
end
|
18
20
|
|
19
21
|
def describe(highlight = nil)
|
@@ -39,7 +41,7 @@ module ActiveFacts
|
|
39
41
|
rs.all_presence_constraint.detect{|pc|
|
40
42
|
pc.max_frequency == 1
|
41
43
|
}
|
42
|
-
}
|
44
|
+
} ? true : false
|
43
45
|
end
|
44
46
|
|
45
47
|
def is_mandatory
|
@@ -49,7 +51,7 @@ module ActiveFacts
|
|
49
51
|
rs.all_presence_constraint.detect{|pc|
|
50
52
|
pc.min_frequency and pc.min_frequency >= 1 and pc.is_mandatory
|
51
53
|
}
|
52
|
-
}
|
54
|
+
} ? true : false
|
53
55
|
end
|
54
56
|
|
55
57
|
# Return the RoleRef to this role from its fact type's preferred_reading
|
@@ -79,7 +81,7 @@ module ActiveFacts
|
|
79
81
|
def role_name(joiner = "-")
|
80
82
|
name_array =
|
81
83
|
if role.fact_type.all_role.size == 1
|
82
|
-
role.fact_type.preferred_reading.
|
84
|
+
role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.split(/\s/)
|
83
85
|
else
|
84
86
|
role.role_name || [leading_adjective, role.concept.name, trailing_adjective].compact.map{|w| w.split(/\s/)}.flatten
|
85
87
|
end
|
@@ -310,8 +312,8 @@ module ActiveFacts
|
|
310
312
|
# REVISIT: This should probably be changed to be the fact role sequence.
|
311
313
|
#
|
312
314
|
# define_role_names here is false (use defined names), true (define names) or nil (neither)
|
313
|
-
def expand(frequency_constraints = [], define_role_names = false)
|
314
|
-
expanded = "#{
|
315
|
+
def expand(frequency_constraints = [], define_role_names = false, literals = [])
|
316
|
+
expanded = "#{text}"
|
315
317
|
role_refs = role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
|
316
318
|
(0...role_refs.size).each{|i|
|
317
319
|
role_ref = role_refs[i]
|
@@ -337,12 +339,15 @@ module ActiveFacts
|
|
337
339
|
else
|
338
340
|
player_name = player.name
|
339
341
|
end
|
342
|
+
literal = literals[i]
|
340
343
|
[
|
341
344
|
fc ? fc : nil,
|
342
345
|
la,
|
343
346
|
define_role_names == false && role_name ? role_name : player_name,
|
344
347
|
ta,
|
345
|
-
define_role_names && role_name && player.name != role_name ? "(as #{role_name})" : nil
|
348
|
+
define_role_names && role_name && player.name != role_name ? "(as #{role_name})" : nil,
|
349
|
+
# Can't have both a literal and a restriction, but we don't enforce that here:
|
350
|
+
literal ? literal : nil
|
346
351
|
].compact*" "
|
347
352
|
}
|
348
353
|
}
|
@@ -352,7 +357,7 @@ module ActiveFacts
|
|
352
357
|
end
|
353
358
|
|
354
359
|
def words_and_role_refs
|
355
|
-
|
360
|
+
text.
|
356
361
|
scan(/(?: |\{[0-9]+\}|[^{} ]+)/). # split up the text into words
|
357
362
|
reject{|s| s==' '}. # Remove white space
|
358
363
|
map do |frag| # and go through the bits
|
@@ -365,6 +370,72 @@ module ActiveFacts
|
|
365
370
|
end
|
366
371
|
end
|
367
372
|
|
373
|
+
class ValueRestriction
|
374
|
+
def describe
|
375
|
+
"restricted to {"+
|
376
|
+
all_allowed_range_sorted.map{|ar| ar.to_s(false) }*", "+
|
377
|
+
"}"
|
378
|
+
end
|
379
|
+
|
380
|
+
def all_allowed_range_sorted
|
381
|
+
all_allowed_range.sort_by{|ar|
|
382
|
+
((min = ar.value_range.minimum_bound) && min.value.literal) ||
|
383
|
+
((max = ar.value_range.maximum_bound) && max.value.literal)
|
384
|
+
}
|
385
|
+
end
|
386
|
+
|
387
|
+
def to_s
|
388
|
+
if all_allowed_range.size > 1
|
389
|
+
"[" +
|
390
|
+
all_allowed_range.sorted.map { |ar| ar.to_s(true) }*", " +
|
391
|
+
"]"
|
392
|
+
else
|
393
|
+
all_allowed_range.single.to_s
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
class AllowedRange
|
399
|
+
def to_s(infinity = true)
|
400
|
+
min = value_range.minimum_bound
|
401
|
+
max = value_range.maximum_bound
|
402
|
+
# Open-ended string ranges will fail in Ruby
|
403
|
+
|
404
|
+
if min = value_range.minimum_bound
|
405
|
+
min = min.value
|
406
|
+
if min.is_a_string
|
407
|
+
min_literal = min.literal.inspect.gsub(/\A"|"\Z/,"'") # Escape string characters
|
408
|
+
else
|
409
|
+
min_literal = min.literal
|
410
|
+
end
|
411
|
+
else
|
412
|
+
min_literal = infinity ? "INFINITY" : ""
|
413
|
+
end
|
414
|
+
if max = value_range.maximum_bound
|
415
|
+
max = max.value
|
416
|
+
if max.is_a_string
|
417
|
+
max_literal = max.literal.inspect.gsub(/\A"|"\Z/,"'") # Escape string characters
|
418
|
+
else
|
419
|
+
max_literal = max.literal
|
420
|
+
end
|
421
|
+
else
|
422
|
+
max_literal = infinity ? "INFINITY" : ""
|
423
|
+
end
|
424
|
+
|
425
|
+
min_literal +
|
426
|
+
(min_literal != (max&&max_literal) ? (".." + max_literal) : "")
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
class Value
|
431
|
+
def to_s
|
432
|
+
(is_a_string ? literal.inspect.gsub(/\A"|"\Z/,"'") : literal) + (unit ? " " + unit.name : "")
|
433
|
+
end
|
434
|
+
def inspect
|
435
|
+
to_s
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
368
439
|
class PresenceConstraint
|
369
440
|
def frequency
|
370
441
|
min = min_frequency
|