activefacts 0.7.3 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
@@ -11,80 +11,147 @@ require 'treetop'
|
|
11
11
|
require 'activefacts/cql/LexicalRules'
|
12
12
|
require 'activefacts/cql/Language/English'
|
13
13
|
require 'activefacts/cql/Expressions'
|
14
|
+
require 'activefacts/cql/Terms'
|
14
15
|
require 'activefacts/cql/Concepts'
|
15
|
-
require 'activefacts/cql/
|
16
|
+
require 'activefacts/cql/ValueTypes'
|
16
17
|
require 'activefacts/cql/FactTypes'
|
18
|
+
require 'activefacts/cql/Context'
|
17
19
|
require 'activefacts/cql/CQLParser'
|
18
20
|
|
19
21
|
module ActiveFacts
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
22
|
+
module CQL
|
23
|
+
# Extend the generated parser:
|
24
|
+
class Parser < CQLParser
|
25
|
+
include ActiveFacts
|
26
|
+
|
27
|
+
class BlackHole
|
28
|
+
def method_missing(m, *p, &b)
|
29
|
+
self # Make all calls vanish
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class InputProxy < Object
|
34
|
+
attr_reader :context
|
35
|
+
|
36
|
+
def initialize(input, context)
|
37
|
+
@input = input
|
38
|
+
@context = context
|
39
|
+
end
|
40
|
+
|
41
|
+
def length
|
42
|
+
@input.length
|
43
|
+
end
|
44
|
+
|
45
|
+
def size
|
46
|
+
length
|
47
|
+
end
|
48
|
+
|
49
|
+
def [](*a)
|
50
|
+
@input[*a]
|
51
|
+
end
|
52
|
+
|
53
|
+
def index(*a)
|
54
|
+
@input.index(*a)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def context
|
59
|
+
@context ||= BlackHole.new
|
60
|
+
end
|
40
61
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
62
|
+
def parse(input, options = {})
|
63
|
+
input = InputProxy.new(input, context) unless input.respond_to?(:context)
|
64
|
+
super(input, options)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Repeatedly parse rule_name until all input is consumed,
|
68
|
+
# returning an array of syntax trees for each definition.
|
69
|
+
def parse_all(input, rule_name = nil, &block)
|
70
|
+
self.root = rule_name if rule_name
|
71
|
+
|
72
|
+
@index = 0 # Byte offset to start next parse
|
73
|
+
self.consume_all_input = false
|
74
|
+
results = []
|
75
|
+
begin
|
76
|
+
node = parse(InputProxy.new(input, context), :index => @index)
|
77
|
+
return nil unless node
|
78
|
+
node = block.call(node) if block
|
79
|
+
results << node if node
|
80
|
+
end until self.index == @input_length
|
81
|
+
results
|
82
|
+
end
|
83
|
+
|
84
|
+
def definition(node)
|
85
|
+
name, ast = *node.value
|
86
|
+
kind, *value = *ast
|
87
|
+
|
88
|
+
begin
|
89
|
+
debug "CQL: Processing definition #{[kind, name].compact*" "}" do
|
90
|
+
case kind
|
91
|
+
when :vocabulary
|
92
|
+
[kind, name]
|
93
|
+
when :value_type
|
94
|
+
value_type_ast(name, value)
|
95
|
+
when :entity_type
|
96
|
+
supertypes = value.shift
|
97
|
+
entity_type_ast(name, supertypes, value)
|
98
|
+
when :fact_type
|
99
|
+
f = fact_type_ast(name, value)
|
100
|
+
when :unit
|
101
|
+
ast
|
102
|
+
when :constraint
|
103
|
+
ast
|
104
|
+
else
|
105
|
+
raise "CQL: internal error, unknown definition kind"
|
106
|
+
end
|
59
107
|
end
|
60
108
|
end
|
109
|
+
rescue => e
|
110
|
+
raise "in #{kind.to_s.camelcase(true)} definition, #{e.message}:\n\t#{node.text_value}" +
|
111
|
+
(ENV['DEBUG'] =~ /\bexception\b/ ? "\nfrom\t"+e.backtrace*"\n\t" : "")
|
61
112
|
end
|
62
|
-
rescue => e
|
63
|
-
raise "in #{kind.to_s.camelcase(true)} definition, #{e.message}:\n\t#{node.text_value}"
|
64
|
-
end
|
65
113
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
114
|
+
def value_type_ast(name, value)
|
115
|
+
# REVISIT: Massage/check value type here?
|
116
|
+
[:value_type, name, *value]
|
117
|
+
end
|
70
118
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
119
|
+
def entity_type_ast(name, supertypes, value)
|
120
|
+
#print "entity_type parameters for #{name}: "; p value
|
121
|
+
identification, mapping_pragmas, clauses = *value
|
122
|
+
clauses ||= []
|
75
123
|
|
76
|
-
|
124
|
+
# raise "Entity type clauses must all be fact types" if clauses.detect{|c| c[0] != :fact_clause }
|
77
125
|
|
78
|
-
|
79
|
-
|
126
|
+
[:entity_type, name, supertypes, identification, mapping_pragmas, clauses]
|
127
|
+
end
|
128
|
+
|
129
|
+
def fact_type_ast(name, value)
|
130
|
+
clauses, conditions = value
|
131
|
+
|
132
|
+
if conditions.empty? && includes_literals(clauses)
|
133
|
+
[:fact, nil, clauses]
|
134
|
+
elsif clauses.size == 1 &&
|
135
|
+
(popname = clauses[0][2]).size == 1 &&
|
136
|
+
popname[0].keys == [:word] &&
|
137
|
+
includes_literals(conditions)
|
138
|
+
[:fact, popname[0][:word], conditions]
|
139
|
+
else
|
140
|
+
[:fact_type, name, clauses, conditions]
|
141
|
+
end
|
142
|
+
end
|
80
143
|
|
81
|
-
|
82
|
-
|
144
|
+
def includes_literals(clauses)
|
145
|
+
clauses.detect do |clause|
|
146
|
+
raise "alternate clauses are not yet supported" if clause[0] == :"||"
|
147
|
+
fc, qualifiers, phrases, context_note = *clause
|
148
|
+
phrases.detect{|w| w[:literal]}
|
149
|
+
end
|
150
|
+
end
|
83
151
|
|
84
|
-
[:fact_type, name, defined_readings, clauses]
|
85
152
|
end
|
86
153
|
|
87
154
|
end
|
88
155
|
|
89
|
-
Polyglot.register('cql',
|
156
|
+
Polyglot.register('cql', CQL::Parser)
|
90
157
|
end
|
@@ -31,33 +31,22 @@ module ActiveFacts
|
|
31
31
|
|
32
32
|
def value_type_dump(o)
|
33
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
|
+
|
34
37
|
if o.name == o.supertype.name
|
35
|
-
# In ActiveFacts, parameterising a ValueType will create a new
|
36
|
-
# throw Can't handle parameterized value type of same name as its
|
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 ...
|
37
40
|
end
|
38
41
|
|
39
42
|
parameters =
|
40
43
|
[ o.length != 0 || o.scale != 0 ? o.length : nil,
|
41
44
|
o.scale != 0 ? o.scale : nil
|
42
45
|
].compact
|
43
|
-
parameters = parameters.length > 0 ? "("+parameters.join(",")+")" : "
|
44
|
-
|
45
|
-
#" restricted to {#{(allowed_values.map{|r| r.inspect}*", ").gsub('"',"'")}}")
|
46
|
+
parameters = parameters.length > 0 ? "("+parameters.join(",")+")" : ""
|
46
47
|
|
47
48
|
puts "#{o.name} is written as #{o.supertype.name}#{ parameters }#{
|
48
|
-
o.value_restriction
|
49
|
-
o.value_restriction.all_allowed_range.sort_by{|ar|
|
50
|
-
((min = ar.value_range.minimum_bound) && min.value) ||
|
51
|
-
((max = ar.value_range.maximum_bound) && max.value)
|
52
|
-
}.map{|ar|
|
53
|
-
# REVISIT: Need to display as string or numeric according to type here...
|
54
|
-
min = ar.value_range.minimum_bound
|
55
|
-
max = ar.value_range.maximum_bound
|
56
|
-
|
57
|
-
(min ? min.value : "") +
|
58
|
-
(min.value != (max&&max.value) ? (".." + (max ? max.value : "")) : "")
|
59
|
-
}*", "
|
60
|
-
}}" : ""
|
49
|
+
o.value_restriction && " "+o.value_restriction.describe
|
61
50
|
};"
|
62
51
|
end
|
63
52
|
|
@@ -65,6 +54,18 @@ module ActiveFacts
|
|
65
54
|
reading << " [#{(ring.ring_type.scan(/[A-Z][a-z]*/)*", ").downcase}]"
|
66
55
|
end
|
67
56
|
|
57
|
+
def mapping_pragma(entity_type)
|
58
|
+
ti = entity_type.all_type_inheritance_as_subtype
|
59
|
+
assimilation = ti.map{|t| t.assimilation }.compact[0]
|
60
|
+
return "" unless entity_type.is_independent || assimilation
|
61
|
+
" [" +
|
62
|
+
[
|
63
|
+
entity_type.is_independent ? "independent" : nil,
|
64
|
+
assimilation || nil
|
65
|
+
].compact*", " +
|
66
|
+
"]"
|
67
|
+
end
|
68
|
+
|
68
69
|
def identified_by_roles_and_facts(entity_type, identifying_roles, identifying_facts, preferred_readings)
|
69
70
|
identifying_role_names = identifying_roles.map{|role|
|
70
71
|
preferred_role_ref = preferred_readings[role.fact_type].role_sequence.all_role_ref.detect{|reading_rr|
|
@@ -94,13 +95,16 @@ module ActiveFacts
|
|
94
95
|
# Just beware that readings having the same players will be considered to be of the same fact type, even if they're not.
|
95
96
|
|
96
97
|
# Detect standard reference-mode scenarios
|
97
|
-
|
98
|
+
external_identifying_facts = identifying_facts - [entity_type.fact_type]
|
99
|
+
ft = external_identifying_facts[0]
|
98
100
|
fact_constraints = nil
|
99
|
-
ftr = ft.all_role.sort_by{|role| role.ordinal}
|
100
|
-
if
|
101
|
+
ftr = ft && ft.all_role.sort_by{|role| role.ordinal}
|
102
|
+
if external_identifying_facts.size == 1 and
|
101
103
|
entity_role = ftr[n = (ftr[0].concept == entity_type ? 0 : 1)] and
|
102
104
|
value_role = ftr[1-n] and
|
103
|
-
|
105
|
+
value_player = value_role.concept and
|
106
|
+
value_player.is_a?(ActiveFacts::Metamodel::ValueType) and
|
107
|
+
value_name = value_player.name and
|
104
108
|
residual = value_name.gsub(%r{#{entity_role.concept.name}},'') and
|
105
109
|
residual != '' and
|
106
110
|
residual != value_name
|
@@ -111,13 +115,13 @@ module ActiveFacts
|
|
111
115
|
# Detect standard reference-mode readings:
|
112
116
|
forward_reading = reverse_reading = nil
|
113
117
|
ft.all_reading.each do |reading|
|
114
|
-
if reading.
|
118
|
+
if reading.text =~ /^\{(\d)\} has \{\d\}$/
|
115
119
|
if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == entity_role
|
116
120
|
forward_reading = reading
|
117
121
|
else
|
118
122
|
reverse_reading = reading
|
119
123
|
end
|
120
|
-
elsif reading.
|
124
|
+
elsif reading.text =~ /^\{(\d)\} is of \{\d\}$/
|
121
125
|
if reading.role_sequence.all_role_ref.detect{|rr| rr.ordinal == $1.to_i}.role == value_role
|
122
126
|
reverse_reading = reading
|
123
127
|
else
|
@@ -141,6 +145,7 @@ module ActiveFacts
|
|
141
145
|
@constraints_used[pc] = true
|
142
146
|
end
|
143
147
|
end
|
148
|
+
fact_constraints += Array(@presence_constraints_by_fact[entity_type.fact_type])
|
144
149
|
|
145
150
|
@fact_types_dumped[ft] = true
|
146
151
|
|
@@ -148,10 +153,19 @@ module ActiveFacts
|
|
148
153
|
other_readings = ft.all_reading - [forward_reading] - [reverse_reading]
|
149
154
|
debug :mode, "--- other_readings.size now = #{other_readings.size}" if other_readings.size > 0
|
150
155
|
|
151
|
-
fact_text =
|
152
|
-
|
153
|
-
|
154
|
-
|
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)}" +
|
155
169
|
(fact_text != "" ? " where\n\t" + fact_text : "")
|
156
170
|
end
|
157
171
|
end
|
@@ -163,6 +177,7 @@ module ActiveFacts
|
|
163
177
|
}.flatten*",\n\t"
|
164
178
|
|
165
179
|
" identified by #{ identifying_role_names*" and " }" +
|
180
|
+
mapping_pragma(entity_type) +
|
166
181
|
" where\n\t"+@identifying_fact_text
|
167
182
|
end
|
168
183
|
|
@@ -183,7 +198,10 @@ module ActiveFacts
|
|
183
198
|
print "#{o.name} is a kind of #{ o.supertypes.map(&:name)*", " }"
|
184
199
|
if pi
|
185
200
|
print identified_by(o, pi)
|
201
|
+
else
|
202
|
+
print mapping_pragma(o)
|
186
203
|
end
|
204
|
+
|
187
205
|
# If there's a preferred_identifier for this subtype, identifying readings were emitted
|
188
206
|
print((pi ? "," : " where") + "\n\t" + fact_readings(o.fact_type)) if o.fact_type
|
189
207
|
puts ";\n"
|
@@ -191,7 +209,7 @@ module ActiveFacts
|
|
191
209
|
|
192
210
|
def non_subtype_dump(o, pi)
|
193
211
|
print "#{o.name} is" + identified_by(o, pi)
|
194
|
-
print(" where\n\t"+ fact_readings(o.fact_type)) if o.fact_type
|
212
|
+
# print(" where\n\t"+ fact_readings(o.fact_type)) if o.fact_type
|
195
213
|
puts ";\n"
|
196
214
|
end
|
197
215
|
|
@@ -209,6 +227,7 @@ module ActiveFacts
|
|
209
227
|
pi = fact_type.entity_type.preferred_identifier
|
210
228
|
if pi && primary_supertype && primary_supertype.preferred_identifier != pi
|
211
229
|
print identified_by(o, pi)
|
230
|
+
# REVISIT: This *has* to be wrong. When you fix it, remember mapping_pragmas!
|
212
231
|
print ";\n"
|
213
232
|
end
|
214
233
|
end
|
@@ -244,55 +263,25 @@ module ActiveFacts
|
|
244
263
|
end
|
245
264
|
|
246
265
|
def dump_presence_constraint(c)
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
=
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
else
|
267
|
-
counterparts = roles.map{|r|
|
268
|
-
r.fact_type.all_role[r.fact_type.all_role[0] != r ? 0 : -1]
|
269
|
-
}
|
270
|
-
player = roleplayer_subclass(counterparts)
|
271
|
-
"#{c.frequency} #{player ? player.name : "UNKNOWN" } exists for each "
|
272
|
-
end +
|
273
|
-
"#{
|
274
|
-
c.role_sequence.all_role_ref.map{|rr|
|
275
|
-
"'#{rr.role.fact_type.default_reading([], nil)}'"
|
276
|
-
}*", "
|
277
|
-
}"
|
278
|
-
=end
|
279
|
-
|
280
|
-
=begin
|
281
|
-
puts \
|
282
|
-
"FOR each #{players*", "}" +
|
283
|
-
(c.role_sequence.all_role_ref.size > 1 ? " "+c.frequency+" of these holds" : "") + "\n\t"+
|
284
|
-
"#{c.role_sequence.all_role_ref.map{|rr|
|
285
|
-
role = rr.role
|
286
|
-
fact_type = role.fact_type
|
287
|
-
some_that = Array.new(fact_type.all_role.size, "some")
|
288
|
-
c.role_sequence.all_role_ref.each{|rr2|
|
289
|
-
next if rr2.role.fact_type != fact_type
|
290
|
-
some_that[fact_type.all_role.index(role)] = "that"
|
291
|
-
}
|
292
|
-
rr.role.fact_type.default_reading(some_that, nil)
|
293
|
-
}*",\n\t"}" +
|
294
|
-
";"
|
295
|
-
=end
|
266
|
+
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
|
+
};"
|
274
|
+
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
|
+
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
|
280
|
+
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
|
+
";"
|
284
|
+
end
|
296
285
|
end
|
297
286
|
|
298
287
|
# Find the common supertype of these concepts.
|
@@ -331,11 +320,13 @@ module ActiveFacts
|
|
331
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}*", "+")"}*", "}"
|
332
321
|
|
333
322
|
players_differ = [] # Record which players are also played by subclasses
|
334
|
-
players = (0...player_count).map do |
|
335
|
-
# Find the common supertype of the players of the
|
336
|
-
concepts = scrs.map
|
337
|
-
|
338
|
-
|
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
|
327
|
+
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
|
339
330
|
player
|
340
331
|
end
|
341
332
|
#puts "#{c.class.basename} has players #{players.map{|p| p.name}*", "}"
|
@@ -349,22 +340,42 @@ module ActiveFacts
|
|
349
340
|
return
|
350
341
|
end
|
351
342
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
343
|
+
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;"
|
361
|
+
else
|
362
|
+
mode = c.is_mandatory ? "exactly one" : "at most one"
|
363
|
+
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
|
+
)+';'
|
378
|
+
end
|
368
379
|
end
|
369
380
|
|
370
381
|
# Expand this reading using (in)definite articles where needed
|
@@ -386,7 +397,7 @@ module ActiveFacts
|
|
386
397
|
}
|
387
398
|
frequency_constraints = [] unless frequency_constraints.detect{|fc| fc[0] != "some" }
|
388
399
|
|
389
|
-
#$stderr.puts "fact_type roles (#{fact_type.all_role.map{|r| r.concept.name}*","}) default_reading '#{fact_type.preferred_reading.
|
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}"
|
390
401
|
|
391
402
|
# REVISIT: Make sure that we refer to the constrained players by their common supertype
|
392
403
|
|