activefacts 0.8.6 → 0.8.8
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +33 -2
- data/README.rdoc +30 -36
- data/Rakefile +16 -20
- data/bin/afgen +17 -11
- data/bin/cql +313 -36
- data/download.html +43 -19
- data/examples/CQL/Address.cql +15 -15
- data/examples/CQL/Blog.cql +8 -8
- data/examples/CQL/CompanyDirectorEmployee.cql +6 -5
- data/examples/CQL/Death.cql +3 -3
- data/examples/CQL/Diplomacy.cql +48 -0
- data/examples/CQL/Genealogy.cql +41 -41
- data/examples/CQL/Insurance.cql +311 -0
- data/examples/CQL/JoinEquality.cql +35 -0
- data/examples/CQL/Marriage.cql +1 -1
- data/examples/CQL/Metamodel.cql +290 -185
- data/examples/CQL/MetamodelNext.cql +420 -0
- data/examples/CQL/Monogamy.cql +24 -0
- data/examples/CQL/MonthInSeason.cql +27 -0
- data/examples/CQL/Moon.cql +23 -0
- data/examples/CQL/MultiInheritance.cql +4 -4
- data/examples/CQL/NonRoleId.cql +14 -0
- data/examples/CQL/OddIdentifier.cql +18 -0
- data/examples/CQL/OilSupply.cql +24 -24
- data/examples/CQL/OneToOnes.cql +17 -0
- data/examples/CQL/Orienteering.cql +55 -55
- data/examples/CQL/OrienteeringER.cql +58 -0
- data/examples/CQL/PersonPlaysGame.cql +2 -2
- data/examples/CQL/RedundantDependency.cql +34 -0
- data/examples/CQL/SchoolActivities.cql +5 -5
- data/examples/CQL/SeparateSubtype.cql +28 -0
- data/examples/CQL/ServiceDirector.cql +283 -0
- data/examples/CQL/SimplestUnary.cql +2 -2
- data/examples/CQL/SubtypePI.cql +11 -11
- data/examples/CQL/Supervision.cql +38 -0
- data/examples/CQL/Tests.Test5.Load.cql +38 -0
- data/examples/CQL/WaiterTips.cql +33 -0
- data/examples/CQL/Warehousing.cql +55 -53
- data/examples/CQL/WindowInRoomInBldg.cql +9 -9
- data/examples/CQL/unit.cql +433 -544
- data/examples/index.html +314 -170
- data/examples/intro.html +6 -176
- data/examples/local.css +8 -4
- data/index.html +40 -25
- data/lib/activefacts/api/concept.rb +2 -2
- data/lib/activefacts/api/constellation.rb +4 -4
- data/lib/activefacts/api/instance.rb +2 -2
- data/lib/activefacts/api/instance_index.rb +4 -0
- data/lib/activefacts/api/numeric.rb +3 -1
- data/lib/activefacts/api/role.rb +1 -1
- data/lib/activefacts/api/standard_types.rb +23 -16
- data/lib/activefacts/api/support.rb +3 -1
- data/lib/activefacts/api/vocabulary.rb +4 -0
- data/lib/activefacts/cql/CQLParser.treetop +87 -39
- data/lib/activefacts/cql/Concepts.treetop +95 -69
- data/lib/activefacts/cql/Context.treetop +11 -2
- data/lib/activefacts/cql/Expressions.treetop +23 -59
- data/lib/activefacts/cql/FactTypes.treetop +141 -95
- data/lib/activefacts/cql/Language/English.treetop +33 -21
- data/lib/activefacts/cql/LexicalRules.treetop +6 -1
- data/lib/activefacts/cql/Terms.treetop +75 -26
- data/lib/activefacts/cql/ValueTypes.treetop +52 -54
- data/lib/activefacts/cql/compiler.rb +46 -1691
- data/lib/activefacts/cql/compiler/constraint.rb +602 -0
- data/lib/activefacts/cql/compiler/entity_type.rb +425 -0
- data/lib/activefacts/cql/compiler/fact.rb +300 -0
- data/lib/activefacts/cql/compiler/fact_type.rb +230 -0
- data/lib/activefacts/cql/compiler/reading.rb +832 -0
- data/lib/activefacts/cql/compiler/shared.rb +109 -0
- data/lib/activefacts/cql/compiler/value_type.rb +104 -0
- data/lib/activefacts/cql/parser.rb +132 -81
- data/lib/activefacts/generate/cql.rb +397 -274
- data/lib/activefacts/generate/oo.rb +13 -12
- data/lib/activefacts/generate/ordered.rb +107 -117
- data/lib/activefacts/generate/ruby.rb +34 -38
- data/lib/activefacts/generate/sql/mysql.rb +62 -45
- data/lib/activefacts/generate/sql/server.rb +59 -42
- data/lib/activefacts/input/cql.rb +6 -3
- data/lib/activefacts/input/orm.rb +991 -557
- data/lib/activefacts/persistence/columns.rb +16 -12
- data/lib/activefacts/persistence/foreignkey.rb +7 -4
- data/lib/activefacts/persistence/index.rb +3 -4
- data/lib/activefacts/persistence/reference.rb +5 -2
- data/lib/activefacts/support.rb +20 -14
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary.rb +1 -0
- data/lib/activefacts/vocabulary/extensions.rb +328 -44
- data/lib/activefacts/vocabulary/metamodel.rb +145 -20
- data/lib/activefacts/vocabulary/verbaliser.rb +621 -0
- data/spec/absorption_spec.rb +4 -4
- data/spec/api/value_type.rb +1 -1
- data/spec/cql/context_spec.rb +45 -22
- data/spec/cql/deontic_spec.rb +88 -0
- data/spec/cql/matching_spec.rb +517 -0
- data/spec/cql/samples_spec.rb +88 -31
- data/spec/cql/unit_spec.rb +58 -37
- data/spec/cql_cql_spec.rb +12 -7
- data/spec/cql_mysql_spec.rb +3 -7
- data/spec/cql_parse_spec.rb +0 -4
- data/spec/cql_ruby_spec.rb +1 -4
- data/spec/cql_sql_spec.rb +5 -18
- data/spec/cql_symbol_tables_spec.rb +3 -0
- data/spec/cqldump_spec.rb +0 -2
- data/spec/helpers/array_matcher.rb +35 -0
- data/spec/helpers/ctrl_c_support.rb +52 -0
- data/spec/helpers/diff_matcher.rb +38 -0
- data/spec/helpers/file_matcher.rb +5 -3
- data/spec/helpers/string_matcher.rb +39 -0
- data/spec/helpers/test_parser.rb +13 -0
- data/spec/norma_cql_spec.rb +13 -5
- data/spec/norma_ruby_spec.rb +11 -3
- data/spec/{absorption_ruby_spec.rb → norma_ruby_sql_spec.rb} +37 -32
- data/spec/norma_sql_spec.rb +11 -5
- data/spec/norma_tables_spec.rb +33 -29
- data/spec/spec_helper.rb +4 -1
- data/status.html +92 -23
- metadata +102 -36
- 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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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.
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
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
|
-
|
95
|
-
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
174
|
-
|
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(
|
263
|
+
fact_readings_with_constraints(verbaliser, f)
|
177
264
|
}.flatten*",\n\t"
|
178
265
|
|
179
|
-
" identified by #{
|
266
|
+
" identified by #{ irn*" and " }" +
|
180
267
|
mapping_pragma(entity_type) +
|
181
|
-
" where\n\t"
|
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
|
-
|
201
|
-
|
202
|
-
print mapping_pragma(o)
|
282
|
+
puts identified_by(o, pi)+';'
|
283
|
+
return
|
203
284
|
end
|
204
285
|
|
205
|
-
|
206
|
-
|
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
|
-
|
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
|
-
|
222
|
-
|
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 =
|
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
|
-
|
230
|
-
|
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
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
-
|
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
|
279
|
-
|
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
|
-
"#{
|
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
|
-
|
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
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
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
|
-
|
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
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
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
|
-
|
329
|
-
|
330
|
-
|
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
|
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
|
-
|
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.
|
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.
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
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
|
-
|
475
|
+
verbaliser.verbalise_over_role_sequence(c.subset_role_sequence) +
|
436
476
|
"\n\tonly if " +
|
437
|
-
|
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
|
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
|
-
|
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
|