activefacts 1.6.0 → 1.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +60 -0
- data/Rakefile +3 -80
- data/activefacts.gemspec +36 -0
- data/bin/afgen +4 -2
- data/bin/cql +5 -1
- data/lib/activefacts.rb +3 -12
- data/lib/activefacts/{vocabulary/query_evaluator.rb → query/evaluator.rb} +0 -0
- data/lib/activefacts/version.rb +2 -2
- metadata +48 -296
- data/History.txt +0 -4
- data/LICENSE +0 -19
- data/Manifest.txt +0 -165
- data/README.rdoc +0 -81
- data/css/offline.css +0 -3
- data/css/orm2.css +0 -124
- data/css/print.css +0 -8
- data/css/style-print.css +0 -357
- data/css/style.css +0 -387
- data/download.html +0 -110
- data/examples/CQL/Address.cql +0 -44
- data/examples/CQL/Blog.cql +0 -54
- data/examples/CQL/CompanyDirectorEmployee.cql +0 -56
- data/examples/CQL/Death.cql +0 -17
- data/examples/CQL/Diplomacy.cql +0 -48
- data/examples/CQL/Genealogy.cql +0 -98
- data/examples/CQL/Insurance.cql +0 -320
- data/examples/CQL/Marriage.cql +0 -18
- data/examples/CQL/Metamodel.cql +0 -493
- data/examples/CQL/Monogamy.cql +0 -24
- data/examples/CQL/MultiInheritance.cql +0 -22
- data/examples/CQL/NonRoleId.cql +0 -14
- data/examples/CQL/OddIdentifier.cql +0 -18
- data/examples/CQL/OilSupply.cql +0 -53
- data/examples/CQL/OneToOnes.cql +0 -17
- data/examples/CQL/Orienteering.cql +0 -111
- data/examples/CQL/PersonPlaysGame.cql +0 -18
- data/examples/CQL/RedundantDependency.cql +0 -34
- data/examples/CQL/SchoolActivities.cql +0 -33
- data/examples/CQL/SeparateSubtype.cql +0 -30
- data/examples/CQL/ServiceDirector.cql +0 -276
- data/examples/CQL/SimplestUnary.cql +0 -12
- data/examples/CQL/Supervision.cql +0 -34
- data/examples/CQL/WaiterTips.cql +0 -33
- data/examples/CQL/Warehousing.cql +0 -101
- data/examples/CQL/WindowInRoomInBldg.cql +0 -28
- data/examples/CQL/unit.cql +0 -474
- data/examples/index.html +0 -420
- data/examples/intro.html +0 -327
- data/examples/local.css +0 -24
- data/index.html +0 -111
- data/lib/activefacts/cql.rb +0 -35
- data/lib/activefacts/cql/CQLParser.treetop +0 -158
- data/lib/activefacts/cql/Context.treetop +0 -48
- data/lib/activefacts/cql/Expressions.treetop +0 -67
- data/lib/activefacts/cql/FactTypes.treetop +0 -358
- data/lib/activefacts/cql/Language/English.treetop +0 -315
- data/lib/activefacts/cql/LexicalRules.treetop +0 -253
- data/lib/activefacts/cql/ObjectTypes.treetop +0 -210
- data/lib/activefacts/cql/Rakefile +0 -14
- data/lib/activefacts/cql/Terms.treetop +0 -183
- data/lib/activefacts/cql/ValueTypes.treetop +0 -202
- data/lib/activefacts/cql/compiler.rb +0 -156
- data/lib/activefacts/cql/compiler/clause.rb +0 -1137
- data/lib/activefacts/cql/compiler/constraint.rb +0 -581
- data/lib/activefacts/cql/compiler/entity_type.rb +0 -457
- data/lib/activefacts/cql/compiler/expression.rb +0 -443
- data/lib/activefacts/cql/compiler/fact.rb +0 -390
- data/lib/activefacts/cql/compiler/fact_type.rb +0 -421
- data/lib/activefacts/cql/compiler/query.rb +0 -106
- data/lib/activefacts/cql/compiler/shared.rb +0 -161
- data/lib/activefacts/cql/compiler/value_type.rb +0 -174
- data/lib/activefacts/cql/nodes.rb +0 -49
- data/lib/activefacts/cql/parser.rb +0 -241
- data/lib/activefacts/dependency_analyser.rb +0 -182
- data/lib/activefacts/generate/absorption.rb +0 -70
- data/lib/activefacts/generate/composition.rb +0 -118
- data/lib/activefacts/generate/cql.rb +0 -714
- data/lib/activefacts/generate/dm.rb +0 -279
- data/lib/activefacts/generate/help.rb +0 -64
- data/lib/activefacts/generate/helpers/inject.rb +0 -16
- data/lib/activefacts/generate/helpers/oo.rb +0 -162
- data/lib/activefacts/generate/helpers/ordered.rb +0 -605
- data/lib/activefacts/generate/helpers/rails.rb +0 -57
- data/lib/activefacts/generate/html/glossary.rb +0 -461
- data/lib/activefacts/generate/json.rb +0 -337
- data/lib/activefacts/generate/null.rb +0 -32
- data/lib/activefacts/generate/rails/models.rb +0 -246
- data/lib/activefacts/generate/rails/schema.rb +0 -216
- data/lib/activefacts/generate/records.rb +0 -46
- data/lib/activefacts/generate/ruby.rb +0 -133
- data/lib/activefacts/generate/sql/mysql.rb +0 -280
- data/lib/activefacts/generate/sql/server.rb +0 -273
- data/lib/activefacts/generate/stats.rb +0 -69
- data/lib/activefacts/generate/text.rb +0 -27
- data/lib/activefacts/generate/topics.rb +0 -265
- data/lib/activefacts/generate/traits/datavault.rb +0 -241
- data/lib/activefacts/generate/traits/oo.rb +0 -73
- data/lib/activefacts/generate/traits/ordered.rb +0 -33
- data/lib/activefacts/generate/traits/ruby.rb +0 -210
- data/lib/activefacts/generate/transform/datavault.rb +0 -266
- data/lib/activefacts/generate/transform/surrogate.rb +0 -214
- data/lib/activefacts/generate/version.rb +0 -26
- data/lib/activefacts/input/cql.rb +0 -43
- data/lib/activefacts/input/orm.rb +0 -1636
- data/lib/activefacts/mapping/rails.rb +0 -132
- data/lib/activefacts/persistence.rb +0 -15
- data/lib/activefacts/persistence/columns.rb +0 -446
- data/lib/activefacts/persistence/foreignkey.rb +0 -187
- data/lib/activefacts/persistence/index.rb +0 -240
- data/lib/activefacts/persistence/object_type.rb +0 -198
- data/lib/activefacts/persistence/reference.rb +0 -434
- data/lib/activefacts/persistence/tables.rb +0 -380
- data/lib/activefacts/registry.rb +0 -11
- data/lib/activefacts/support.rb +0 -132
- data/lib/activefacts/vocabulary.rb +0 -9
- data/lib/activefacts/vocabulary/extensions.rb +0 -1348
- data/lib/activefacts/vocabulary/metamodel.rb +0 -570
- data/lib/activefacts/vocabulary/verbaliser.rb +0 -804
- data/script/txt2html +0 -71
- data/spec/absorption_spec.rb +0 -95
- data/spec/cql/comparison_spec.rb +0 -89
- data/spec/cql/context_spec.rb +0 -94
- data/spec/cql/contractions_spec.rb +0 -224
- data/spec/cql/deontic_spec.rb +0 -88
- data/spec/cql/entity_type_spec.rb +0 -320
- data/spec/cql/expressions_spec.rb +0 -66
- data/spec/cql/fact_type_matching_spec.rb +0 -338
- data/spec/cql/french_spec.rb +0 -21
- data/spec/cql/parser/bad_literals_spec.rb +0 -86
- data/spec/cql/parser/constraints_spec.rb +0 -19
- data/spec/cql/parser/entity_types_spec.rb +0 -106
- data/spec/cql/parser/expressions_spec.rb +0 -199
- data/spec/cql/parser/fact_types_spec.rb +0 -44
- data/spec/cql/parser/literals_spec.rb +0 -312
- data/spec/cql/parser/pragmas_spec.rb +0 -89
- data/spec/cql/parser/value_types_spec.rb +0 -42
- data/spec/cql/role_matching_spec.rb +0 -148
- data/spec/cql/samples_spec.rb +0 -244
- data/spec/cql_cql_spec.rb +0 -73
- data/spec/cql_dm_spec.rb +0 -136
- data/spec/cql_mysql_spec.rb +0 -69
- data/spec/cql_parse_spec.rb +0 -34
- data/spec/cql_ruby_spec.rb +0 -73
- data/spec/cql_sql_spec.rb +0 -72
- data/spec/cql_symbol_tables_spec.rb +0 -261
- data/spec/cqldump_spec.rb +0 -170
- data/spec/helpers/array_matcher.rb +0 -23
- data/spec/helpers/ctrl_c_support.rb +0 -52
- data/spec/helpers/diff_matcher.rb +0 -39
- data/spec/helpers/file_matcher.rb +0 -34
- data/spec/helpers/parse_to_ast_matcher.rb +0 -80
- data/spec/helpers/string_matcher.rb +0 -30
- data/spec/helpers/test_parser.rb +0 -15
- data/spec/norma_cql_spec.rb +0 -66
- data/spec/norma_ruby_spec.rb +0 -62
- data/spec/norma_ruby_sql_spec.rb +0 -107
- data/spec/norma_sql_spec.rb +0 -57
- data/spec/norma_tables_spec.rb +0 -95
- data/spec/ruby_api_spec.rb +0 -23
- data/spec/spec_helper.rb +0 -35
- data/spec/transform_surrogate_spec.rb +0 -59
- data/status.html +0 -138
- data/why.html +0 -60
@@ -1,1137 +0,0 @@
|
|
1
|
-
module ActiveFacts
|
2
|
-
module CQL
|
3
|
-
class Compiler < ActiveFacts::CQL::Parser
|
4
|
-
|
5
|
-
class Clause
|
6
|
-
attr_reader :phrases
|
7
|
-
attr_accessor :qualifiers, :context_note
|
8
|
-
attr_accessor :certainty # nil, true, false -> maybe, definitely, not
|
9
|
-
attr_accessor :conjunction # one of {nil, 'and', ',', 'or', 'where'}
|
10
|
-
attr_accessor :fact_type
|
11
|
-
attr_reader :reading, :role_sequence # These are the Metamodel objects
|
12
|
-
attr_reader :side_effects # How to adjust the phrases if this fact_type match is accepted
|
13
|
-
attr_accessor :fact # When binding fact instances the fact goes here
|
14
|
-
attr_accessor :objectified_as # The Reference which objectified this fact type
|
15
|
-
|
16
|
-
def initialize phrases, qualifiers = [], context_note = nil
|
17
|
-
@phrases = phrases
|
18
|
-
refs.each { |ref| ref.clause = self }
|
19
|
-
@certainty = true
|
20
|
-
@qualifiers = qualifiers
|
21
|
-
@context_note = context_note
|
22
|
-
end
|
23
|
-
|
24
|
-
def refs
|
25
|
-
@phrases.select{|r| r.respond_to?(:player)}
|
26
|
-
end
|
27
|
-
|
28
|
-
# A clause that contains only the name of a ObjectType and no literal or reading text
|
29
|
-
# refers only to the existence of that ObjectType (as opposed to an instance of the object_type).
|
30
|
-
def is_existential_type
|
31
|
-
@phrases.size == 1 and
|
32
|
-
@phrases[0].is_a?(Reference) and
|
33
|
-
!@phrases[0].literal
|
34
|
-
end
|
35
|
-
|
36
|
-
def display
|
37
|
-
to_s
|
38
|
-
end
|
39
|
-
|
40
|
-
def inspect
|
41
|
-
to_s
|
42
|
-
end
|
43
|
-
|
44
|
-
def to_s phrases = nil
|
45
|
-
phrases ||= @phrases
|
46
|
-
"#{
|
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
|
54
|
-
}#{
|
55
|
-
(
|
56
|
-
phrases.map do |phrase|
|
57
|
-
case phrase
|
58
|
-
when String
|
59
|
-
'"' + phrase.to_s + '"'
|
60
|
-
when Reference
|
61
|
-
phrase.to_s +
|
62
|
-
if phrase.nested_clauses
|
63
|
-
' (in which ' +
|
64
|
-
phrase.nested_clauses.map do |c|
|
65
|
-
((j = c.conjunction) ? j+' ' : '') +
|
66
|
-
c.to_s
|
67
|
-
end*' ' +
|
68
|
-
')'
|
69
|
-
else
|
70
|
-
''
|
71
|
-
end
|
72
|
-
when Operation
|
73
|
-
phrase.inspect
|
74
|
-
when Literal
|
75
|
-
phrase.inspect
|
76
|
-
#when FunctionCallChain # REVISIT: Add something here when I re-add functions
|
77
|
-
# phrase.inspect
|
78
|
-
else
|
79
|
-
raise "Unexpected phrase type in clause: #{phrase.class}"
|
80
|
-
end
|
81
|
-
end * ' '
|
82
|
-
).gsub(/" "/, ' ')
|
83
|
-
}#{
|
84
|
-
@context_note && ' ' + @context_note.inspect
|
85
|
-
}"
|
86
|
-
end
|
87
|
-
|
88
|
-
def identify_players_with_role_name context
|
89
|
-
refs.each do |ref|
|
90
|
-
ref.identify_players_with_role_name(context)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def identify_other_players context
|
95
|
-
refs.each do |ref|
|
96
|
-
ref.identify_other_players(context)
|
97
|
-
# Include players in nested clauses, if any
|
98
|
-
ref.nested_clauses.each{|clause| clause.identify_other_players(context)} if ref.nested_clauses
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def includes_literals
|
103
|
-
refs.detect{|ref| ref.literal || (ja = ref.nested_clauses and ja.detect{|jr| jr.includes_literals })}
|
104
|
-
end
|
105
|
-
|
106
|
-
def is_equality_comparison
|
107
|
-
false
|
108
|
-
end
|
109
|
-
|
110
|
-
def bind context
|
111
|
-
role_names = refs.map{ |ref| ref.role_name }.compact
|
112
|
-
|
113
|
-
# Check uniqueness of role names and subscripts within this clause:
|
114
|
-
role_names.each do |rn|
|
115
|
-
next if role_names.select{|rn2| rn2 == rn}.size == 1
|
116
|
-
raise "Duplicate role #{rn.is_a?(Integer) ? "subscript" : "name"} '#{rn}' in clause"
|
117
|
-
end
|
118
|
-
|
119
|
-
refs.each do |ref|
|
120
|
-
ref.bind context
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
# This method is used in matching unary fact types in entity identification
|
125
|
-
# It disregards literals, which are not allowed in this context.
|
126
|
-
def phrases_match(phrases)
|
127
|
-
@phrases.zip(phrases).each do |mine, theirs|
|
128
|
-
return false if mine.is_a?(Reference) != theirs.is_a?(Reference)
|
129
|
-
if mine.is_a?(Reference)
|
130
|
-
return false unless mine.key == theirs.key
|
131
|
-
else
|
132
|
-
return false unless mine == theirs
|
133
|
-
end
|
134
|
-
end
|
135
|
-
true
|
136
|
-
end
|
137
|
-
|
138
|
-
# This method chooses the existing fact type which matches most closely.
|
139
|
-
# It returns nil if there is none, or a ClauseMatchSideEffects object if matched.
|
140
|
-
#
|
141
|
-
# As this match may not necessarily be used (depending on the side effects),
|
142
|
-
# no change is made to this Clause object - those will be done later.
|
143
|
-
#
|
144
|
-
def match_existing_fact_type context, options = {}
|
145
|
-
raise "Cannot match a clause that contains no object types" if refs.size == 0
|
146
|
-
raise "Internal error, clause already matched, should not match again" if @fact_type
|
147
|
-
|
148
|
-
if is_naked_object_type
|
149
|
-
ref = refs[0] # "There can be only one"
|
150
|
-
return true unless ref.nested_clauses
|
151
|
-
ref.nested_clauses.each do |nested|
|
152
|
-
ft = nested.match_existing_fact_type(context)
|
153
|
-
raise "Unrecognised fact type #{nested.display} nested under #{inspect}" unless ft
|
154
|
-
if (ft.entity_type == ref.player)
|
155
|
-
ref.objectification_of = ft
|
156
|
-
nested.objectified_as = ref
|
157
|
-
end
|
158
|
-
end
|
159
|
-
raise "#{ref.inspect} contains objectification steps that do not objectify it" unless ref.objectification_of
|
160
|
-
return true
|
161
|
-
end
|
162
|
-
|
163
|
-
# If we fail to match, try a left contraction (or save this for a subsequent left contraction):
|
164
|
-
left_contract_this_onto = context.left_contractable_clause
|
165
|
-
new_conjunction = (conjunction == nil || conjunction == ',')
|
166
|
-
changed_conjunction = (lcc = context.left_contraction_conjunction) && lcc != conjunction
|
167
|
-
if context.left_contraction_allowed && (new_conjunction || changed_conjunction)
|
168
|
-
# Conjunctions are that/who, where, comparison-operator, ','
|
169
|
-
trace :matching, "A left contraction will be against #{self.inspect}, conjunction is #{conjunction.inspect}"
|
170
|
-
context.left_contractable_clause = self
|
171
|
-
left_contract_this_onto = nil # Can't left-contract this clause
|
172
|
-
end
|
173
|
-
context.left_contraction_conjunction = new_conjunction ? nil : @conjunction
|
174
|
-
|
175
|
-
phrases = @phrases
|
176
|
-
vrs = []+refs
|
177
|
-
|
178
|
-
# A left contraction is where the first player in the previous clause continues as first player of this clause
|
179
|
-
contracted_left = false
|
180
|
-
can_contract_right = false
|
181
|
-
left_insertion = nil
|
182
|
-
right_insertion = nil
|
183
|
-
supposed_roles = [] # Arrange to unbind incorrect references supposed due to contraction
|
184
|
-
contract_left = proc do
|
185
|
-
contracted_from = left_contract_this_onto.refs[0]
|
186
|
-
contraction_player = contracted_from.player
|
187
|
-
contracted_role = Reference.new(contraction_player.name)
|
188
|
-
supposed_roles << contracted_role
|
189
|
-
left_insertion = contracted_role.inspect+' '
|
190
|
-
contracted_role.player = contracted_from.player
|
191
|
-
contracted_role.role_name = contracted_from.role_name
|
192
|
-
contracted_role.bind(context)
|
193
|
-
vrs.unshift contracted_role
|
194
|
-
contracted_left = true
|
195
|
-
phrases = [contracted_role]+phrases
|
196
|
-
trace :matching, "Failed to match #{inspect}. Trying again using left contraction onto #{contraction_player.name}"
|
197
|
-
end
|
198
|
-
|
199
|
-
contract_right = proc do
|
200
|
-
contracted_from = left_contract_this_onto.refs[-1]
|
201
|
-
contraction_player = contracted_from.player
|
202
|
-
contracted_role = Reference.new(contraction_player.name)
|
203
|
-
supposed_roles << contracted_role
|
204
|
-
right_insertion = ' '+contracted_role.inspect
|
205
|
-
contracted_role.player = contracted_from.player
|
206
|
-
contracted_role.role_name = contracted_from.role_name
|
207
|
-
contracted_role.bind(context)
|
208
|
-
vrs.push contracted_role
|
209
|
-
phrases = phrases+[contracted_role]
|
210
|
-
trace :matching, "Failed to match #{inspect}. Trying again using right contraction onto #{contraction_player.name}"
|
211
|
-
end
|
212
|
-
|
213
|
-
begin
|
214
|
-
players = vrs.map{|vr| vr.player}
|
215
|
-
|
216
|
-
if players.size == 0
|
217
|
-
can_contract_right = left_contract_this_onto.refs.size == 2
|
218
|
-
contract_left.call
|
219
|
-
redo
|
220
|
-
end
|
221
|
-
|
222
|
-
raise "Must identify players before matching fact types" if players.include? nil
|
223
|
-
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
|
224
|
-
|
225
|
-
player_names = players.map{|p| p.name}
|
226
|
-
|
227
|
-
trace :matching, "Looking for existing #{players.size}-ary fact types matching '#{inspect}'" do
|
228
|
-
trace :matching, "Players are '#{player_names.inspect}'"
|
229
|
-
|
230
|
-
# Match existing fact types in nested clauses first:
|
231
|
-
# (not for contractions) REVISIT: Why not?
|
232
|
-
if !contracted_left
|
233
|
-
vrs.each do |ref|
|
234
|
-
next if ref.is_a?(Operation)
|
235
|
-
next unless steps = ref.nested_clauses and !steps.empty?
|
236
|
-
ref.nested_clauses.each do |nested|
|
237
|
-
ft = nested.match_existing_fact_type(context)
|
238
|
-
raise "Unrecognised fact type #{nested.display}" unless ft
|
239
|
-
if (ft && ft.entity_type == ref.player)
|
240
|
-
ref.objectification_of = ft
|
241
|
-
nested.objectified_as = ref
|
242
|
-
end
|
243
|
-
end
|
244
|
-
raise "#{ref.inspect} contains objectification steps that do not objectify it" unless ref.objectification_of
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
# For each role player, find the compatible types (the set of all subtypes and supertypes).
|
249
|
-
# For a player that's an objectification, we don't allow implicit supertype steps
|
250
|
-
player_related_types =
|
251
|
-
vrs.zip(players).map do |ref, player|
|
252
|
-
disallow_subtyping = ref && ref.objectification_of || options[:exact_type]
|
253
|
-
((disallow_subtyping ? [] : player.supertypes_transitive) +
|
254
|
-
player.subtypes_transitive).uniq
|
255
|
-
end
|
256
|
-
|
257
|
-
trace :matching, "Players must match '#{player_related_types.map{|pa| pa.map{|p|p.name}}.inspect}'"
|
258
|
-
|
259
|
-
start_obj = player_related_types[0] || [left_contract_this_onto.refs[-1].player]
|
260
|
-
# The candidate fact types have the right number of role players of related types.
|
261
|
-
# If any role is played by a supertype or subtype of the required type, there's an implicit subtyping steps
|
262
|
-
# REVISIT: A double contraction results in player_related_types being empty here
|
263
|
-
candidate_fact_types =
|
264
|
-
start_obj.map do |related_type|
|
265
|
-
related_type.all_role.select do |role|
|
266
|
-
# next if role.fact_type.all_reading.size == 0
|
267
|
-
next if role.fact_type.is_a?(ActiveFacts::Metamodel::LinkFactType)
|
268
|
-
next if role.fact_type.all_role.size != players.size # Wrong number of players
|
269
|
-
|
270
|
-
compatible_readings = role.fact_type.compatible_readings(player_related_types)
|
271
|
-
next unless compatible_readings.size > 0
|
272
|
-
trace :matching_fails, "These readings are compatible: #{compatible_readings.map(&:expand).inspect}"
|
273
|
-
true
|
274
|
-
end.
|
275
|
-
map{ |role| role.fact_type}
|
276
|
-
end.flatten.uniq
|
277
|
-
|
278
|
-
# If there is more than one possible exact match (same adjectives) with different subyping, the implicit query is ambiguous and is not allowed
|
279
|
-
|
280
|
-
trace :matching, "Looking amongst #{candidate_fact_types.size} existing fact types for one matching #{left_insertion}'#{inspect}'#{right_insertion}" do
|
281
|
-
matches = {}
|
282
|
-
candidate_fact_types.map do |fact_type|
|
283
|
-
fact_type.all_reading.map do |reading|
|
284
|
-
next unless side_effects = clause_matches(fact_type, reading, phrases)
|
285
|
-
matches[reading] = side_effects if side_effects
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
# REVISIT: Side effects that leave extra adjectives should only be allowed if the
|
290
|
-
# same extra adjectives exist in some other clause in the same declaration.
|
291
|
-
# The extra adjectives are then necessary to associate the two role players
|
292
|
-
# when consumed adjectives were required to bind to the underlying fact types.
|
293
|
-
# This requires the final decision on fact type matching to be postponed until
|
294
|
-
# the whole declaration has been processed and the extra adjectives can be matched.
|
295
|
-
|
296
|
-
best_matches = matches.keys.sort_by{|match|
|
297
|
-
# Between equivalents, prefer the one without steps on the first role
|
298
|
-
(m = matches[match]).cost*2 + ((!(e = m.role_side_effects[0]) || e.cost) == 0 ? 0 : 1)
|
299
|
-
}
|
300
|
-
trace :matching_fails, "Found #{matches.size} valid matches#{matches.size > 0 ? ', best is '+best_matches[0].expand : ''}"
|
301
|
-
|
302
|
-
if matches.size > 1
|
303
|
-
first = matches[best_matches[0]]
|
304
|
-
cost = first.cost
|
305
|
-
equal_best = matches.select{|k,m| m.cost == cost}
|
306
|
-
|
307
|
-
if equal_best.size > 1 and equal_best.detect{|k,m| !m.fact_type.is_a?(Metamodel::TypeInheritance)}
|
308
|
-
# Complain if there's more than one equivalent cost match (unless all are TypeInheritance):
|
309
|
-
raise "#{@phrases.inspect} could match any of the following:\n\t"+
|
310
|
-
best_matches.map { |reading| reading.expand + " with " + matches[reading].describe } * "\n\t"
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
if matches.size >= 1
|
315
|
-
@reading = best_matches[0]
|
316
|
-
@side_effects = matches[@reading]
|
317
|
-
@fact_type = @side_effects.fact_type
|
318
|
-
trace :matching, "Matched '#{@fact_type.default_reading}'"
|
319
|
-
@phrases = phrases
|
320
|
-
apply_side_effects(context, @side_effects)
|
321
|
-
return @fact_type
|
322
|
-
end
|
323
|
-
|
324
|
-
end
|
325
|
-
trace :matching, "No fact type matched, candidates were '#{candidate_fact_types.map{|ft| ft.default_reading}*"', '"}'"
|
326
|
-
end
|
327
|
-
if left_contract_this_onto
|
328
|
-
if !contracted_left
|
329
|
-
contract_left.call
|
330
|
-
redo
|
331
|
-
elsif can_contract_right
|
332
|
-
contract_right.call
|
333
|
-
can_contract_right = false
|
334
|
-
redo
|
335
|
-
end
|
336
|
-
end
|
337
|
-
end until true # Once through, unless we hit a redo
|
338
|
-
supposed_roles.each do |role|
|
339
|
-
role.unbind context
|
340
|
-
end
|
341
|
-
@fact_type = nil
|
342
|
-
end
|
343
|
-
|
344
|
-
# The Reading passed has the same players as this Clause. Does it match?
|
345
|
-
# Twisty curves. This is a complex bit of code!
|
346
|
-
# Find whether the phrases of this clause match the fact type reading,
|
347
|
-
# which may require absorbing unmarked adjectives.
|
348
|
-
#
|
349
|
-
# If it does match, make the required changes and set @ref to the matching role ref.
|
350
|
-
# Adjectives that were used to match are removed (and leaving any additional adjectives intact).
|
351
|
-
#
|
352
|
-
# Approach:
|
353
|
-
# Match each element where element means:
|
354
|
-
# a role player phrase (perhaps with adjectives)
|
355
|
-
# Our phrase must either be
|
356
|
-
# a player that contains the same adjectives as in the reading.
|
357
|
-
# a word (unmarked leading adjective) that introduces a sequence
|
358
|
-
# of adjectives leading up to a matching player
|
359
|
-
# trailing adjectives, both marked and unmarked, are absorbed too.
|
360
|
-
# a word that matches the reading's
|
361
|
-
#
|
362
|
-
def clause_matches(fact_type, reading, phrases = @phrases)
|
363
|
-
implicitly_negated = false
|
364
|
-
side_effects = [] # An array of items for each role, describing any side-effects of the match.
|
365
|
-
intervening_words = nil
|
366
|
-
residual_adjectives = false
|
367
|
-
|
368
|
-
# The following form of negation is, e.g., where "Person was invited to no Party",
|
369
|
-
# as opposed to where "Person was not invited to that Party". Quite different meaning,
|
370
|
-
# because a free Party variable is required, but the join step is still disallowed.
|
371
|
-
# REVISIT: I'll create the free variable when I implement some/that binding
|
372
|
-
# REVISIT: the verbaliser will need to know about a negated step to a free variable
|
373
|
-
implicitly_negated = true if refs.detect{|ref| q = ref.quantifier and q.is_zero }
|
374
|
-
|
375
|
-
trace :matching_fails, "Does '#{phrases.inspect}' match '#{reading.expand}'" do
|
376
|
-
phrase_num = 0
|
377
|
-
reading_parts = reading.text.split(/\s+/)
|
378
|
-
reading_parts.each do |element|
|
379
|
-
phrase = phrases[phrase_num]
|
380
|
-
if phrase == 'not'
|
381
|
-
raise "Stop playing games with your double negatives: #{phrases.inspect}" if implicitly_negated
|
382
|
-
trace :matching, "Negation detected"
|
383
|
-
implicitly_negated = true
|
384
|
-
phrase = phrases[phrase_num += 1]
|
385
|
-
end
|
386
|
-
if element !~ /\{(\d+)\}/
|
387
|
-
# Just a word; it must match
|
388
|
-
unless phrase == element
|
389
|
-
trace :matching_fails, "Mismatched ordinary word #{phrases[phrase_num].inspect} (wanted #{element})"
|
390
|
-
return nil
|
391
|
-
end
|
392
|
-
phrase_num += 1
|
393
|
-
next
|
394
|
-
else
|
395
|
-
role_ref = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[$1.to_i]
|
396
|
-
end
|
397
|
-
|
398
|
-
player = role_ref.role.object_type
|
399
|
-
|
400
|
-
# Figure out what's next in this phrase (the next player and the words leading up to it)
|
401
|
-
next_player_phrase = nil
|
402
|
-
intervening_words = []
|
403
|
-
while (phrase = phrases[phrase_num])
|
404
|
-
phrase_num += 1
|
405
|
-
if phrase.respond_to?(:player)
|
406
|
-
next_player_phrase = phrase
|
407
|
-
next_player_phrase_num = phrase_num-1
|
408
|
-
break
|
409
|
-
else
|
410
|
-
intervening_words << phrase
|
411
|
-
end
|
412
|
-
end
|
413
|
-
return nil unless next_player_phrase # reading has more players than we do.
|
414
|
-
next_player = next_player_phrase.player
|
415
|
-
|
416
|
-
# The next player must match:
|
417
|
-
common_supertype = nil
|
418
|
-
if next_player != player
|
419
|
-
# This relies on the supertypes being in breadth-first order:
|
420
|
-
common_supertype = (next_player.supertypes_transitive & player.supertypes_transitive)[0]
|
421
|
-
if !common_supertype
|
422
|
-
trace :matching_fails, "Reading discounted because next player #{player.name} doesn't match #{next_player.name}"
|
423
|
-
return nil
|
424
|
-
end
|
425
|
-
|
426
|
-
trace :matching_fails, "Subtype step is required between #{player.name} and #{next_player_phrase.player.name} via common supertype #{common_supertype.name}"
|
427
|
-
else
|
428
|
-
if !next_player_phrase
|
429
|
-
next # Contraction succeeded so far
|
430
|
-
end
|
431
|
-
end
|
432
|
-
|
433
|
-
# It's the right player. Do the adjectives match? This must include the intervening_words, if any.
|
434
|
-
|
435
|
-
role_has_residual_adjectives = false
|
436
|
-
absorbed_precursors = 0
|
437
|
-
if la = role_ref.leading_adjective and !la.empty?
|
438
|
-
# The leading adjectives must match, one way or another
|
439
|
-
la = la.split(/\s+/)
|
440
|
-
if (la[0, intervening_words.size] == intervening_words) # Exact match
|
441
|
-
iw = intervening_words
|
442
|
-
else
|
443
|
-
# We may have hyphenated adjectives. Break them up to check:
|
444
|
-
iw = intervening_words.map{|w| w.split(/-/)}.flatten
|
445
|
-
return nil unless la[0,iw.size] == iw
|
446
|
-
end
|
447
|
-
|
448
|
-
# Any intervening_words matched, see what remains
|
449
|
-
la.slice!(0, iw.size)
|
450
|
-
|
451
|
-
# If there were intervening_words, the remaining reading adjectives must match the phrase's leading_adjective exactly.
|
452
|
-
phrase_la = (next_player_phrase.leading_adjective||'').split(/\s+/)
|
453
|
-
return nil if !iw.empty? && la != phrase_la
|
454
|
-
# If not, the phrase's leading_adjectives must *end* with the reading's
|
455
|
-
return nil if phrase_la[-la.size..-1] != la
|
456
|
-
role_has_residual_adjectives = true if phrase_la.size > la.size
|
457
|
-
# The leading adjectives and the player matched! Check the trailing adjectives.
|
458
|
-
absorbed_precursors = intervening_words.size
|
459
|
-
intervening_words = []
|
460
|
-
elsif intervening_words.size > 0 || next_player_phrase.leading_adjective
|
461
|
-
role_has_residual_adjectives = true
|
462
|
-
end
|
463
|
-
|
464
|
-
absorbed_followers = 0
|
465
|
-
if ta = role_ref.trailing_adjective and !ta.empty?
|
466
|
-
ta = ta.split(/\s+/) # These are the trailing adjectives to match
|
467
|
-
|
468
|
-
phrase_ta = (next_player_phrase.trailing_adjective||'').split(/\s+/)
|
469
|
-
i = 0 # Pad the phrases up to the size of the trailing_adjectives
|
470
|
-
while phrase_ta.size < ta.size
|
471
|
-
break unless (word = phrases[phrase_num+i]).is_a?(String)
|
472
|
-
phrase_ta << word
|
473
|
-
i += 1
|
474
|
-
end
|
475
|
-
# ta is the adjectives in the fact type being matched
|
476
|
-
# phrase_ta is the explicit adjectives augmented with implicit ones to the same size
|
477
|
-
return nil if ta != phrase_ta[0,ta.size]
|
478
|
-
role_has_residual_adjectives = true if phrase_ta.size > ta.size
|
479
|
-
absorbed_followers = i
|
480
|
-
phrase_num += i # Skip following words that were consumed as trailing adjectives
|
481
|
-
elsif next_player_phrase.trailing_adjective
|
482
|
-
role_has_residual_adjectives = true
|
483
|
-
end
|
484
|
-
|
485
|
-
# REVISIT: I'm not even sure I should be caring about role names here.
|
486
|
-
# Role names are on roles, and are only useful within the fact type definition.
|
487
|
-
# At some point, we need to worry about role names on clauses within fact type derivations,
|
488
|
-
# which means they'll move to the Role Ref class; but even then they only match within the
|
489
|
-
# definition that creates that Role Ref.
|
490
|
-
=begin
|
491
|
-
if a = (!phrase.role_name.is_a?(Integer) && phrase.role_name) and
|
492
|
-
e = role_ref.role.role_name and
|
493
|
-
a != e
|
494
|
-
trace :matching, "Role names #{e.inspect} for #{player.name} and #{a.inspect} for #{next_player_phrase.player.name} don't match"
|
495
|
-
return nil
|
496
|
-
end
|
497
|
-
=end
|
498
|
-
|
499
|
-
residual_adjectives ||= role_has_residual_adjectives
|
500
|
-
if residual_adjectives && next_player_phrase.binding.refs.size == 1
|
501
|
-
# This makes matching order-dependent, because there may be no "other purpose"
|
502
|
-
# until another reading has been matched and the roles rebound.
|
503
|
-
trace :matching_fails, "Residual adjectives have no other purpose, so this match fails"
|
504
|
-
return nil
|
505
|
-
end
|
506
|
-
|
507
|
-
# The phrases matched this reading's next role_ref, save data to apply the side-effects:
|
508
|
-
side_effects << ClauseMatchSideEffect.new(next_player_phrase, role_ref, next_player_phrase_num, absorbed_precursors, absorbed_followers, common_supertype, role_has_residual_adjectives)
|
509
|
-
end
|
510
|
-
|
511
|
-
if phrase_num != phrases.size || !intervening_words.empty?
|
512
|
-
trace :matching_fails, "Extra words #{(intervening_words + phrases[phrase_num..-1]).inspect}"
|
513
|
-
return nil
|
514
|
-
end
|
515
|
-
|
516
|
-
if fact_type.is_a?(Metamodel::TypeInheritance)
|
517
|
-
# There may be only one subtyping step on a TypeInheritance fact type.
|
518
|
-
ti_steps = side_effects.select{|side_effect| side_effect.common_supertype}
|
519
|
-
if ti_steps.size > 1 # Not allowed
|
520
|
-
trace :matching_fails, "Can't have more than one subtyping step on a TypeInheritance fact type"
|
521
|
-
return nil
|
522
|
-
end
|
523
|
-
|
524
|
-
if ti = ti_steps[0]
|
525
|
-
# The Type Inheritance step must continue in the same direction as this reading.
|
526
|
-
allowed = fact_type.supertype == ti.role_ref.role.object_type ?
|
527
|
-
fact_type.subtype.supertypes_transitive :
|
528
|
-
fact_type.supertype.subtypes_transitive
|
529
|
-
if !allowed.include?(ti.common_supertype)
|
530
|
-
trace :matching_fails, "Implicit subtyping step extends in the wrong direction"
|
531
|
-
return nil
|
532
|
-
end
|
533
|
-
end
|
534
|
-
end
|
535
|
-
|
536
|
-
trace :matching, "Matched reading '#{reading.expand}' (with #{
|
537
|
-
side_effects.map{|side_effect|
|
538
|
-
side_effect.absorbed_precursors+side_effect.absorbed_followers + (side_effect.common_supertype ? 1 : 0)
|
539
|
-
}.inspect
|
540
|
-
} side effects)#{residual_adjectives ? ' and residual adjectives' : ''}"
|
541
|
-
end
|
542
|
-
# There will be one side_effects for each role player
|
543
|
-
@certainty = !@certainty if implicitly_negated
|
544
|
-
@certainty = !@certainty if reading.is_negative
|
545
|
-
ClauseMatchSideEffects.new(fact_type, self, residual_adjectives, side_effects, implicitly_negated)
|
546
|
-
end
|
547
|
-
|
548
|
-
def apply_side_effects(context, side_effects)
|
549
|
-
@applied_side_effects = side_effects
|
550
|
-
# Enact the side-effects of this match (delete the consumed adjectives):
|
551
|
-
# Since this deletes words from the phrases, we do it in reverse order.
|
552
|
-
trace :matching, "Apply side-effects" do
|
553
|
-
side_effects.apply_all do |side_effect|
|
554
|
-
phrase = side_effect.phrase
|
555
|
-
|
556
|
-
# We re-use the role_ref if possible (no extra adjectives were used, no rolename or step, etc).
|
557
|
-
trace :matching, "side-effect means binding #{phrase.inspect} matches role ref #{side_effect.role_ref.role.object_type.name}"
|
558
|
-
phrase.role_ref = side_effect.role_ref
|
559
|
-
|
560
|
-
changed = false
|
561
|
-
|
562
|
-
# Where this phrase has leading or trailing adjectives that are in excess of those of
|
563
|
-
# the role_ref, those must be local, and we'll need to extract them.
|
564
|
-
|
565
|
-
if rra = side_effect.role_ref.trailing_adjective
|
566
|
-
trace :matching, "Deleting matched trailing adjective '#{rra}'#{side_effect.absorbed_followers>0 ? " in #{side_effect.absorbed_followers} followers" : ""}, cost is #{side_effect.cost}"
|
567
|
-
side_effect.cancel_cost side_effect.absorbed_followers
|
568
|
-
|
569
|
-
# These adjective(s) matched either an adjective here, or a follower word, or both.
|
570
|
-
if a = phrase.trailing_adjective
|
571
|
-
if a.size >= rra.size
|
572
|
-
a = a[rra.size+1..-1]
|
573
|
-
phrase.trailing_adjective = a == '' ? nil : a
|
574
|
-
changed = true
|
575
|
-
end
|
576
|
-
elsif side_effect.absorbed_followers > 0
|
577
|
-
# The following statement is incorrect. The absorbed adjective is what caused the match.
|
578
|
-
# This phrase is absorbing non-hyphenated adjective(s), which changes its binding
|
579
|
-
# phrase.trailing_adjective =
|
580
|
-
@phrases.slice!(side_effect.num+1, side_effect.absorbed_followers)*' '
|
581
|
-
changed = true
|
582
|
-
end
|
583
|
-
end
|
584
|
-
|
585
|
-
if rra = side_effect.role_ref.leading_adjective
|
586
|
-
trace :matching, "Deleting matched leading adjective '#{rra}'#{side_effect.absorbed_precursors>0 ? " in #{side_effect.absorbed_precursors} precursors" : ""}, cost is #{side_effect.cost}"
|
587
|
-
side_effect.cancel_cost side_effect.absorbed_precursors
|
588
|
-
|
589
|
-
# These adjective(s) matched either an adjective here, or a precursor word, or both.
|
590
|
-
if a = phrase.leading_adjective
|
591
|
-
if a.size >= rra.size
|
592
|
-
a = a[0...-rra.size]
|
593
|
-
phrase.leading_adjective = a == '' ? nil : a
|
594
|
-
changed = true
|
595
|
-
end
|
596
|
-
elsif side_effect.absorbed_precursors > 0
|
597
|
-
# The following statement is incorrect. The absorbed adjective is what caused the match.
|
598
|
-
# This phrase is absorbing non-hyphenated adjective(s), which changes its binding
|
599
|
-
#phrase.leading_adjective =
|
600
|
-
@phrases.slice!(side_effect.num-side_effect.absorbed_precursors, side_effect.absorbed_precursors)*' '
|
601
|
-
changed = true
|
602
|
-
end
|
603
|
-
end
|
604
|
-
if changed
|
605
|
-
phrase.rebind context
|
606
|
-
end
|
607
|
-
|
608
|
-
end
|
609
|
-
end
|
610
|
-
end
|
611
|
-
|
612
|
-
# Make a new fact type with roles for this clause.
|
613
|
-
# Don't assign @fact_type; that will happen when the reading is added
|
614
|
-
def make_fact_type vocabulary
|
615
|
-
fact_type = vocabulary.constellation.FactType(:new)
|
616
|
-
trace :matching, "Making new fact type for #{@phrases.inspect}" do
|
617
|
-
@phrases.each do |phrase|
|
618
|
-
next unless phrase.respond_to?(:player)
|
619
|
-
phrase.role = vocabulary.constellation.Role(fact_type, fact_type.all_role.size, :object_type => phrase.player, :concept => :new)
|
620
|
-
phrase.role.role_name = phrase.role_name if phrase.role_name && phrase.role_name.is_a?(String)
|
621
|
-
end
|
622
|
-
end
|
623
|
-
fact_type
|
624
|
-
end
|
625
|
-
|
626
|
-
def make_reading vocabulary, fact_type
|
627
|
-
@fact_type = fact_type
|
628
|
-
constellation = vocabulary.constellation
|
629
|
-
@role_sequence = constellation.RoleSequence(:new)
|
630
|
-
reading_words = @phrases.clone
|
631
|
-
index = 0
|
632
|
-
trace :matching, "Making new reading for #{@phrases.inspect}" do
|
633
|
-
reading_words.map! do |phrase|
|
634
|
-
if phrase.respond_to?(:player)
|
635
|
-
# phrase.role will be set if this reading was used to make_fact_type.
|
636
|
-
# Otherwise we have to find the existing role via the Binding. This is pretty ugly.
|
637
|
-
unless phrase.role
|
638
|
-
# Find another binding for this phrase which already has a role_ref to the same fact type:
|
639
|
-
ref = phrase.binding.refs.detect{|ref| ref.role_ref && ref.role_ref.role.fact_type == fact_type}
|
640
|
-
role_ref = ref.role_ref
|
641
|
-
phrase.role = role_ref.role
|
642
|
-
end
|
643
|
-
rr = constellation.RoleRef(@role_sequence, index, :role => phrase.role)
|
644
|
-
phrase.role_ref = rr
|
645
|
-
|
646
|
-
if la = phrase.leading_adjective
|
647
|
-
# If we have used one or more adjective to match an existing reading, that has already been removed.
|
648
|
-
rr.leading_adjective = la
|
649
|
-
end
|
650
|
-
if ta = phrase.trailing_adjective
|
651
|
-
rr.trailing_adjective = ta
|
652
|
-
end
|
653
|
-
|
654
|
-
if phrase.value_constraint
|
655
|
-
raise "The role #{rr.inspect} already has a value constraint" if rr.role.role_value_constraint
|
656
|
-
phrase.value_constraint.constellation = fact_type.constellation
|
657
|
-
rr.role.role_value_constraint = phrase.value_constraint.compile
|
658
|
-
end
|
659
|
-
|
660
|
-
index += 1
|
661
|
-
"{#{index-1}}"
|
662
|
-
else
|
663
|
-
phrase
|
664
|
-
end
|
665
|
-
end
|
666
|
-
if existing = @fact_type.all_reading.detect{|r|
|
667
|
-
r.text == reading_words*' ' and
|
668
|
-
r.role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type} ==
|
669
|
-
role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type}
|
670
|
-
}
|
671
|
-
existing
|
672
|
-
#raise "Reading '#{existing.expand}' already exists, so why are we creating a duplicate?"
|
673
|
-
end
|
674
|
-
r = constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_words*" ", :is_negative => (certainty == false))
|
675
|
-
r
|
676
|
-
end
|
677
|
-
end
|
678
|
-
|
679
|
-
# When we match an existing reading, we might have matched using additional adjectives.
|
680
|
-
# These adjectives have been removed from the phrases. If there are any remaining adjectives,
|
681
|
-
# we need to make a new RoleSequence, otherwise we can use the existing one.
|
682
|
-
def adjust_for_match
|
683
|
-
return unless @applied_side_effects
|
684
|
-
new_role_sequence_needed = @applied_side_effects.residual_adjectives
|
685
|
-
|
686
|
-
role_phrases = []
|
687
|
-
reading_words = []
|
688
|
-
new_role_sequence_needed = false
|
689
|
-
@phrases.each do |phrase|
|
690
|
-
if phrase.respond_to?(:player)
|
691
|
-
role_phrases << phrase
|
692
|
-
reading_words << "{#{phrase.role_ref.ordinal}}"
|
693
|
-
if phrase.role_name != phrase.role_ref.role.role_name ||
|
694
|
-
phrase.leading_adjective ||
|
695
|
-
phrase.trailing_adjective
|
696
|
-
trace :matching, "phrase in matched clause has residual adjectives or role name, so needs a new role_sequence" if @fact_type.all_reading.size > 0
|
697
|
-
new_role_sequence_needed = true
|
698
|
-
end
|
699
|
-
else
|
700
|
-
reading_words << phrase
|
701
|
-
false
|
702
|
-
end
|
703
|
-
end
|
704
|
-
|
705
|
-
trace :matching, "Clause '#{reading_words*' '}' #{new_role_sequence_needed ? 'requires' : 'does not require'} a new Role Sequence"
|
706
|
-
|
707
|
-
constellation = @fact_type.constellation
|
708
|
-
reading_text = reading_words*" "
|
709
|
-
if new_role_sequence_needed
|
710
|
-
@role_sequence = constellation.RoleSequence(:new)
|
711
|
-
extra_adjectives = []
|
712
|
-
role_phrases.each_with_index do |rp, i|
|
713
|
-
role_ref = constellation.RoleRef(@role_sequence, i, :role => rp.role_ref.role)
|
714
|
-
if a = rp.leading_adjective
|
715
|
-
role_ref.leading_adjective = a
|
716
|
-
extra_adjectives << a+"-"
|
717
|
-
end
|
718
|
-
if a = rp.trailing_adjective
|
719
|
-
role_ref.trailing_adjective = a
|
720
|
-
extra_adjectives << "-"+a
|
721
|
-
end
|
722
|
-
if (a = rp.role_name) && (e = rp.role_ref.role.role_name) && a != e
|
723
|
-
raise "Can't create new reading '#{reading_text}' for '#{reading.expand}' with alternate role name #{a}"
|
724
|
-
extra_adjectives << "(as #{a})"
|
725
|
-
end
|
726
|
-
end
|
727
|
-
trace :matching, "Making new role sequence for new reading #{reading_text} due to #{extra_adjectives.inspect}"
|
728
|
-
else
|
729
|
-
# Use existing RoleSequence
|
730
|
-
@role_sequence = role_phrases[0].role_ref.role_sequence
|
731
|
-
if @role_sequence.all_reading.detect{|r| r.text == reading_text }
|
732
|
-
trace :matching, "No need to re-create identical reading for #{reading_text}"
|
733
|
-
return @role_sequence
|
734
|
-
else
|
735
|
-
trace :matching, "Using existing role sequence for new reading '#{reading_text}'"
|
736
|
-
end
|
737
|
-
end
|
738
|
-
if @fact_type.all_reading.
|
739
|
-
detect do |r|
|
740
|
-
r.text == reading_text and
|
741
|
-
r.role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type} ==
|
742
|
-
@role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type}
|
743
|
-
end
|
744
|
-
# raise "Reading '#{@reading.expand}' already exists, so why are we creating a duplicate (with #{extra_adjectives.inspect})?"
|
745
|
-
else
|
746
|
-
constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_text, :is_negative => (certainty == false))
|
747
|
-
end
|
748
|
-
@role_sequence
|
749
|
-
end
|
750
|
-
|
751
|
-
def make_embedded_constraints vocabulary
|
752
|
-
refs.each do |ref|
|
753
|
-
next unless ref.quantifier
|
754
|
-
# puts "Quantifier #{ref.inspect} not implemented as a presence constraint"
|
755
|
-
ref.make_embedded_presence_constraint vocabulary
|
756
|
-
end
|
757
|
-
|
758
|
-
if @qualifiers && @qualifiers.size > 0
|
759
|
-
# We shouldn't make a new ring constraint if there's already one over this ring.
|
760
|
-
existing_rcs =
|
761
|
-
@role_sequence.all_role_ref.map{|rr| rr.role.all_ring_constraint.to_a }.flatten.uniq
|
762
|
-
unless existing_rcs[0]
|
763
|
-
rc = RingConstraint.new(@role_sequence, @qualifiers)
|
764
|
-
rc.vocabulary = vocabulary
|
765
|
-
rc.constellation = vocabulary.constellation
|
766
|
-
rc.compile
|
767
|
-
else
|
768
|
-
# Ignore the fact that the ring might be of a different type.
|
769
|
-
end
|
770
|
-
|
771
|
-
# REVISIT: Check maybe and other qualifiers:
|
772
|
-
trace :constraint, "Need to make constraints for #{@qualifiers.inspect}" if @qualifiers.size > 0 or @certainty != true
|
773
|
-
end
|
774
|
-
end
|
775
|
-
|
776
|
-
def is_naked_object_type
|
777
|
-
@phrases.size == 1 && refs.size == 1
|
778
|
-
end
|
779
|
-
|
780
|
-
end
|
781
|
-
|
782
|
-
# An instance of ClauseMatchSideEffects is created when the compiler matches an existing fact type.
|
783
|
-
# It captures the details that have to be adjusted for the match to be regarded a success.
|
784
|
-
class ClauseMatchSideEffect
|
785
|
-
attr_reader :phrase, :role_ref, :num, :absorbed_precursors, :absorbed_followers, :common_supertype, :residual_adjectives
|
786
|
-
|
787
|
-
def initialize phrase, role_ref, num, absorbed_precursors, absorbed_followers, common_supertype, residual_adjectives
|
788
|
-
@phrase = phrase
|
789
|
-
@role_ref = role_ref
|
790
|
-
@num = num
|
791
|
-
@absorbed_precursors = absorbed_precursors
|
792
|
-
@absorbed_followers = absorbed_followers
|
793
|
-
@common_supertype = common_supertype
|
794
|
-
@residual_adjectives = residual_adjectives
|
795
|
-
@cancelled_cost = 0
|
796
|
-
trace :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
|
797
|
-
end
|
798
|
-
|
799
|
-
def cost
|
800
|
-
absorbed_precursors + absorbed_followers + (common_supertype ? 1 : 0) - @cancelled_cost
|
801
|
-
end
|
802
|
-
|
803
|
-
def cancel_cost c
|
804
|
-
@cancelled_cost += c
|
805
|
-
end
|
806
|
-
|
807
|
-
def to_s
|
808
|
-
"#{@phrase.inspect} absorbs #{@absorbed_precursors||0}/#{@absorbed_followers||0} at #{@num}#{@common_supertype && ' super '+@common_supertype.name}#{@residual_adjectives ? ' with residual adjectives' : ''}"
|
809
|
-
end
|
810
|
-
end
|
811
|
-
|
812
|
-
class ClauseMatchSideEffects
|
813
|
-
attr_reader :residual_adjectives
|
814
|
-
attr_reader :fact_type
|
815
|
-
attr_reader :role_side_effects # One array of values per Reference matched, in order
|
816
|
-
attr_reader :negated
|
817
|
-
attr_reader :optional
|
818
|
-
|
819
|
-
def initialize fact_type, clause, residual_adjectives, role_side_effects, negated = false
|
820
|
-
@fact_type = fact_type
|
821
|
-
@clause = clause
|
822
|
-
@residual_adjectives = residual_adjectives
|
823
|
-
@role_side_effects = role_side_effects
|
824
|
-
@negated = negated
|
825
|
-
end
|
826
|
-
|
827
|
-
def inspect
|
828
|
-
'side-effects are [' +
|
829
|
-
@role_side_effects.map{|r| r.to_s}*', ' +
|
830
|
-
']' +
|
831
|
-
"#{@negated ? ' negated' : ''}" +
|
832
|
-
"#{@residual_adjectives ? ' with residual adjectives' : ''}"
|
833
|
-
end
|
834
|
-
|
835
|
-
def apply_all &b
|
836
|
-
@role_side_effects.reverse.each{ |role_side_effect| b.call(*role_side_effect) }
|
837
|
-
end
|
838
|
-
|
839
|
-
def cost
|
840
|
-
c = 0
|
841
|
-
@role_side_effects.each do |side_effect|
|
842
|
-
c += side_effect.cost
|
843
|
-
end
|
844
|
-
c += 1 if @residual_adjectives
|
845
|
-
c += 2 if @negated
|
846
|
-
c
|
847
|
-
end
|
848
|
-
|
849
|
-
def describe
|
850
|
-
actual_effects =
|
851
|
-
@role_side_effects.map do |side_effect|
|
852
|
-
( [side_effect.common_supertype ? "supertype step over #{side_effect.common_supertype.name}" : nil] +
|
853
|
-
[side_effect.absorbed_precursors > 0 ? "absorbs #{side_effect.absorbed_precursors} preceding words" : nil] +
|
854
|
-
[side_effect.absorbed_followers > 0 ? "absorbs #{side_effect.absorbed_followers} following words" : nil] +
|
855
|
-
[@negated ? 'implicitly negated' : nil]
|
856
|
-
)
|
857
|
-
end.flatten.compact*','
|
858
|
-
actual_effects.empty? ? "no side effects" : actual_effects
|
859
|
-
end
|
860
|
-
end
|
861
|
-
|
862
|
-
class Reference
|
863
|
-
attr_reader :term, :quantifier, :function_call, :value_constraint, :literal, :nested_clauses
|
864
|
-
attr_accessor :leading_adjective, :trailing_adjective, :role_name
|
865
|
-
attr_accessor :player # What ObjectType does the Binding denote
|
866
|
-
attr_accessor :binding # What Binding for that ObjectType
|
867
|
-
attr_accessor :role # Which Role of this ObjectType
|
868
|
-
attr_accessor :role_ref # Which RoleRef to that Role
|
869
|
-
attr_accessor :clause # The clause that this Reference is part of
|
870
|
-
attr_accessor :objectification_of # If nested_clauses is set, this is the fact type it objectifies
|
871
|
-
attr_reader :embedded_presence_constraint # This refers to the ActiveFacts::Metamodel::PresenceConstraint
|
872
|
-
|
873
|
-
def initialize term, leading_adjective = nil, trailing_adjective = nil, quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, nested_clauses = nil
|
874
|
-
@term = term
|
875
|
-
@leading_adjective = leading_adjective
|
876
|
-
@trailing_adjective = trailing_adjective
|
877
|
-
@quantifier = quantifier
|
878
|
-
# @function_call = function_call # Not used or implemented
|
879
|
-
@role_name = role_name
|
880
|
-
@value_constraint = value_constraint
|
881
|
-
@literal = literal
|
882
|
-
@nested_clauses = nested_clauses
|
883
|
-
end
|
884
|
-
|
885
|
-
def inspect
|
886
|
-
to_s
|
887
|
-
end
|
888
|
-
|
889
|
-
def to_s
|
890
|
-
"{#{
|
891
|
-
@quantifier && @quantifier.inspect+' '
|
892
|
-
}#{
|
893
|
-
@leading_adjective && @leading_adjective.sub(/ |$/,'- ').sub(/ *$/,' ')
|
894
|
-
}#{
|
895
|
-
@term
|
896
|
-
}#{
|
897
|
-
@trailing_adjective && ' '+@trailing_adjective.sub(/(.* |^)/, '\1-')
|
898
|
-
}#{
|
899
|
-
@role_name and @role_name.is_a?(Integer) ? "(#{@role_name})" : " (as #{@role_name})"
|
900
|
-
}#{
|
901
|
-
@literal && ' '+@literal.inspect
|
902
|
-
}#{
|
903
|
-
@value_constraint && ' '+@value_constraint.to_s
|
904
|
-
}}"
|
905
|
-
end
|
906
|
-
|
907
|
-
def <=>(other)
|
908
|
-
( 4*(@term <=> other.term) +
|
909
|
-
2*((@leading_adjective||'') <=> (other.leading_adjective||'')) +
|
910
|
-
1*((@trailing_adjective||'') <=> (other.trailing_adjective||''))
|
911
|
-
) <=> 0
|
912
|
-
end
|
913
|
-
|
914
|
-
def includes_literals
|
915
|
-
@nested_clauses && @nested_clauses.detect{|nested| nested.includes_literals}
|
916
|
-
end
|
917
|
-
|
918
|
-
# We create value types for the results of arithmetic expressions, and they get assigned here:
|
919
|
-
def player=(player)
|
920
|
-
@player = player
|
921
|
-
end
|
922
|
-
|
923
|
-
def identify_players_with_role_name(context)
|
924
|
-
identify_player(context) if role_name
|
925
|
-
# Include players in nested clauses, if any
|
926
|
-
nested_clauses.each{|clause| clause.identify_players_with_role_name(context)} if nested_clauses
|
927
|
-
end
|
928
|
-
|
929
|
-
def identify_other_players context
|
930
|
-
identify_player context
|
931
|
-
end
|
932
|
-
|
933
|
-
def identify_player context
|
934
|
-
@player || begin
|
935
|
-
@player = context.object_type @term
|
936
|
-
raise "ObjectType #{@term} unrecognised" unless @player
|
937
|
-
context.player_by_role_name[@role_name] = player if @role_name
|
938
|
-
@player
|
939
|
-
end
|
940
|
-
end
|
941
|
-
|
942
|
-
def uses_role_name?
|
943
|
-
@term != @player.name
|
944
|
-
end
|
945
|
-
|
946
|
-
def key
|
947
|
-
if @role_name
|
948
|
-
key = [@term, @role_name] # Defines a role name
|
949
|
-
elsif uses_role_name?
|
950
|
-
key = [@player.name, @term] # Uses a role name
|
951
|
-
else
|
952
|
-
l = @leading_adjective
|
953
|
-
t = @trailing_adjective
|
954
|
-
key = [!l || l.empty? ? nil : l, @term, !t || t.empty? ? nil : t]
|
955
|
-
end
|
956
|
-
key += [:literal, literal.literal] if @literal
|
957
|
-
key
|
958
|
-
end
|
959
|
-
|
960
|
-
def bind context
|
961
|
-
@nested_clauses.each{|c| c.bind context} if @nested_clauses
|
962
|
-
if role_name = @role_name
|
963
|
-
# Omit these tests to see if anything evil eventuates:
|
964
|
-
#if @leading_adjective || @trailing_adjective
|
965
|
-
# raise "Role reference may not have adjectives if it defines a role name or uses a subscript: #{inspect}"
|
966
|
-
#end
|
967
|
-
else
|
968
|
-
if uses_role_name?
|
969
|
-
if @leading_adjective || @trailing_adjective
|
970
|
-
raise "Role reference may not have adjectives if it uses a role name: #{inspect}"
|
971
|
-
end
|
972
|
-
role_name = @term
|
973
|
-
end
|
974
|
-
end
|
975
|
-
k = key
|
976
|
-
@binding = context.bindings[k]
|
977
|
-
if !@binding
|
978
|
-
if !literal
|
979
|
-
# Find a binding that has a literal, and bind to it if it's the only one
|
980
|
-
candidates = context.bindings.map do |binding_key, binding|
|
981
|
-
binding_key[0...k.size] == k &&
|
982
|
-
binding_key[-2] == :literal ? binding : nil
|
983
|
-
end.compact
|
984
|
-
raise "Uncertain binding reference for #{to_s}, could be any of #{candidates.inspect}" if candidates.size > 1
|
985
|
-
@binding = candidates[0]
|
986
|
-
else
|
987
|
-
# New binding has a literal, look for one without:
|
988
|
-
@binding = context.bindings[k[0...-2]]
|
989
|
-
end
|
990
|
-
end
|
991
|
-
|
992
|
-
if !@binding
|
993
|
-
@binding = Binding.new(@player, role_name)
|
994
|
-
context.bindings[k] = @binding
|
995
|
-
end
|
996
|
-
@binding.add_ref self
|
997
|
-
@binding
|
998
|
-
end
|
999
|
-
|
1000
|
-
def unbind context
|
1001
|
-
# The key has changed.
|
1002
|
-
@binding.delete_ref self
|
1003
|
-
if @binding.refs.empty?
|
1004
|
-
# Remove the binding from the context if this was the last reference
|
1005
|
-
context.bindings.delete_if {|k,v| v == @binding }
|
1006
|
-
end
|
1007
|
-
@binding = nil
|
1008
|
-
end
|
1009
|
-
|
1010
|
-
def rebind(context)
|
1011
|
-
unbind context
|
1012
|
-
bind context
|
1013
|
-
end
|
1014
|
-
|
1015
|
-
def rebind_to(context, other_ref)
|
1016
|
-
trace :binding, "Rebinding #{inspect} to #{other_ref.inspect}"
|
1017
|
-
|
1018
|
-
old_binding = binding # Remember to move all refs across
|
1019
|
-
unbind(context)
|
1020
|
-
|
1021
|
-
new_binding = other_ref.binding
|
1022
|
-
[self, *old_binding.refs].each do |ref|
|
1023
|
-
ref.binding = new_binding
|
1024
|
-
new_binding.add_ref ref
|
1025
|
-
end
|
1026
|
-
old_binding.rebound_to = new_binding
|
1027
|
-
end
|
1028
|
-
|
1029
|
-
# These are called when we successfully match a fact type reading that has relevant adjectives:
|
1030
|
-
def wipe_leading_adjective
|
1031
|
-
@leading_adjective = nil
|
1032
|
-
end
|
1033
|
-
|
1034
|
-
def wipe_trailing_adjective
|
1035
|
-
@trailing_adjective = nil
|
1036
|
-
end
|
1037
|
-
|
1038
|
-
def find_pc_over_roles(roles)
|
1039
|
-
return nil if roles.size == 0 # Safeguard; this would chuck an exception otherwise
|
1040
|
-
roles[0].all_role_ref.each do |role_ref|
|
1041
|
-
next if role_ref.role_sequence.all_role_ref.map(&:role) != roles
|
1042
|
-
pc = role_ref.role_sequence.all_presence_constraint.single # Will return nil if there's more than one.
|
1043
|
-
#puts "Existing PresenceConstraint matches those roles!" if pc
|
1044
|
-
return pc if pc
|
1045
|
-
end
|
1046
|
-
nil
|
1047
|
-
end
|
1048
|
-
|
1049
|
-
def make_embedded_presence_constraint vocabulary
|
1050
|
-
raise "No Role for embedded_presence_constraint" unless @role_ref
|
1051
|
-
fact_type = @role_ref.role.fact_type
|
1052
|
-
constellation = vocabulary.constellation
|
1053
|
-
|
1054
|
-
trace :constraint, "Processing embedded constraint #{@quantifier.inspect} on #{@role_ref.role.object_type.name} in #{fact_type.describe}" do
|
1055
|
-
# Preserve the role order of the clause, excluding this role:
|
1056
|
-
constrained_roles = (@clause.refs-[self]).map{|vr| vr.role_ref.role}
|
1057
|
-
if constrained_roles.empty?
|
1058
|
-
trace :constraint, "Quantifier over unary role has no effect"
|
1059
|
-
return
|
1060
|
-
end
|
1061
|
-
constraint = find_pc_over_roles(constrained_roles)
|
1062
|
-
if constraint
|
1063
|
-
raise "Conflicting maximum frequency for constraint" if constraint.max_frequency && constraint.max_frequency != @quantifier.max
|
1064
|
-
trace :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
|
1065
|
-
constraint.max_frequency = @quantifier.max
|
1066
|
-
raise "Conflicting minimum frequency for constraint" if constraint.min_frequency && constraint.min_frequency != @quantifier.min
|
1067
|
-
trace :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
|
1068
|
-
constraint.min_frequency = @quantifier.min
|
1069
|
-
else
|
1070
|
-
role_sequence = constellation.RoleSequence(:new)
|
1071
|
-
constrained_roles.each_with_index do |constrained_role, i|
|
1072
|
-
role_ref = constellation.RoleRef(role_sequence, i, :role => constrained_role)
|
1073
|
-
end
|
1074
|
-
constraint = constellation.PresenceConstraint(
|
1075
|
-
:new,
|
1076
|
-
:vocabulary => vocabulary,
|
1077
|
-
:role_sequence => role_sequence,
|
1078
|
-
:is_mandatory => @quantifier.min && @quantifier.min > 0, # REVISIT: Check "maybe" qualifier?
|
1079
|
-
:max_frequency => @quantifier.max,
|
1080
|
-
:min_frequency => @quantifier.min
|
1081
|
-
)
|
1082
|
-
if @quantifier.pragmas
|
1083
|
-
@quantifier.pragmas.each do |p|
|
1084
|
-
constellation.ConceptAnnotation(:concept => constraint.concept, :mapping_annotation => p)
|
1085
|
-
end
|
1086
|
-
end
|
1087
|
-
trace :constraint, "Made new embedded PC GUID=#{constraint.concept.guid} min=#{@quantifier.min.inspect} max=#{@quantifier.max.inspect} over #{(e = fact_type.entity_type) ? e.name : role_sequence.describe} in #{fact_type.describe}"
|
1088
|
-
@quantifier.enforcement.compile(constellation, constraint) if @quantifier.enforcement
|
1089
|
-
@embedded_presence_constraint = constraint
|
1090
|
-
end
|
1091
|
-
constraint
|
1092
|
-
end
|
1093
|
-
|
1094
|
-
end
|
1095
|
-
|
1096
|
-
def result(context = nil)
|
1097
|
-
self
|
1098
|
-
end
|
1099
|
-
end
|
1100
|
-
|
1101
|
-
# REVISIT: This needs to handle annotations for some/that/which, etc.
|
1102
|
-
class Quantifier
|
1103
|
-
attr_accessor :enforcement
|
1104
|
-
attr_accessor :context_note
|
1105
|
-
attr_accessor :pragmas
|
1106
|
-
attr_reader :min, :max
|
1107
|
-
|
1108
|
-
def initialize min, max, enforcement = nil, context_note = nil, pragmas = nil
|
1109
|
-
@min = min
|
1110
|
-
@max = max
|
1111
|
-
@enforcement = enforcement
|
1112
|
-
@context_note = context_note
|
1113
|
-
@pragmas = pragmas
|
1114
|
-
end
|
1115
|
-
|
1116
|
-
def is_unique
|
1117
|
-
@max and @max == 1
|
1118
|
-
end
|
1119
|
-
|
1120
|
-
def is_mandatory
|
1121
|
-
@min and @min >= 1
|
1122
|
-
end
|
1123
|
-
|
1124
|
-
def is_zero
|
1125
|
-
@min == 0 and @max == 0
|
1126
|
-
end
|
1127
|
-
|
1128
|
-
def inspect
|
1129
|
-
"[#{@min}..#{@max}]#{
|
1130
|
-
@context_note && ' ' + @context_note.inspect
|
1131
|
-
}"
|
1132
|
-
end
|
1133
|
-
end
|
1134
|
-
|
1135
|
-
end
|
1136
|
-
end
|
1137
|
-
end
|