activefacts 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/Rakefile +2 -2
- data/bin/afgen +1 -1
- data/bin/cql +118 -27
- data/examples/CQL/Insurance.cql +2 -2
- data/examples/CQL/Metamodel.cql +3 -3
- data/examples/CQL/SchoolActivities.cql +1 -1
- data/examples/CQL/Warehousing.cql +5 -4
- data/lib/activefacts/cql.rb +1 -1
- data/lib/activefacts/cql/Language/English.treetop +2 -1
- data/lib/activefacts/cql/compiler.rb +6 -6
- data/lib/activefacts/cql/compiler/clause.rb +69 -46
- data/lib/activefacts/cql/compiler/constraint.rb +14 -14
- data/lib/activefacts/cql/compiler/entity_type.rb +24 -24
- data/lib/activefacts/cql/compiler/fact.rb +40 -27
- data/lib/activefacts/cql/compiler/fact_type.rb +16 -16
- data/lib/activefacts/cql/compiler/query.rb +12 -12
- data/lib/activefacts/cql/compiler/shared.rb +9 -0
- data/lib/activefacts/cql/compiler/value_type.rb +4 -4
- data/lib/activefacts/cql/parser.rb +9 -9
- data/lib/activefacts/generate/cql.rb +41 -20
- data/lib/activefacts/generate/helpers/oo.rb +33 -70
- data/lib/activefacts/generate/helpers/ordered.rb +61 -87
- data/lib/activefacts/generate/ruby.rb +12 -72
- data/lib/activefacts/generate/transform/surrogate.rb +13 -13
- data/lib/activefacts/input/orm.rb +72 -71
- data/lib/activefacts/persistence/columns.rb +66 -31
- data/lib/activefacts/persistence/foreignkey.rb +6 -6
- data/lib/activefacts/persistence/index.rb +12 -12
- data/lib/activefacts/persistence/object_type.rb +15 -12
- data/lib/activefacts/persistence/reference.rb +20 -18
- data/lib/activefacts/persistence/tables.rb +40 -36
- data/lib/activefacts/support.rb +69 -123
- data/lib/activefacts/version.rb +2 -2
- data/lib/activefacts/vocabulary/extensions.rb +42 -39
- data/lib/activefacts/vocabulary/metamodel.rb +11 -1
- data/lib/activefacts/vocabulary/verbaliser.rb +28 -28
- data/spec/cql/contractions_spec.rb +1 -1
- data/spec/cql/entity_type_spec.rb +1 -1
- data/spec/cql/fact_type_matching_spec.rb +3 -3
- data/spec/cql/role_matching_spec.rb +4 -4
- data/spec/cql/samples_spec.rb +2 -2
- data/spec/cql_cql_spec.rb +1 -1
- data/spec/helpers/array_matcher.rb +1 -1
- data/spec/norma_ruby_sql_spec.rb +2 -2
- data/spec/norma_tables_spec.rb +3 -2
- metadata +47 -68
@@ -13,7 +13,7 @@ module ActiveFacts
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def prepare_roles clauses = nil
|
16
|
-
|
16
|
+
trace :binding, "preparing roles" do
|
17
17
|
@context ||= CompilationContext.new(@vocabulary)
|
18
18
|
@context.bind clauses||[], @conditions, @returning
|
19
19
|
end
|
@@ -28,7 +28,7 @@ module ActiveFacts
|
|
28
28
|
|
29
29
|
# Build the query:
|
30
30
|
unless @conditions.empty? and !@returning
|
31
|
-
|
31
|
+
trace :query, "building query for derived fact type" do
|
32
32
|
@query = build_variables(@conditions.flatten)
|
33
33
|
@roles_by_binding = build_all_steps(@conditions)
|
34
34
|
@query.validate
|
@@ -43,7 +43,7 @@ module ActiveFacts
|
|
43
43
|
@conditions.each do |condition|
|
44
44
|
next if condition.is_naked_object_type
|
45
45
|
# REVISIT: Many conditions will imply a number of different steps, which need to be handled (similar to nested_clauses).
|
46
|
-
|
46
|
+
trace :projection, "matching condition fact_type #{condition.inspect}" do
|
47
47
|
fact_type = condition.match_existing_fact_type @context
|
48
48
|
raise "Unrecognised fact type #{condition.inspect} in #{self.class}" unless fact_type
|
49
49
|
end
|
@@ -131,7 +131,7 @@ module ActiveFacts
|
|
131
131
|
@existing_clauses = [first_clause]
|
132
132
|
elsif (n = @clauses.size - @existing_clauses.size) > 0
|
133
133
|
raise "Cannot extend a negated fact type" if @existing_clauses.detect {|clause| clause.certainty == false }
|
134
|
-
|
134
|
+
trace :binding, "Extending existing fact type with #{n} new readings"
|
135
135
|
end
|
136
136
|
|
137
137
|
# Now make any new readings:
|
@@ -236,7 +236,7 @@ module ActiveFacts
|
|
236
236
|
return nil if fact_types.empty? # There are no matched fact types
|
237
237
|
|
238
238
|
if @clauses.size == 1 && @existing_clauses[0].side_effects.cost != 0
|
239
|
-
|
239
|
+
trace :matching, "There's only a single clause, but it's not an exact match"
|
240
240
|
return nil
|
241
241
|
end
|
242
242
|
|
@@ -283,9 +283,9 @@ module ActiveFacts
|
|
283
283
|
|
284
284
|
# We need to check uniqueness constraints after processing the whole vocabulary
|
285
285
|
# raise "Fact type must be named as it has no identifying uniqueness constraint" unless @name || @fact_type.all_role.size == 1
|
286
|
-
|
286
|
+
trace :constraint, "Need to check #{@fact_type.default_reading.inspect} for a uniqueness constraint"
|
287
287
|
fact_type.check_and_add_spanning_uniqueness_constraint = proc do
|
288
|
-
|
288
|
+
trace :constraint, "Checking #{@fact_type.default_reading.inspect} for a uniqueness constraint"
|
289
289
|
existing_pc = nil
|
290
290
|
found = @fact_type.all_role.
|
291
291
|
detect do |role|
|
@@ -312,9 +312,9 @@ module ActiveFacts
|
|
312
312
|
:max_frequency => 1,
|
313
313
|
:is_preferred_identifier => true # (prefer || !!@fact_type.entity_type)
|
314
314
|
)
|
315
|
-
|
315
|
+
trace :constraint, "Made new fact type implicit PC GUID=#{pc.concept.guid} #{pc.name} min=nil max=1 over #{rs.describe}"
|
316
316
|
elsif pc
|
317
|
-
|
317
|
+
trace :constraint, "Will rely on existing UC GUID=#{pc.concept.guid} #{pc.name} to be used as PI over #{rs.describe}"
|
318
318
|
end
|
319
319
|
end
|
320
320
|
end
|
@@ -349,7 +349,7 @@ module ActiveFacts
|
|
349
349
|
clauses_m = clauses_by_refs[variants[m]]
|
350
350
|
l_keys = variants[l]-common
|
351
351
|
m_keys = variants[m]-common
|
352
|
-
|
352
|
+
trace :binding, "Try to collapse variant #{m} onto #{l}; diffs are #{l_keys.inspect} -> #{m_keys.inspect}"
|
353
353
|
rebindings = 0
|
354
354
|
l_keys.each_with_index do |l_key, i|
|
355
355
|
# Find possible rebinding candidates; there must be exactly one.
|
@@ -358,19 +358,19 @@ module ActiveFacts
|
|
358
358
|
m_key = m_keys[j]
|
359
359
|
l_ref = refs_by_clause_and_key[[clauses_l[0], l_key]]
|
360
360
|
m_ref = refs_by_clause_and_key[[clauses_m[0], m_key]]
|
361
|
-
|
361
|
+
trace :binding, "Can we match #{l_ref.inspect} (#{i}) with #{m_ref.inspect} (#{j})?"
|
362
362
|
next if m_ref.player != l_ref.player
|
363
363
|
if has_more_adjectives(m_ref, l_ref)
|
364
|
-
|
364
|
+
trace :binding, "can rebind #{m_ref.inspect} to #{l_ref.inspect}"
|
365
365
|
candidates << [m_ref, l_ref]
|
366
366
|
elsif has_more_adjectives(l_ref, m_ref)
|
367
|
-
|
367
|
+
trace :binding, "can rebind #{l_ref.inspect} to #{m_ref.inspect}"
|
368
368
|
candidates << [l_ref, m_ref]
|
369
369
|
end
|
370
370
|
end
|
371
371
|
|
372
|
-
#
|
373
|
-
|
372
|
+
# trace :binding, "found #{candidates.size} rebinding candidates for this role"
|
373
|
+
trace :binding, "rebinding is ambiguous so not attempted" if candidates.size > 1
|
374
374
|
if (candidates.size == 1)
|
375
375
|
candidates[0][0].rebind_to(@context, candidates[0][1])
|
376
376
|
rebindings += 1
|
@@ -379,7 +379,7 @@ module ActiveFacts
|
|
379
379
|
end
|
380
380
|
if (rebindings == l_keys.size)
|
381
381
|
# Successfully rebound this fact type
|
382
|
-
|
382
|
+
trace :binding, "Successfully rebound clauses #{clauses_l.map{|r|r.inspect}*'; '} on to #{clauses_m.map{|r|r.inspect}*'; '}"
|
383
383
|
break
|
384
384
|
else
|
385
385
|
# No point continuing, we failed on this one.
|
@@ -4,11 +4,11 @@ module ActiveFacts
|
|
4
4
|
class Definition
|
5
5
|
# Make a Variable for every binding present in these clauses
|
6
6
|
def build_variables(clauses_list)
|
7
|
-
|
7
|
+
trace :query, "Building variables" do
|
8
8
|
query = @constellation.Query(:new)
|
9
9
|
all_bindings_in_clauses(clauses_list).
|
10
10
|
each do |binding|
|
11
|
-
|
11
|
+
trace :query, "Creating variable #{query.all_variable.size} for #{binding.inspect}"
|
12
12
|
binding.variable = @constellation.Variable(query, query.all_variable.size, :object_type => binding.player)
|
13
13
|
if literal = binding.refs.detect{|r| r.literal}
|
14
14
|
if literal.kind_of?(ActiveFacts::CQL::Compiler::Reference)
|
@@ -25,7 +25,7 @@ module ActiveFacts
|
|
25
25
|
|
26
26
|
def build_all_steps(clauses_list)
|
27
27
|
roles_by_binding = {}
|
28
|
-
|
28
|
+
trace :query, "Building steps" do
|
29
29
|
clauses_list.each do |clause|
|
30
30
|
next if clause.is_naked_object_type
|
31
31
|
build_steps(clause, roles_by_binding)
|
@@ -37,7 +37,7 @@ module ActiveFacts
|
|
37
37
|
def build_steps clause, roles_by_binding = {}, objectification_node = nil
|
38
38
|
plays = []
|
39
39
|
incidental_roles = []
|
40
|
-
|
40
|
+
trace :query, "Creating Role Sequence for #{clause.inspect} with #{clause.refs.size} role refs" do
|
41
41
|
objectification_step = nil
|
42
42
|
clause.refs.each do |ref|
|
43
43
|
# These refs are the Compiler::References, which have associated Metamodel::RoleRefs,
|
@@ -62,7 +62,7 @@ module ActiveFacts
|
|
62
62
|
end
|
63
63
|
refs_count += objectification_ref_count
|
64
64
|
|
65
|
-
|
65
|
+
trace :query, "Creating Variable #{ref.inspect} (counts #{refs_count}/#{objectification_ref_count}) and objectification Step for #{ref.inspect}" do
|
66
66
|
|
67
67
|
raise "Internal error: Trying to add role of #{role.object_type.name} to variable for #{binding.variable.object_type.name}" unless binding.variable.object_type == role.object_type
|
68
68
|
play = @constellation.Play(binding.variable, role)
|
@@ -81,7 +81,7 @@ module ActiveFacts
|
|
81
81
|
# This is an implicit objectification, just the FT clause, not ET(where ...clause...)
|
82
82
|
# We need to create a Variable for this object, even though it has no References
|
83
83
|
query = binding.variable.query
|
84
|
-
|
84
|
+
trace :query, "Creating JN#{query.all_variable.size} for #{clause.fact_type.entity_type.name} in objectification"
|
85
85
|
objectification_node = @constellation.Variable(query, query.all_variable.size, :object_type => clause.fact_type.entity_type)
|
86
86
|
end
|
87
87
|
raise "Internal error: Trying to add role of #{role.link_fact_type.all_role.single.object_type.name} to variable for #{objectification_node.object_type.name}" unless objectification_node.object_type == role.link_fact_type.all_role.single.object_type
|
@@ -99,14 +99,14 @@ module ActiveFacts
|
|
99
99
|
if clause.certainty == nil
|
100
100
|
objectification_step.is_optional = true
|
101
101
|
end
|
102
|
-
|
103
|
-
|
102
|
+
trace :query, "New #{objectification_step.describe}"
|
103
|
+
trace :query, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{objectification_step.describe}" if incidental_roles.size > 0
|
104
104
|
incidental_roles.each { |jr| jr.step = objectification_step }
|
105
105
|
incidental_roles = []
|
106
106
|
plays = []
|
107
107
|
end
|
108
108
|
else
|
109
|
-
|
109
|
+
trace :query, "Creating Reference for #{ref.inspect}" do
|
110
110
|
# REVISIT: If there's an implicit subtyping step here, create it; then always raise the error here.
|
111
111
|
# I don't want to do this for now because the verbaliser will always verbalise all steps.
|
112
112
|
if binding.variable.object_type != role.object_type and
|
@@ -129,7 +129,7 @@ module ActiveFacts
|
|
129
129
|
# Each of these ImplicitFactTypes has a single phantom role played by the objectifying entity type
|
130
130
|
# One of these phantom roles is likely to be the subject of an objectification step.
|
131
131
|
ref.nested_clauses.each do |r|
|
132
|
-
|
132
|
+
trace :query, "Building objectification step for #{ref.nested_clauses.inspect}" do
|
133
133
|
build_steps r, roles_by_binding, binding.variable
|
134
134
|
end
|
135
135
|
end
|
@@ -154,8 +154,8 @@ module ActiveFacts
|
|
154
154
|
:is_disallowed => clause.certainty == false,
|
155
155
|
:is_optional => clause.certainty == nil
|
156
156
|
)
|
157
|
-
|
158
|
-
|
157
|
+
trace :query, "New #{js.describe}"
|
158
|
+
trace :query, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{js.describe}" if incidental_roles.size > 0
|
159
159
|
incidental_roles.each { |jr| jr.step = js }
|
160
160
|
end
|
161
161
|
roles_by_binding
|
@@ -29,9 +29,9 @@ module ActiveFacts
|
|
29
29
|
end
|
30
30
|
@offset = nil if @offset.to_f == 0
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
trace :units, "Defining new unit #{@singular}#{@plural ? "/"+@plural : ""}" do
|
33
|
+
trace :units, "Coefficient is #{coefficient.numerator}#{coefficient.denominator != 1 ? "/#{coefficient.denominator}" : ""} #{coefficient.is_precise ? "exactly" : "approximately"}" if coefficient
|
34
|
+
trace :units, "Offset is #{@offset}" if @offset
|
35
35
|
raise "Redefinition of unit #{@singular}" if @constellation.Unit.values.detect{|u| u.name == @singular}
|
36
36
|
raise "Redefinition of unit #{@plural}" if @constellation.Unit.values.detect{|u| u.name == @plural}
|
37
37
|
unit = @constellation.Unit(:new,
|
@@ -45,7 +45,7 @@ module ActiveFacts
|
|
45
45
|
)
|
46
46
|
@base_units.each do |base_unit, exponent|
|
47
47
|
base = @constellation.Unit.values.detect{|u| u.name == base_unit || u.plural_name == base_unit }
|
48
|
-
|
48
|
+
trace :units, "Base unit #{base_unit}^#{exponent} #{base ? "" : "(implicitly fundamental)"}"
|
49
49
|
base ||= @constellation.Unit(:new, :name => base_unit, :is_fundamental => true, :vocabulary => @vocabulary)
|
50
50
|
@constellation.Derivation(:derived_unit => unit, :base_unit => base, :exponent => exponent)
|
51
51
|
end
|
@@ -58,12 +58,12 @@ module ActiveFacts
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def object_type(name, kind)
|
61
|
-
index_name(@terms, name) &&
|
61
|
+
index_name(@terms, name) && trace(:context, "new #{kind} '#{name}'")
|
62
62
|
true
|
63
63
|
end
|
64
64
|
|
65
65
|
def reset_role_names
|
66
|
-
|
66
|
+
trace :context, "\tresetting role names #{@role_names.keys.sort*", "}" if @role_names && @role_names.size > 0
|
67
67
|
@role_names = {}
|
68
68
|
end
|
69
69
|
|
@@ -72,17 +72,17 @@ module ActiveFacts
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def new_leading_adjective_term(adj, term)
|
75
|
-
index_name(@role_names, "#{adj} #{term}", term) &&
|
75
|
+
index_name(@role_names, "#{adj} #{term}", term) && trace(:context, "new compound term '#{adj}- #{term}'")
|
76
76
|
true
|
77
77
|
end
|
78
78
|
|
79
79
|
def new_trailing_adjective_term(adj, term)
|
80
|
-
index_name(@role_names, "#{term} #{adj}", term) &&
|
80
|
+
index_name(@role_names, "#{term} #{adj}", term) && trace(:context, "new compound term '#{term} -#{adj}'")
|
81
81
|
true
|
82
82
|
end
|
83
83
|
|
84
84
|
def role_name(name)
|
85
|
-
index_name(@role_names, name) &&
|
85
|
+
index_name(@role_names, name) && trace(:context, "new role '#{name}'")
|
86
86
|
true
|
87
87
|
end
|
88
88
|
|
@@ -98,11 +98,11 @@ module ActiveFacts
|
|
98
98
|
@global_term = @term = @term_part
|
99
99
|
@context_saver.context = {:term => @term, :global_term => @global_term }
|
100
100
|
end
|
101
|
-
|
101
|
+
trace :context, "Term #{t[s] ? "is" : "starts"} '#{@term_part}'"
|
102
102
|
elsif @allowed_forward_terms.include?(@term_part)
|
103
103
|
@term = @term_part
|
104
104
|
@context_saver.context = {:term => @term, :global_term => @term }
|
105
|
-
|
105
|
+
trace :context, "Term #{s} is an allowed forward"
|
106
106
|
return true
|
107
107
|
end
|
108
108
|
t
|
@@ -119,13 +119,13 @@ module ActiveFacts
|
|
119
119
|
w = "role_name"
|
120
120
|
end
|
121
121
|
if t
|
122
|
-
|
122
|
+
trace :context, "Multi-word #{w} #{t[@term_part] ? 'ends at' : 'continues to'} #{@term_part.inspect}"
|
123
123
|
|
124
124
|
# Record the name of the full term and the underlying global term:
|
125
125
|
if t[@term_part]
|
126
126
|
@term = @term_part if t[@term_part]
|
127
127
|
@global_term = (t = t[@term_part]) == true ? @term_part : t
|
128
|
-
|
128
|
+
trace :context, "saving context #{@term}/#{@global_term}"
|
129
129
|
@context_saver.context = {:term => @term, :global_term => @global_term }
|
130
130
|
end
|
131
131
|
end
|
@@ -15,8 +15,9 @@ module ActiveFacts
|
|
15
15
|
# afgen --cql <file>.cql
|
16
16
|
class CQL < Helpers::OrderedDumper
|
17
17
|
private
|
18
|
-
def vocabulary_start
|
19
|
-
puts "vocabulary #{vocabulary.name};\n\n"
|
18
|
+
def vocabulary_start
|
19
|
+
puts "vocabulary #{@vocabulary.name};\n\n"
|
20
|
+
build_indices
|
20
21
|
end
|
21
22
|
|
22
23
|
def vocabulary_end
|
@@ -56,7 +57,7 @@ module ActiveFacts
|
|
56
57
|
end
|
57
58
|
|
58
59
|
def entity_type_dump(o)
|
59
|
-
|
60
|
+
o.ordered_dumped!
|
60
61
|
pi = o.preferred_identifier
|
61
62
|
|
62
63
|
supers = o.supertypes
|
@@ -67,7 +68,7 @@ module ActiveFacts
|
|
67
68
|
else
|
68
69
|
non_subtype_dump(o, pi)
|
69
70
|
end
|
70
|
-
|
71
|
+
pi.ordered_dumped! if pi
|
71
72
|
end
|
72
73
|
|
73
74
|
def append_ring_to_reading(reading, ring)
|
@@ -124,8 +125,8 @@ module ActiveFacts
|
|
124
125
|
end
|
125
126
|
end
|
126
127
|
end
|
127
|
-
|
128
|
-
|
128
|
+
trace :mode, "Didn't find standard forward reading" unless forward_reading
|
129
|
+
trace :mode, "Didn't find standard reverse reading" unless reverse_reading
|
129
130
|
[forward_reading, reverse_reading]
|
130
131
|
end
|
131
132
|
|
@@ -149,7 +150,7 @@ module ActiveFacts
|
|
149
150
|
return nil if entity_type.fact_type and
|
150
151
|
entity_type.fact_type.all_role.detect{|role| role.object_type == value_role.object_type }
|
151
152
|
|
152
|
-
|
153
|
+
fact_type.ordered_dumped! # We've covered this fact type
|
153
154
|
|
154
155
|
# Elide the constraints that would have been emitted on the standard readings.
|
155
156
|
# If there is a UC that's not in the standard form for a reference mode,
|
@@ -158,13 +159,13 @@ module ActiveFacts
|
|
158
159
|
fact_constraints.each do |pc|
|
159
160
|
if (pc.role_sequence.all_role_ref.size == 1 and pc.max_frequency == 1)
|
160
161
|
# It's a uniqueness constraint, and will be regenerated
|
161
|
-
|
162
|
+
pc.ordered_dumped!
|
162
163
|
end
|
163
164
|
end
|
164
165
|
|
165
166
|
# Figure out which non-standard readings exist, if any:
|
166
167
|
nonstandard_readings = fact_type.all_reading - [forward_reading, reverse_reading]
|
167
|
-
|
168
|
+
trace :mode, "--- nonstandard_readings.size now = #{nonstandard_readings.size}" if nonstandard_readings.size > 0
|
168
169
|
|
169
170
|
verbaliser = ActiveFacts::Metamodel::Verbaliser.new
|
170
171
|
|
@@ -214,7 +215,7 @@ module ActiveFacts
|
|
214
215
|
identifying_facts.each do |fact_type|
|
215
216
|
# The RoleRefs for corresponding roles across all readings are for the same player.
|
216
217
|
verbaliser.alternate_readings fact_type.all_reading
|
217
|
-
|
218
|
+
fact_type.ordered_dumped! unless fact_type.entity_type # Must dump objectification still!
|
218
219
|
end
|
219
220
|
verbaliser.create_subscripts(:rolenames)
|
220
221
|
|
@@ -304,7 +305,7 @@ module ActiveFacts
|
|
304
305
|
if reading_texts.size > 1
|
305
306
|
ambiguity =
|
306
307
|
fact_type.all_role.to_a[0].object_type.all_role.map{|r| r.fact_type}.
|
307
|
-
select{|f| f != fact_type &&
|
308
|
+
select{|f| f != fact_type && f.ordered_dumped }.
|
308
309
|
detect do |dft|
|
309
310
|
ambiguous_readings =
|
310
311
|
reading_texts & dft.all_reading.map{|r| naiive_expand(r)}
|
@@ -397,16 +398,16 @@ module ActiveFacts
|
|
397
398
|
|
398
399
|
# Tell the verbaliser all we know, so it can figure out which players to subscript:
|
399
400
|
players = []
|
400
|
-
|
401
|
+
trace :subscript, "Preparing query across projected roles in set comparison constraint" do
|
401
402
|
transposed_role_refs.each do |role_refs|
|
402
403
|
verbaliser.role_refs_have_subtype_steps role_refs
|
403
404
|
join_over, = ActiveFacts::Metamodel.plays_over(role_refs.map{|rr| rr.role})
|
404
405
|
players << join_over
|
405
406
|
end
|
406
407
|
end
|
407
|
-
|
408
|
+
trace :subscript, "Preparing query between roles in set comparison constraint" do
|
408
409
|
role_sequences.each do |role_sequence|
|
409
|
-
|
410
|
+
trace :subscript, "role sequence is #{role_sequence.describe}" do
|
410
411
|
verbaliser.prepare_role_sequence role_sequence
|
411
412
|
end
|
412
413
|
end
|
@@ -559,8 +560,8 @@ module ActiveFacts
|
|
559
560
|
value_constraints = []
|
560
561
|
roles.each do |role|
|
561
562
|
value_constraints <<
|
562
|
-
if vc = role.role_value_constraint and
|
563
|
-
|
563
|
+
if vc = role.role_value_constraint and !vc.ordered_dumped
|
564
|
+
vc.ordered_dumped!
|
564
565
|
vc.describe
|
565
566
|
else
|
566
567
|
nil
|
@@ -571,10 +572,10 @@ module ActiveFacts
|
|
571
572
|
constraint = fact_constraints.
|
572
573
|
detect do |c| # Find a UC that spans all other Roles
|
573
574
|
c.is_a?(ActiveFacts::Metamodel::PresenceConstraint) &&
|
574
|
-
|
575
|
+
!c.ordered_dumped && # Already verbalised
|
575
576
|
roles-c.role_sequence.all_role_ref.map(&:role) == [role]
|
576
577
|
end
|
577
|
-
|
578
|
+
constraint.ordered_dumped! if constraint
|
578
579
|
constraint && constraint.frequency
|
579
580
|
else
|
580
581
|
nil
|
@@ -585,8 +586,8 @@ module ActiveFacts
|
|
585
586
|
expanded = "it is not the case that "+expanded if (reading.is_negative)
|
586
587
|
|
587
588
|
if (ft_rings = @ring_constraints_by_fact[reading.fact_type]) &&
|
588
|
-
(ring = ft_rings.detect{|rc|
|
589
|
-
|
589
|
+
(ring = ft_rings.detect{|rc| !rc.ordered_dumped})
|
590
|
+
ring.ordered_dumped!
|
590
591
|
append_ring_to_reading(expanded, ring)
|
591
592
|
end
|
592
593
|
expanded
|
@@ -609,6 +610,26 @@ module ActiveFacts
|
|
609
610
|
expanded
|
610
611
|
end
|
611
612
|
|
613
|
+
def build_indices
|
614
|
+
@presence_constraints_by_fact = Hash.new{ |h, k| h[k] = [] }
|
615
|
+
@ring_constraints_by_fact = Hash.new{ |h, k| h[k] = [] }
|
616
|
+
|
617
|
+
@vocabulary.all_constraint.each { |c|
|
618
|
+
case c
|
619
|
+
when ActiveFacts::Metamodel::PresenceConstraint
|
620
|
+
fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq # All fact types spanned by this constraint
|
621
|
+
if fact_types.size == 1 # There's only one, save it:
|
622
|
+
# trace "Single-fact constraint on #{fact_types[0].concept.guid}: #{c.name}"
|
623
|
+
(@presence_constraints_by_fact[fact_types[0]] ||= []) << c
|
624
|
+
end
|
625
|
+
when ActiveFacts::Metamodel::RingConstraint
|
626
|
+
(@ring_constraints_by_fact[c.role.fact_type] ||= []) << c
|
627
|
+
else
|
628
|
+
# trace "Found unhandled constraint #{c.class} #{c.name}"
|
629
|
+
end
|
630
|
+
}
|
631
|
+
end
|
632
|
+
|
612
633
|
end
|
613
634
|
end
|
614
635
|
|