activefacts 0.8.16 → 0.8.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Manifest.txt +10 -4
- data/bin/afgen +26 -20
- data/bin/cql +1 -1
- data/css/orm2.css +89 -9
- data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
- data/examples/CQL/Genealogy.cql +5 -5
- data/examples/CQL/Metamodel.cql +121 -91
- data/examples/CQL/MonthInSeason.cql +2 -6
- data/examples/CQL/SeparateSubtype.cql +11 -9
- data/examples/CQL/ServiceDirector.cql +21 -33
- data/examples/CQL/Supervision.cql +0 -3
- data/examples/CQL/WindowInRoomInBldg.cql +10 -4
- data/examples/CQL/unit.cql +1 -1
- data/lib/activefacts.rb +1 -0
- data/lib/activefacts/cql/CQLParser.treetop +5 -1
- data/lib/activefacts/cql/Context.treetop +2 -7
- data/lib/activefacts/cql/Expressions.treetop +2 -2
- data/lib/activefacts/cql/FactTypes.treetop +37 -31
- data/lib/activefacts/cql/Language/English.treetop +21 -4
- data/lib/activefacts/cql/LexicalRules.treetop +59 -1
- data/lib/activefacts/cql/ObjectTypes.treetop +22 -12
- data/lib/activefacts/cql/Terms.treetop +13 -9
- data/lib/activefacts/cql/ValueTypes.treetop +30 -11
- data/lib/activefacts/cql/compiler.rb +34 -5
- data/lib/activefacts/cql/compiler/clause.rb +207 -116
- data/lib/activefacts/cql/compiler/constraint.rb +129 -105
- data/lib/activefacts/cql/compiler/entity_type.rb +49 -27
- data/lib/activefacts/cql/compiler/expression.rb +71 -42
- data/lib/activefacts/cql/compiler/fact.rb +70 -64
- data/lib/activefacts/cql/compiler/fact_type.rb +108 -57
- data/lib/activefacts/cql/compiler/query.rb +178 -0
- data/lib/activefacts/cql/compiler/shared.rb +13 -12
- data/lib/activefacts/cql/compiler/value_type.rb +10 -4
- data/lib/activefacts/cql/nodes.rb +1 -1
- data/lib/activefacts/cql/parser.rb +6 -2
- data/lib/activefacts/generate/absorption.rb +6 -3
- data/lib/activefacts/generate/cql.rb +140 -84
- data/lib/activefacts/generate/dm.rb +12 -6
- data/lib/activefacts/generate/help.rb +25 -6
- data/lib/activefacts/generate/helpers/oo.rb +195 -0
- data/lib/activefacts/generate/helpers/ordered.rb +589 -0
- data/lib/activefacts/generate/helpers/rails.rb +57 -0
- data/lib/activefacts/generate/html/glossary.rb +274 -54
- data/lib/activefacts/generate/json.rb +25 -22
- data/lib/activefacts/generate/null.rb +1 -0
- data/lib/activefacts/generate/rails/models.rb +244 -0
- data/lib/activefacts/generate/rails/schema.rb +185 -0
- data/lib/activefacts/generate/records.rb +1 -0
- data/lib/activefacts/generate/ruby.rb +51 -30
- data/lib/activefacts/generate/sql/mysql.rb +5 -3
- data/lib/activefacts/generate/sql/server.rb +8 -4
- data/lib/activefacts/generate/text.rb +1 -0
- data/lib/activefacts/generate/transform/surrogate.rb +209 -0
- data/lib/activefacts/generate/version.rb +1 -0
- data/lib/activefacts/input/orm.rb +234 -181
- data/lib/activefacts/mapping/rails.rb +122 -0
- data/lib/activefacts/persistence/columns.rb +34 -18
- data/lib/activefacts/persistence/foreignkey.rb +129 -71
- data/lib/activefacts/persistence/index.rb +42 -12
- data/lib/activefacts/persistence/reference.rb +37 -23
- data/lib/activefacts/persistence/tables.rb +53 -19
- data/lib/activefacts/registry.rb +11 -0
- data/lib/activefacts/support.rb +28 -10
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +246 -117
- data/lib/activefacts/vocabulary/metamodel.rb +105 -65
- data/lib/activefacts/vocabulary/verbaliser.rb +226 -194
- data/spec/absorption_spec.rb +1 -0
- data/spec/cql/comparison_spec.rb +8 -8
- data/spec/cql/contractions_spec.rb +16 -43
- data/spec/cql/entity_type_spec.rb +2 -1
- data/spec/cql/expressions_spec.rb +2 -2
- data/spec/cql/fact_type_matching_spec.rb +4 -1
- data/spec/cql/parser/bad_literals_spec.rb +30 -30
- data/spec/cql/parser/entity_types_spec.rb +6 -6
- data/spec/cql/parser/expressions_spec.rb +25 -19
- data/spec/cql/samples_spec.rb +5 -4
- data/spec/cql_cql_spec.rb +2 -1
- data/spec/cql_dm_spec.rb +4 -0
- data/spec/cql_mysql_spec.rb +4 -0
- data/spec/cql_parse_spec.rb +2 -0
- data/spec/cql_ruby_spec.rb +4 -0
- data/spec/cql_sql_spec.rb +4 -0
- data/spec/cqldump_spec.rb +7 -4
- data/spec/helpers/parse_to_ast_matcher.rb +7 -3
- data/spec/helpers/test_parser.rb +2 -0
- data/spec/norma_cql_spec.rb +5 -2
- data/spec/norma_ruby_spec.rb +4 -1
- data/spec/norma_ruby_sql_spec.rb +4 -1
- data/spec/norma_sql_spec.rb +4 -1
- data/spec/norma_tables_spec.rb +2 -2
- data/spec/ruby_api_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/transform_surrogate_spec.rb +59 -0
- metadata +70 -60
- data/TODO +0 -308
- data/lib/activefacts/cql/compiler/join.rb +0 -162
- data/lib/activefacts/generate/oo.rb +0 -176
- data/lib/activefacts/generate/ordered.rb +0 -602
@@ -5,21 +5,23 @@ module ActiveFacts
|
|
5
5
|
class Clause
|
6
6
|
attr_reader :phrases
|
7
7
|
attr_accessor :qualifiers, :context_note
|
8
|
+
attr_accessor :certainty # nil, true, false -> maybe, definitely, not
|
8
9
|
attr_accessor :conjunction # one of {nil, 'and', ',', 'or', 'where'}
|
9
10
|
attr_accessor :fact_type
|
10
11
|
attr_reader :reading, :role_sequence # These are the Metamodel objects
|
11
12
|
attr_reader :side_effects # How to adjust the phrases if this fact_type match is accepted
|
12
13
|
attr_accessor :fact # When binding fact instances the fact goes here
|
13
|
-
attr_accessor :objectified_as # The
|
14
|
+
attr_accessor :objectified_as # The Reference which objectified this fact type
|
14
15
|
|
15
16
|
def initialize phrases, qualifiers = [], context_note = nil
|
16
17
|
@phrases = phrases
|
17
|
-
|
18
|
+
refs.each { |ref| ref.clause = self }
|
19
|
+
@certainty = true
|
18
20
|
@qualifiers = qualifiers
|
19
21
|
@context_note = context_note
|
20
22
|
end
|
21
23
|
|
22
|
-
def
|
24
|
+
def refs
|
23
25
|
@phrases.select{|r| r.respond_to?(:player)}
|
24
26
|
end
|
25
27
|
|
@@ -27,7 +29,7 @@ module ActiveFacts
|
|
27
29
|
# refers only to the existence of that ObjectType (as opposed to an instance of the object_type).
|
28
30
|
def is_existential_type
|
29
31
|
@phrases.size == 1 and
|
30
|
-
@phrases[0].is_a?(
|
32
|
+
@phrases[0].is_a?(Reference) and
|
31
33
|
!@phrases[0].literal
|
32
34
|
end
|
33
35
|
|
@@ -35,22 +37,29 @@ module ActiveFacts
|
|
35
37
|
to_s
|
36
38
|
end
|
37
39
|
|
38
|
-
def inspect
|
39
|
-
to_s
|
40
|
+
def inspect phrases = nil
|
41
|
+
to_s(phrases||@phrases)
|
40
42
|
end
|
41
43
|
|
42
|
-
def to_s
|
44
|
+
def to_s phrases = nil
|
45
|
+
phrases ||= @phrases
|
43
46
|
"#{
|
44
47
|
@qualifiers && @qualifiers.size > 0 ? @qualifiers.sort.inspect+' ' : nil
|
48
|
+
}#{
|
49
|
+
case @certainty
|
50
|
+
when nil; 'maybe '
|
51
|
+
when false; 'negated '
|
52
|
+
# else 'definitely '
|
53
|
+
end
|
45
54
|
}#{
|
46
55
|
quotes = false
|
47
|
-
|
56
|
+
phrases.inject(""){|s, p|
|
48
57
|
if String === p
|
49
58
|
s + (quotes ? '' : (quotes = true; '"')) + p.to_s + ' '
|
50
59
|
# REVISIT: Add something here when I re-add functions
|
51
60
|
# elsif FunctionCallChain === p
|
52
61
|
# s[0..-2] + (quotes ? (quotes = false; '" ') : '') + p.to_s
|
53
|
-
else # if
|
62
|
+
else # if Reference === p
|
54
63
|
s[0..-2] + (quotes ? (quotes = false; '" ') : '') + p.to_s +
|
55
64
|
((oj = p.nested_clauses) ? ' ('+ oj.map{|c| ((j=c.conjunction) ? j+' ' : '') + c.to_s}*' ' + ')' : '') +
|
56
65
|
' '
|
@@ -64,21 +73,21 @@ module ActiveFacts
|
|
64
73
|
end
|
65
74
|
|
66
75
|
def identify_players_with_role_name context
|
67
|
-
|
68
|
-
|
76
|
+
refs.each do |ref|
|
77
|
+
ref.identify_players_with_role_name(context)
|
69
78
|
end
|
70
79
|
end
|
71
80
|
|
72
81
|
def identify_other_players context
|
73
|
-
|
74
|
-
|
82
|
+
refs.each do |ref|
|
83
|
+
ref.identify_other_players(context)
|
75
84
|
# Include players in nested clauses, if any
|
76
|
-
|
85
|
+
ref.nested_clauses.each{|clause| clause.identify_other_players(context)} if ref.nested_clauses
|
77
86
|
end
|
78
87
|
end
|
79
88
|
|
80
89
|
def includes_literals
|
81
|
-
|
90
|
+
refs.detect{|ref| ref.literal || (ja = ref.nested_clauses and ja.detect{|jr| jr.includes_literals })}
|
82
91
|
end
|
83
92
|
|
84
93
|
def is_equality_comparison
|
@@ -86,7 +95,7 @@ module ActiveFacts
|
|
86
95
|
end
|
87
96
|
|
88
97
|
def bind context
|
89
|
-
role_names =
|
98
|
+
role_names = refs.map{ |ref| ref.role_name }.compact
|
90
99
|
|
91
100
|
# Check uniqueness of role names and subscripts within this clause:
|
92
101
|
role_names.each do |rn|
|
@@ -94,15 +103,15 @@ module ActiveFacts
|
|
94
103
|
raise "Duplicate role #{rn.is_a?(Integer) ? "subscript" : "name"} '#{rn}' in clause"
|
95
104
|
end
|
96
105
|
|
97
|
-
|
98
|
-
|
106
|
+
refs.each do |ref|
|
107
|
+
ref.bind context
|
99
108
|
end
|
100
109
|
end
|
101
110
|
|
102
111
|
def phrases_match(phrases)
|
103
112
|
@phrases.zip(phrases).each do |mine, theirs|
|
104
|
-
return false if mine.is_a?(
|
105
|
-
if mine.is_a?(
|
113
|
+
return false if mine.is_a?(Reference) != theirs.is_a?(Reference)
|
114
|
+
if mine.is_a?(Reference)
|
106
115
|
return false unless mine.key == theirs.key
|
107
116
|
else
|
108
117
|
return false unless mine == theirs
|
@@ -118,8 +127,9 @@ module ActiveFacts
|
|
118
127
|
# no change is made to this Clause object - those will be done later.
|
119
128
|
#
|
120
129
|
def match_existing_fact_type context, options = {}
|
130
|
+
raise "Cannot match a clause that contains no object types" if refs.size == 0
|
121
131
|
raise "Internal error, clause already matched, should not match again" if @fact_type
|
122
|
-
# If we fail to match, try
|
132
|
+
# If we fail to match, try a left contraction (or save this for a subsequent left contraction):
|
123
133
|
left_contract_this_onto = context.left_contractable_clause
|
124
134
|
new_conjunction = (conjunction == nil || conjunction == ',')
|
125
135
|
changed_conjunction = (lcc = context.left_contraction_conjunction) && lcc != conjunction
|
@@ -131,55 +141,100 @@ module ActiveFacts
|
|
131
141
|
end
|
132
142
|
context.left_contraction_conjunction = new_conjunction ? nil : @conjunction
|
133
143
|
|
134
|
-
|
144
|
+
phrases = @phrases
|
145
|
+
vrs = []+refs
|
146
|
+
|
147
|
+
# A left contraction is where the first player in the previous clause continues as first player of this clause
|
148
|
+
contracted_left = false
|
149
|
+
can_contract_right = false
|
150
|
+
left_insertion = nil
|
151
|
+
right_insertion = nil
|
152
|
+
supposed_roles = [] # Arrange to unbind incorrect references supposed due to contraction
|
153
|
+
contract_left = proc do
|
154
|
+
contracted_from = left_contract_this_onto.refs[0]
|
155
|
+
contraction_player = contracted_from.player
|
156
|
+
contracted_role = Reference.new(contraction_player.name)
|
157
|
+
supposed_roles << contracted_role
|
158
|
+
left_insertion = contracted_role.inspect+' '
|
159
|
+
contracted_role.player = contracted_from.player
|
160
|
+
contracted_role.role_name = contracted_from.role_name
|
161
|
+
contracted_role.bind(context)
|
162
|
+
vrs.unshift contracted_role
|
163
|
+
contracted_left = true
|
164
|
+
phrases = [contracted_role]+phrases
|
165
|
+
debug :matching, "Failed to match #{inspect}. Trying again using left contraction onto #{contraction_player.name}"
|
166
|
+
end
|
167
|
+
|
168
|
+
contract_right = proc do
|
169
|
+
contracted_from = left_contract_this_onto.refs[-1]
|
170
|
+
contraction_player = contracted_from.player
|
171
|
+
contracted_role = Reference.new(contraction_player.name)
|
172
|
+
supposed_roles << contracted_role
|
173
|
+
right_insertion = ' '+contracted_role.inspect
|
174
|
+
contracted_role.player = contracted_from.player
|
175
|
+
contracted_role.role_name = contracted_from.role_name
|
176
|
+
contracted_role.bind(context)
|
177
|
+
vrs.push contracted_role
|
178
|
+
phrases = phrases+[contracted_role]
|
179
|
+
debug :matching, "Failed to match #{inspect}. Trying again using right contraction onto #{contraction_player.name}"
|
180
|
+
end
|
135
181
|
|
136
|
-
vrs = []+var_refs
|
137
182
|
begin
|
138
183
|
players = vrs.map{|vr| vr.player}
|
184
|
+
|
185
|
+
if players.size == 0
|
186
|
+
can_contract_right = left_contract_this_onto.refs.size == 2
|
187
|
+
contract_left.call
|
188
|
+
redo
|
189
|
+
end
|
190
|
+
|
139
191
|
raise "Must identify players before matching fact types" if players.include? nil
|
140
192
|
raise "A fact type must involve at least one object type, but there are none in '#{inspect}'" if players.size == 0 && !left_contract_this_onto
|
141
193
|
|
142
194
|
player_names = players.map{|p| p.name}
|
143
195
|
|
144
|
-
debug :matching, "Looking for existing #{players.size}-ary fact types matching '#{
|
196
|
+
debug :matching, "Looking for existing #{players.size}-ary fact types matching '#{inspect(phrases)}'" do
|
145
197
|
debug :matching, "Players are '#{player_names.inspect}'"
|
146
198
|
|
147
|
-
# Match existing fact types in nested clauses first
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
next
|
152
|
-
|
199
|
+
# Match existing fact types in nested clauses first:
|
200
|
+
# (not for contractions) REVISIT: Why not?
|
201
|
+
if !contracted_left
|
202
|
+
vrs.each do |ref|
|
203
|
+
next if ref.is_a?(Operation)
|
204
|
+
next unless steps = ref.nested_clauses and !steps.empty?
|
205
|
+
ref.nested_clauses.each do |oj|
|
153
206
|
ft = oj.match_existing_fact_type(context)
|
154
207
|
raise "Unrecognised fact type #{oj.display}" unless ft
|
155
|
-
if (ft && ft.entity_type ==
|
156
|
-
|
157
|
-
oj.objectified_as =
|
208
|
+
if (ft && ft.entity_type == ref.player)
|
209
|
+
ref.objectification_of = ft
|
210
|
+
oj.objectified_as = ref
|
158
211
|
end
|
159
212
|
end
|
160
|
-
raise "#{
|
213
|
+
raise "#{ref.inspect} contains objectification steps that do not objectify it" unless ref.objectification_of
|
161
214
|
end
|
162
215
|
end
|
163
216
|
|
164
217
|
# For each role player, find the compatible types (the set of all subtypes and supertypes).
|
165
|
-
# For a player that's an objectification, we don't allow implicit supertype
|
218
|
+
# For a player that's an objectification, we don't allow implicit supertype steps
|
166
219
|
player_related_types =
|
167
|
-
vrs.zip(players).map do |
|
168
|
-
disallow_subtyping =
|
220
|
+
vrs.zip(players).map do |ref, player|
|
221
|
+
disallow_subtyping = ref && ref.objectification_of || options[:exact_type]
|
169
222
|
((disallow_subtyping ? [] : player.supertypes_transitive) +
|
170
223
|
player.subtypes_transitive).uniq
|
171
224
|
end
|
172
225
|
|
173
226
|
debug :matching, "Players must match '#{player_related_types.map{|pa| pa.map{|p|p.name}}.inspect}'"
|
174
227
|
|
228
|
+
start_obj = player_related_types[0] || [left_contract_this_onto.refs[-1].player]
|
175
229
|
# The candidate fact types have the right number of role players of related types.
|
176
|
-
# If any role is played by a supertype or subtype of the required type, there's an implicit subtyping
|
230
|
+
# If any role is played by a supertype or subtype of the required type, there's an implicit subtyping steps
|
231
|
+
# REVISIT: A double contraction results in player_related_types being empty here
|
177
232
|
candidate_fact_types =
|
178
|
-
|
233
|
+
start_obj.map do |related_type|
|
179
234
|
related_type.all_role.select do |role|
|
180
235
|
all_roles = role.fact_type.all_role
|
181
236
|
next if all_roles.size != players.size # Wrong number of players
|
182
|
-
next if role.fact_type.is_a?(ActiveFacts::Metamodel::
|
237
|
+
next if role.fact_type.is_a?(ActiveFacts::Metamodel::LinkFactType)
|
183
238
|
|
184
239
|
all_players = all_roles.map{|r| r.object_type} # All the players of this candidate fact type
|
185
240
|
|
@@ -202,13 +257,13 @@ module ActiveFacts
|
|
202
257
|
map{ |role| role.fact_type}
|
203
258
|
end.flatten.uniq
|
204
259
|
|
205
|
-
# If there is more than one possible exact match (same adjectives) with different subyping, the implicit
|
260
|
+
# If there is more than one possible exact match (same adjectives) with different subyping, the implicit query is ambiguous and is not allowed
|
206
261
|
|
207
|
-
debug :matching, "Looking amongst #{candidate_fact_types.size} existing fact types for one matching '#{
|
262
|
+
debug :matching, "Looking amongst #{candidate_fact_types.size} existing fact types for one matching #{left_insertion}'#{inspect}'#{right_insertion}" do
|
208
263
|
matches = {}
|
209
264
|
candidate_fact_types.map do |fact_type|
|
210
265
|
fact_type.all_reading.map do |reading|
|
211
|
-
next unless side_effects = clause_matches(fact_type, reading,
|
266
|
+
next unless side_effects = clause_matches(fact_type, reading, phrases)
|
212
267
|
matches[reading] = side_effects if side_effects
|
213
268
|
end
|
214
269
|
end
|
@@ -221,7 +276,7 @@ module ActiveFacts
|
|
221
276
|
# the whole declaration has been processed and the extra adjectives can be matched.
|
222
277
|
|
223
278
|
best_matches = matches.keys.sort_by{|match|
|
224
|
-
# Between equivalents, prefer the one without
|
279
|
+
# Between equivalents, prefer the one without steps on the first role
|
225
280
|
(m = matches[match]).cost*2 + ((!(e = m.role_side_effects[0]) || e.cost) == 0 ? 0 : 1)
|
226
281
|
}
|
227
282
|
debug :matching_fails, "Found #{matches.size} valid matches#{matches.size > 0 ? ', best is '+best_matches[0].expand : ''}"
|
@@ -243,7 +298,7 @@ module ActiveFacts
|
|
243
298
|
@side_effects = matches[@reading]
|
244
299
|
@fact_type = @side_effects.fact_type
|
245
300
|
debug :matching, "Matched '#{@fact_type.default_reading}'"
|
246
|
-
@phrases
|
301
|
+
@phrases = phrases
|
247
302
|
apply_side_effects(context, @side_effects)
|
248
303
|
return @fact_type
|
249
304
|
end
|
@@ -251,21 +306,19 @@ module ActiveFacts
|
|
251
306
|
end
|
252
307
|
debug :matching, "No fact type matched, candidates were '#{candidate_fact_types.map{|ft| ft.default_reading}*"', '"}'"
|
253
308
|
end
|
254
|
-
if left_contract_this_onto
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
debug :matching, "Failed to match #{inspect}. Trying again using left contraction onto #{contraction_player.name}"
|
264
|
-
redo
|
309
|
+
if left_contract_this_onto
|
310
|
+
if !contracted_left
|
311
|
+
contract_left.call
|
312
|
+
redo
|
313
|
+
elsif can_contract_right
|
314
|
+
contract_right.call
|
315
|
+
can_contract_right = false
|
316
|
+
redo
|
317
|
+
end
|
265
318
|
end
|
266
319
|
end until true # Once through, unless we hit a redo
|
267
|
-
|
268
|
-
|
320
|
+
supposed_roles.each do |role|
|
321
|
+
role.unbind context
|
269
322
|
end
|
270
323
|
@fact_type = nil
|
271
324
|
end
|
@@ -275,7 +328,7 @@ module ActiveFacts
|
|
275
328
|
# Find whether the phrases of this clause match the fact type reading,
|
276
329
|
# which may require absorbing unmarked adjectives.
|
277
330
|
#
|
278
|
-
# If it does match, make the required changes and set @
|
331
|
+
# If it does match, make the required changes and set @ref to the matching role ref.
|
279
332
|
# Adjectives that were used to match are removed (and leaving any additional adjectives intact).
|
280
333
|
#
|
281
334
|
# Approach:
|
@@ -288,18 +341,33 @@ module ActiveFacts
|
|
288
341
|
# trailing adjectives, both marked and unmarked, are absorbed too.
|
289
342
|
# a word that matches the reading's
|
290
343
|
#
|
291
|
-
def clause_matches(fact_type, reading,
|
344
|
+
def clause_matches(fact_type, reading, phrases = @phrases)
|
345
|
+
implicitly_negated = false
|
292
346
|
side_effects = [] # An array of items for each role, describing any side-effects of the match.
|
293
347
|
intervening_words = nil
|
294
348
|
residual_adjectives = false
|
295
|
-
|
349
|
+
|
350
|
+
# The following form of negation is, e.g., where "Person was invited to no Party",
|
351
|
+
# as opposed to where "Person was not invited to that Party". Quite different meaning,
|
352
|
+
# because a free Party variable is required, but the join step is still disallowed.
|
353
|
+
# REVISIT: I'll create the free variable when I implement some/that binding
|
354
|
+
# REVISIT: the verbaliser will need to know about a negated step to a free variable
|
355
|
+
implicitly_negated = true if refs.detect{|ref| q = ref.quantifier and q.is_zero }
|
356
|
+
|
296
357
|
debug :matching_fails, "Does '#{phrases.inspect}' match '#{reading.expand}'" do
|
297
358
|
phrase_num = 0
|
298
359
|
reading_parts = reading.text.split(/\s+/)
|
299
360
|
reading_parts.each do |element|
|
361
|
+
phrase = phrases[phrase_num]
|
362
|
+
if phrase == 'not'
|
363
|
+
raise "Stop playing games with your double negatives: #{phrases.inspect}" if implicitly_negated
|
364
|
+
debug :matching, "Negation detected"
|
365
|
+
implicitly_negated = true
|
366
|
+
phrase = phrases[phrase_num += 1]
|
367
|
+
end
|
300
368
|
if element !~ /\{(\d+)\}/
|
301
369
|
# Just a word; it must match
|
302
|
-
unless
|
370
|
+
unless phrase == element
|
303
371
|
debug :matching_fails, "Mismatched ordinary word #{phrases[phrase_num].inspect} (wanted #{element})"
|
304
372
|
return nil
|
305
373
|
end
|
@@ -337,7 +405,7 @@ module ActiveFacts
|
|
337
405
|
return nil
|
338
406
|
end
|
339
407
|
|
340
|
-
debug :matching_fails, "Subtype
|
408
|
+
debug :matching_fails, "Subtype step is required between #{player.name} and #{next_player_phrase.player.name} via common supertype #{common_supertype.name}"
|
341
409
|
else
|
342
410
|
if !next_player_phrase
|
343
411
|
next # Contraction succeeded so far
|
@@ -406,7 +474,7 @@ module ActiveFacts
|
|
406
474
|
=end
|
407
475
|
|
408
476
|
residual_adjectives ||= role_has_residual_adjectives
|
409
|
-
if residual_adjectives && next_player_phrase.
|
477
|
+
if residual_adjectives && next_player_phrase.binding.refs.size == 1
|
410
478
|
# This makes matching order-dependent, because there may be no "other purpose"
|
411
479
|
# until another reading has been matched and the roles rebound.
|
412
480
|
debug :matching_fails, "Residual adjectives have no other purpose, so this match fails"
|
@@ -423,20 +491,20 @@ module ActiveFacts
|
|
423
491
|
end
|
424
492
|
|
425
493
|
if fact_type.is_a?(Metamodel::TypeInheritance)
|
426
|
-
# There may be only one subtyping
|
427
|
-
|
428
|
-
if
|
429
|
-
debug :matching_fails, "Can't have more than one subtyping
|
494
|
+
# There may be only one subtyping step on a TypeInheritance fact type.
|
495
|
+
ti_steps = side_effects.select{|side_effect| side_effect.common_supertype}
|
496
|
+
if ti_steps.size > 1 # Not allowed
|
497
|
+
debug :matching_fails, "Can't have more than one subtyping step on a TypeInheritance fact type"
|
430
498
|
return nil
|
431
499
|
end
|
432
500
|
|
433
|
-
if ti =
|
434
|
-
# The Type Inheritance
|
501
|
+
if ti = ti_steps[0]
|
502
|
+
# The Type Inheritance step must continue in the same direction as this reading.
|
435
503
|
allowed = fact_type.supertype == ti.role_ref.role.object_type ?
|
436
504
|
fact_type.subtype.supertypes_transitive :
|
437
505
|
fact_type.supertype.subtypes_transitive
|
438
506
|
if !allowed.include?(ti.common_supertype)
|
439
|
-
debug :matching_fails, "Implicit subtyping
|
507
|
+
debug :matching_fails, "Implicit subtyping step extends in the wrong direction"
|
440
508
|
return nil
|
441
509
|
end
|
442
510
|
end
|
@@ -449,7 +517,9 @@ module ActiveFacts
|
|
449
517
|
} side effects)#{residual_adjectives ? ' and residual adjectives' : ''}"
|
450
518
|
end
|
451
519
|
# There will be one side_effects for each role player
|
452
|
-
|
520
|
+
@certainty = !@certainty if implicitly_negated
|
521
|
+
@certainty = !@certainty if reading.is_negative
|
522
|
+
ClauseMatchSideEffects.new(fact_type, self, residual_adjectives, side_effects, implicitly_negated)
|
453
523
|
end
|
454
524
|
|
455
525
|
def apply_side_effects(context, side_effects)
|
@@ -460,8 +530,8 @@ module ActiveFacts
|
|
460
530
|
side_effects.apply_all do |side_effect|
|
461
531
|
phrase = side_effect.phrase
|
462
532
|
|
463
|
-
# We re-use the role_ref if possible (no extra adjectives were used, no rolename or
|
464
|
-
debug :matching, "side-effect means
|
533
|
+
# We re-use the role_ref if possible (no extra adjectives were used, no rolename or step, etc).
|
534
|
+
debug :matching, "side-effect means binding #{phrase.inspect} matches role ref #{side_effect.role_ref.role.object_type.name}"
|
465
535
|
phrase.role_ref = side_effect.role_ref
|
466
536
|
|
467
537
|
changed = false
|
@@ -482,7 +552,7 @@ module ActiveFacts
|
|
482
552
|
end
|
483
553
|
elsif side_effect.absorbed_followers > 0
|
484
554
|
# The following statement is incorrect. The absorbed adjective is what caused the match.
|
485
|
-
# This phrase is absorbing non-hyphenated adjective(s), which changes its
|
555
|
+
# This phrase is absorbing non-hyphenated adjective(s), which changes its binding
|
486
556
|
# phrase.trailing_adjective =
|
487
557
|
@phrases.slice!(side_effect.num+1, side_effect.absorbed_followers)*' '
|
488
558
|
changed = true
|
@@ -502,7 +572,7 @@ module ActiveFacts
|
|
502
572
|
end
|
503
573
|
elsif side_effect.absorbed_precursors > 0
|
504
574
|
# The following statement is incorrect. The absorbed adjective is what caused the match.
|
505
|
-
# This phrase is absorbing non-hyphenated adjective(s), which changes its
|
575
|
+
# This phrase is absorbing non-hyphenated adjective(s), which changes its binding
|
506
576
|
#phrase.leading_adjective =
|
507
577
|
@phrases.slice!(side_effect.num-side_effect.absorbed_precursors, side_effect.absorbed_precursors)*' '
|
508
578
|
changed = true
|
@@ -540,10 +610,10 @@ module ActiveFacts
|
|
540
610
|
reading_words.map! do |phrase|
|
541
611
|
if phrase.respond_to?(:player)
|
542
612
|
# phrase.role will be set if this reading was used to make_fact_type.
|
543
|
-
# Otherwise we have to find the existing role via the
|
613
|
+
# Otherwise we have to find the existing role via the Binding. This is pretty ugly.
|
544
614
|
unless phrase.role
|
545
|
-
# Find another
|
546
|
-
ref = phrase.
|
615
|
+
# Find another binding for this phrase which already has a role_ref to the same fact type:
|
616
|
+
ref = phrase.binding.refs.detect{|ref| ref.role_ref && ref.role_ref.role.fact_type == fact_type}
|
547
617
|
role_ref = ref.role_ref
|
548
618
|
phrase.role = role_ref.role
|
549
619
|
end
|
@@ -578,7 +648,8 @@ module ActiveFacts
|
|
578
648
|
existing
|
579
649
|
#raise "Reading '#{existing.expand}' already exists, so why are we creating a duplicate?"
|
580
650
|
end
|
581
|
-
constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_words*" ")
|
651
|
+
r = constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_words*" ", :is_negative => (certainty == false))
|
652
|
+
r
|
582
653
|
end
|
583
654
|
end
|
584
655
|
|
@@ -649,16 +720,16 @@ module ActiveFacts
|
|
649
720
|
end
|
650
721
|
# raise "Reading '#{@reading.expand}' already exists, so why are we creating a duplicate (with #{extra_adjectives.inspect})?"
|
651
722
|
else
|
652
|
-
constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_text)
|
723
|
+
constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_text, :is_negative => (certainty == false))
|
653
724
|
end
|
654
725
|
@role_sequence
|
655
726
|
end
|
656
727
|
|
657
728
|
def make_embedded_constraints vocabulary
|
658
|
-
|
659
|
-
next unless
|
660
|
-
# puts "Quantifier #{
|
661
|
-
|
729
|
+
refs.each do |ref|
|
730
|
+
next unless ref.quantifier
|
731
|
+
# puts "Quantifier #{ref.inspect} not implemented as a presence constraint"
|
732
|
+
ref.make_embedded_presence_constraint vocabulary
|
662
733
|
end
|
663
734
|
|
664
735
|
if @qualifiers && @qualifiers.size > 0
|
@@ -675,12 +746,12 @@ module ActiveFacts
|
|
675
746
|
end
|
676
747
|
|
677
748
|
# REVISIT: Check maybe and other qualifiers:
|
678
|
-
debug :constraint, "Need to make constraints for #{@qualifiers
|
749
|
+
debug :constraint, "Need to make constraints for #{@qualifiers.inspect}" if @qualifiers.size > 0 or @certainty != true
|
679
750
|
end
|
680
751
|
end
|
681
752
|
|
682
753
|
def is_naked_object_type
|
683
|
-
@phrases.size == 1 &&
|
754
|
+
@phrases.size == 1 && refs.size == 1
|
684
755
|
end
|
685
756
|
|
686
757
|
end
|
@@ -699,7 +770,7 @@ module ActiveFacts
|
|
699
770
|
@common_supertype = common_supertype
|
700
771
|
@residual_adjectives = residual_adjectives
|
701
772
|
@cancelled_cost = 0
|
702
|
-
debug :matching_fails, "Saving side effects for #{@phrase.term}, absorbs #{@absorbed_precursors}/#{@absorbed_followers}#{@common_supertype ? ',
|
773
|
+
debug :matching_fails, "Saving side effects for #{@phrase.term}, absorbs #{@absorbed_precursors}/#{@absorbed_followers}#{@common_supertype ? ', step over supertype '+ @common_supertype.name : ''}" if @absorbed_precursors+@absorbed_followers+(@common_supertype ? 1 : 0) > 0
|
703
774
|
end
|
704
775
|
|
705
776
|
def cost
|
@@ -718,19 +789,23 @@ module ActiveFacts
|
|
718
789
|
class ClauseMatchSideEffects
|
719
790
|
attr_reader :residual_adjectives
|
720
791
|
attr_reader :fact_type
|
721
|
-
attr_reader :role_side_effects # One array of values per
|
792
|
+
attr_reader :role_side_effects # One array of values per Reference matched, in order
|
793
|
+
attr_reader :negated
|
794
|
+
attr_reader :optional
|
722
795
|
|
723
|
-
def initialize fact_type, clause, residual_adjectives, role_side_effects
|
796
|
+
def initialize fact_type, clause, residual_adjectives, role_side_effects, negated = false
|
724
797
|
@fact_type = fact_type
|
725
798
|
@clause = clause
|
726
799
|
@residual_adjectives = residual_adjectives
|
727
800
|
@role_side_effects = role_side_effects
|
801
|
+
@negated = negated
|
728
802
|
end
|
729
803
|
|
730
804
|
def to_s
|
731
805
|
'side-effects are [' +
|
732
806
|
@role_side_effects.map{|r| r.to_s}*', ' +
|
733
807
|
']' +
|
808
|
+
"#{@negated ? ' negated' : ''}" +
|
734
809
|
"#{@residual_adjectives ? ' with residual adjectives' : ''}"
|
735
810
|
end
|
736
811
|
|
@@ -743,29 +818,32 @@ module ActiveFacts
|
|
743
818
|
@role_side_effects.each do |side_effect|
|
744
819
|
c += side_effect.cost
|
745
820
|
end
|
746
|
-
c
|
821
|
+
c += 1 if @residual_adjectives
|
822
|
+
c += 2 if @negated
|
823
|
+
c
|
747
824
|
end
|
748
825
|
|
749
826
|
def describe
|
750
827
|
actual_effects =
|
751
828
|
@role_side_effects.map do |side_effect|
|
752
|
-
( [side_effect.common_supertype ? "supertype
|
829
|
+
( [side_effect.common_supertype ? "supertype step over #{side_effect.common_supertype.name}" : nil] +
|
753
830
|
[side_effect.absorbed_precursors > 0 ? "absorbs #{side_effect.absorbed_precursors} preceding words" : nil] +
|
754
|
-
[side_effect.absorbed_followers > 0 ? "absorbs #{side_effect.absorbed_followers} following words" : nil]
|
831
|
+
[side_effect.absorbed_followers > 0 ? "absorbs #{side_effect.absorbed_followers} following words" : nil] +
|
832
|
+
[@negated ? 'implicitly negated' : nil]
|
755
833
|
)
|
756
834
|
end.flatten.compact*','
|
757
835
|
actual_effects.empty? ? "no side effects" : actual_effects
|
758
836
|
end
|
759
837
|
end
|
760
838
|
|
761
|
-
class
|
839
|
+
class Reference
|
762
840
|
attr_reader :term, :quantifier, :function_call, :value_constraint, :literal, :nested_clauses
|
763
841
|
attr_accessor :leading_adjective, :trailing_adjective, :role_name
|
764
|
-
attr_accessor :player # What ObjectType does the
|
765
|
-
attr_accessor :
|
842
|
+
attr_accessor :player # What ObjectType does the Binding denote
|
843
|
+
attr_accessor :binding # What Binding for that ObjectType
|
766
844
|
attr_accessor :role # Which Role of this ObjectType
|
767
845
|
attr_accessor :role_ref # Which RoleRef to that Role
|
768
|
-
attr_accessor :clause # The clause that this
|
846
|
+
attr_accessor :clause # The clause that this Reference is part of
|
769
847
|
attr_accessor :objectification_of # If nested_clauses is set, this is the fact type it objectifies
|
770
848
|
attr_reader :embedded_presence_constraint # This refers to the ActiveFacts::Metamodel::PresenceConstraint
|
771
849
|
|
@@ -870,19 +948,19 @@ module ActiveFacts
|
|
870
948
|
role_name = @term
|
871
949
|
end
|
872
950
|
end
|
873
|
-
@
|
874
|
-
@
|
875
|
-
@
|
951
|
+
@binding = (context.bindings[key] ||= Binding.new(@player, role_name))
|
952
|
+
@binding.refs << self
|
953
|
+
@binding
|
876
954
|
end
|
877
955
|
|
878
956
|
def unbind context
|
879
957
|
# The key has changed.
|
880
|
-
@
|
881
|
-
if @
|
882
|
-
# Remove the
|
883
|
-
context.
|
958
|
+
@binding.refs.delete(self)
|
959
|
+
if @binding.refs.empty?
|
960
|
+
# Remove the binding from the context if this was the last reference
|
961
|
+
context.bindings.delete_if {|k,v| v == @binding }
|
884
962
|
end
|
885
|
-
@
|
963
|
+
@binding = nil
|
886
964
|
end
|
887
965
|
|
888
966
|
def rebind(context)
|
@@ -890,18 +968,18 @@ module ActiveFacts
|
|
890
968
|
bind context
|
891
969
|
end
|
892
970
|
|
893
|
-
def rebind_to(context,
|
894
|
-
debug :binding, "Rebinding #{inspect} to #{
|
971
|
+
def rebind_to(context, other_ref)
|
972
|
+
debug :binding, "Rebinding #{inspect} to #{other_ref.inspect}"
|
895
973
|
|
896
|
-
|
974
|
+
old_binding = binding # Remember to move all refs across
|
897
975
|
unbind(context)
|
898
976
|
|
899
|
-
|
900
|
-
[self, *
|
901
|
-
ref.
|
902
|
-
|
977
|
+
new_binding = other_ref.binding
|
978
|
+
[self, *old_binding.refs].each do |ref|
|
979
|
+
ref.binding = new_binding
|
980
|
+
new_binding.refs << ref
|
903
981
|
end
|
904
|
-
|
982
|
+
old_binding.rebound_to = new_binding
|
905
983
|
end
|
906
984
|
|
907
985
|
# These are called when we successfully match a fact type reading that has relevant adjectives:
|
@@ -931,17 +1009,18 @@ module ActiveFacts
|
|
931
1009
|
|
932
1010
|
debug :constraint, "Processing embedded constraint #{@quantifier.inspect} on #{@role_ref.role.object_type.name} in #{fact_type.describe}" do
|
933
1011
|
# Preserve the role order of the clause, excluding this role:
|
934
|
-
constrained_roles = (@clause.
|
1012
|
+
constrained_roles = (@clause.refs-[self]).map{|vr| vr.role_ref.role}
|
935
1013
|
if constrained_roles.empty?
|
936
1014
|
debug :constraint, "Quantifier over unary role has no effect"
|
937
1015
|
return
|
938
1016
|
end
|
939
1017
|
constraint = find_pc_over_roles(constrained_roles)
|
940
1018
|
if constraint
|
941
|
-
debug :constraint, "Setting max frequency to #{@quantifier.max} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}"
|
942
1019
|
raise "Conflicting maximum frequency for constraint" if constraint.max_frequency && constraint.max_frequency != @quantifier.max
|
1020
|
+
debug :constraint, "Setting max frequency to #{@quantifier.max} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}" unless constraint.max_frequency
|
943
1021
|
constraint.max_frequency = @quantifier.max
|
944
1022
|
raise "Conflicting minimum frequency for constraint" if constraint.min_frequency && constraint.min_frequency != @quantifier.min
|
1023
|
+
debug :constraint, "Setting min frequency to #{@quantifier.min} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}" unless constraint.min_frequency
|
945
1024
|
constraint.min_frequency = @quantifier.min
|
946
1025
|
else
|
947
1026
|
role_sequence = constellation.RoleSequence(:new)
|
@@ -956,7 +1035,7 @@ module ActiveFacts
|
|
956
1035
|
:max_frequency => @quantifier.max,
|
957
1036
|
:min_frequency => @quantifier.min
|
958
1037
|
)
|
959
|
-
debug :constraint, "Made new PC min=#{@quantifier.min.inspect} max=#{@quantifier.max.inspect}
|
1038
|
+
debug :constraint, "Made new embedded PC GUID=#{constraint.guid} min=#{@quantifier.min.inspect} max=#{@quantifier.max.inspect} over #{(e = fact_type.entity_type) ? e.name : role_sequence.describe} in #{fact_type.describe}"
|
960
1039
|
@quantifier.enforcement.compile(constellation, constraint) if @quantifier.enforcement
|
961
1040
|
@embedded_presence_constraint = constraint
|
962
1041
|
end
|
@@ -982,6 +1061,18 @@ module ActiveFacts
|
|
982
1061
|
@context_note = context_note
|
983
1062
|
end
|
984
1063
|
|
1064
|
+
def is_unique
|
1065
|
+
@max and @max == 1
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
def is_mandatory
|
1069
|
+
@min and @min >= 1
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
def is_zero
|
1073
|
+
@min == 0 and @max == 0
|
1074
|
+
end
|
1075
|
+
|
985
1076
|
def inspect
|
986
1077
|
"[#{@min}..#{@max}]#{
|
987
1078
|
@context_note && ' ' + @context_note.inspect
|