activefacts 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/Rakefile +2 -2
- data/bin/afgen +1 -1
- data/bin/cql +118 -27
- data/examples/CQL/Insurance.cql +2 -2
- data/examples/CQL/Metamodel.cql +3 -3
- data/examples/CQL/SchoolActivities.cql +1 -1
- data/examples/CQL/Warehousing.cql +5 -4
- data/lib/activefacts/cql.rb +1 -1
- data/lib/activefacts/cql/Language/English.treetop +2 -1
- data/lib/activefacts/cql/compiler.rb +6 -6
- data/lib/activefacts/cql/compiler/clause.rb +69 -46
- data/lib/activefacts/cql/compiler/constraint.rb +14 -14
- data/lib/activefacts/cql/compiler/entity_type.rb +24 -24
- data/lib/activefacts/cql/compiler/fact.rb +40 -27
- data/lib/activefacts/cql/compiler/fact_type.rb +16 -16
- data/lib/activefacts/cql/compiler/query.rb +12 -12
- data/lib/activefacts/cql/compiler/shared.rb +9 -0
- data/lib/activefacts/cql/compiler/value_type.rb +4 -4
- data/lib/activefacts/cql/parser.rb +9 -9
- data/lib/activefacts/generate/cql.rb +41 -20
- data/lib/activefacts/generate/helpers/oo.rb +33 -70
- data/lib/activefacts/generate/helpers/ordered.rb +61 -87
- data/lib/activefacts/generate/ruby.rb +12 -72
- data/lib/activefacts/generate/transform/surrogate.rb +13 -13
- data/lib/activefacts/input/orm.rb +72 -71
- data/lib/activefacts/persistence/columns.rb +66 -31
- data/lib/activefacts/persistence/foreignkey.rb +6 -6
- data/lib/activefacts/persistence/index.rb +12 -12
- data/lib/activefacts/persistence/object_type.rb +15 -12
- data/lib/activefacts/persistence/reference.rb +20 -18
- data/lib/activefacts/persistence/tables.rb +40 -36
- data/lib/activefacts/support.rb +69 -123
- data/lib/activefacts/version.rb +2 -2
- data/lib/activefacts/vocabulary/extensions.rb +42 -39
- data/lib/activefacts/vocabulary/metamodel.rb +11 -1
- data/lib/activefacts/vocabulary/verbaliser.rb +28 -28
- data/spec/cql/contractions_spec.rb +1 -1
- data/spec/cql/entity_type_spec.rb +1 -1
- data/spec/cql/fact_type_matching_spec.rb +3 -3
- data/spec/cql/role_matching_spec.rb +4 -4
- data/spec/cql/samples_spec.rb +2 -2
- data/spec/cql_cql_spec.rb +1 -1
- data/spec/helpers/array_matcher.rb +1 -1
- data/spec/norma_ruby_sql_spec.rb +2 -2
- data/spec/norma_tables_spec.rb +3 -2
- metadata +47 -68
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
Yzk1MmFlZTJhZTZiOTY0OGMyZTNjYzExM2Q5OTJhMzQwNTNhNjQ2Nw==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6e791a369dca69a80ccd666e7eec9441e9f00483
|
4
|
+
data.tar.gz: d894b07ab82cfdf942a8e93561fa86a8a3bedd8a
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
ODZhOWRkZDA4ZmIzNDNmYjQzZTMzMzQxYTA1Y2E5MWFkOTgyODM0NTc5MDMw
|
11
|
-
YzA3OWE0ZGQ3ZmFiNTlhNjE3MzlmNzM2MGNlYjY0YTgzZmVmNmI=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
YzJmNzk1OGVmMTM3Y2E0YzE4MzJhMzg2YzVmMWE2ZjVkYjA2Y2JlODAxY2M5
|
14
|
-
MzE1ZGIzZDRiYzc4YjBiZTUwZDZlOWJiNjZmZjA0ZWQxMzJmNzFlODMwOTI1
|
15
|
-
MjM0MjAyMDFhNjgzY2I1ZTc3ZDU0OTViYzE2MmMzMTQ0Mzg0OTI=
|
6
|
+
metadata.gz: c5f05a7b8b3b116f478b55819baba8162b12c8807122f9825474c93819a9f0f3255dd5c2040eaf54dd59e6e445c5572bcd1d82267d3b9312af140ae8b51d8ce5
|
7
|
+
data.tar.gz: e6d40c1c950bbca20c96700002c88fa640c1a019c3dfe72b4e88da5a549d401d6d5d87eead9c20d6ced53c688750f7458f2ae598181a6a9c02d84775c6407ca4
|
data/Rakefile
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
3
|
require 'fileutils'
|
4
|
+
$: << "lib"
|
4
5
|
require File.dirname(__FILE__) + '/lib/activefacts'
|
5
6
|
|
6
7
|
def File.read_utf(path)
|
@@ -38,8 +39,7 @@ and object models in SQL, Ruby and other languages.
|
|
38
39
|
|
39
40
|
gem.files = File.open("Manifest.txt"){|f| f.read.split(/\n/)}
|
40
41
|
gem.executables = gem.files.grep(%r{^bin/}).map{|f| f.sub('bin/', '')}
|
41
|
-
gem.rdoc_options =
|
42
|
-
# RDoc used to have these options: -A has_one -A one_to_one -A maybe
|
42
|
+
gem.rdoc_options = # RDoc used to have these options: -A has_one -A one_to_one -A maybe
|
43
43
|
%w{
|
44
44
|
-x lib/activefacts/cql/.*.rb
|
45
45
|
-x lib/activefacts/vocabulary/.*.rb
|
data/bin/afgen
CHANGED
data/bin/cql
CHANGED
@@ -7,16 +7,6 @@
|
|
7
7
|
#
|
8
8
|
require 'readline'
|
9
9
|
|
10
|
-
# Load the ruby debugger before everything else, if requested
|
11
|
-
#if d = ENV['DEBUG'] and d.split(/,/).include?('debug')
|
12
|
-
# begin
|
13
|
-
# require 'ruby-debug'
|
14
|
-
# Debugger.start(:post_mortem => true) # Stop when an exception is thrown, but before it's rescued
|
15
|
-
# rescue LoadError
|
16
|
-
# # Ok, no debugger, tough luck.
|
17
|
-
# end
|
18
|
-
#end
|
19
|
-
|
20
10
|
require 'activefacts'
|
21
11
|
require 'activefacts/cql/compiler'
|
22
12
|
|
@@ -28,6 +18,11 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
|
|
28
18
|
self.root = :definition
|
29
19
|
end
|
30
20
|
|
21
|
+
def add_logger logger
|
22
|
+
@constellation.loggers << logger
|
23
|
+
@constellation.loggers.uniq!
|
24
|
+
end
|
25
|
+
|
31
26
|
def list_instances name
|
32
27
|
if (name.is_a?(ActiveFacts::Metamodel::ObjectType))
|
33
28
|
name.all_instance
|
@@ -40,7 +35,7 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
|
|
40
35
|
|
41
36
|
def toggle_trace words
|
42
37
|
words.each { |word|
|
43
|
-
puts "#{word} #{
|
38
|
+
puts "#{word} #{trace.toggle(word) ? "en" : "dis"}abled"
|
44
39
|
}
|
45
40
|
end
|
46
41
|
|
@@ -79,7 +74,7 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
|
|
79
74
|
case cmd = words.shift
|
80
75
|
when "/trace"
|
81
76
|
if words.empty?
|
82
|
-
puts
|
77
|
+
puts trace_keys*", "
|
83
78
|
else
|
84
79
|
toggle_trace words
|
85
80
|
end
|
@@ -89,6 +84,13 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
|
|
89
84
|
when "/highlight"
|
90
85
|
@show_highlight = !@show_highlight
|
91
86
|
puts "Will #{@show_highlight ? "" : "not "}show the highlighted parse"
|
87
|
+
when "/log"
|
88
|
+
@logger ||= proc {|*a|
|
89
|
+
unless @undoing
|
90
|
+
print "LOG: "; p a
|
91
|
+
end
|
92
|
+
}
|
93
|
+
add_logger @logger
|
92
94
|
when "/timings"
|
93
95
|
$show_timings = !$show_timings
|
94
96
|
puts "Will #{$show_timings ? "" : "not "}show command timings"
|
@@ -103,13 +105,13 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
|
|
103
105
|
end
|
104
106
|
when "/load"
|
105
107
|
load_file(words*' ')
|
108
|
+
when "/quit"
|
109
|
+
exit
|
110
|
+
when "/help", "/"
|
111
|
+
help words
|
106
112
|
else
|
107
|
-
|
108
|
-
|
109
|
-
else
|
110
|
-
puts "Unknown metacommand #{line}?"
|
111
|
-
help
|
112
|
-
end
|
113
|
+
puts "Unknown metacommand #{line}?"
|
114
|
+
help
|
113
115
|
end
|
114
116
|
end
|
115
117
|
|
@@ -124,17 +126,57 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
|
|
124
126
|
$stderr.puts e.message
|
125
127
|
rescue => e
|
126
128
|
puts e.message
|
127
|
-
puts "\t#{e.backtrace*"\n\t"}" if
|
129
|
+
puts "\t#{e.backtrace*"\n\t"}" if trace :exception
|
128
130
|
end
|
129
131
|
end
|
130
132
|
|
131
|
-
def process_query(
|
132
|
-
|
133
|
-
|
133
|
+
def process_query(query)
|
134
|
+
result_projection = {} # A set of {hash of Variable to Instance}
|
135
|
+
query_block =
|
136
|
+
proc do |unbound_variables = [], bindings = {}|
|
137
|
+
# unbound_variables is an array of the remaining dimensions of the search
|
138
|
+
# bindings is a hash of Varable to Instance
|
139
|
+
next_variable = unbound_variables[0]
|
140
|
+
if next_variable
|
141
|
+
ins = next_variable.object_type.all_instance
|
142
|
+
trace :result, "Query search through #{ins.size} #{next_variable.role_name || next_variable.object_type.name}" do
|
143
|
+
next_variable.object_type.all_instance.each do |instance|
|
144
|
+
trace :result, "Considering #{instance.verbalise}"
|
145
|
+
# REVISIT: Check whether this instance satisfies the query steps that contain the bound variables
|
146
|
+
(b = bindings.dup).store(next_variable, instance)
|
147
|
+
result_projection[b] = true
|
148
|
+
query_block.call(unbound_variables[1..-1], b)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
unbound_variables = query.all_variable.to_a
|
155
|
+
bound_variables = {}
|
156
|
+
unbound_variables.reject!{|v| v.value != nil ? bound_variables[v] = v.value : nil }
|
157
|
+
print 'bound variables: '; p bound_variables.keys.map{|v| [v.object_type.name, v.value.inspect] }
|
158
|
+
print 'unbound variables: '; p unbound_variables.map{|v| v.object_type.name}
|
159
|
+
|
160
|
+
query_block.call(unbound_variables, {})
|
161
|
+
result_projection.map do |result, _|
|
162
|
+
puts(result.map{|v, i| i.verbalise }.join(', '))
|
163
|
+
end
|
164
|
+
|
165
|
+
=begin
|
166
|
+
if query.all_variable.size == 1 and
|
167
|
+
(v = query.all_variable.single).all_play.map{|p| p.all_step.to_a}.flatten.uniq.size == 0
|
168
|
+
if v.object_type.all_instance.size == 0
|
169
|
+
puts "There are no instances of #{v.object_type.name}"
|
170
|
+
else
|
171
|
+
list_instances(v.object_type)
|
172
|
+
end
|
134
173
|
else
|
135
|
-
# REVISIT: Identify all the unbound
|
136
|
-
puts "Can't yet process complex queries (involving #{
|
174
|
+
# REVISIT: Identify all the unbound Variables, and do a nested-loops iteration checking all Steps
|
175
|
+
puts "Can't yet process complex queries (involving #{
|
176
|
+
query.all_variable.map{|v| v.object_type.name}
|
177
|
+
}*', '})"
|
137
178
|
end
|
179
|
+
=end
|
138
180
|
end
|
139
181
|
|
140
182
|
def process(statement)
|
@@ -148,15 +190,64 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
|
|
148
190
|
end
|
149
191
|
rescue => e
|
150
192
|
puts e
|
151
|
-
puts "\t"+e.backtrace*"\n\t" if
|
193
|
+
puts "\t"+e.backtrace*"\n\t" if trace :exception
|
152
194
|
end
|
153
195
|
end
|
154
196
|
|
197
|
+
def start_log
|
198
|
+
@constellation.loggers << proc{|*a| self.log(*a) }
|
199
|
+
@logged = []
|
200
|
+
end
|
201
|
+
|
202
|
+
def log *a
|
203
|
+
if @undoing
|
204
|
+
print 'UNDO: '; p a
|
205
|
+
else
|
206
|
+
@logged << a
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def undo
|
211
|
+
puts '>'*30+' UNDO '+'<'*30
|
212
|
+
@undoing = true
|
213
|
+
@logged.reverse.each do |a|
|
214
|
+
case a[0]
|
215
|
+
when :assert
|
216
|
+
action, klass, identifying_role_values = *a
|
217
|
+
# puts "retracting #{klass.basename} #{identifying_role_values.inspect}"
|
218
|
+
object = @constellation.instances[klass][identifying_role_values]
|
219
|
+
object && object.retract
|
220
|
+
|
221
|
+
when :retract
|
222
|
+
action, klass, identifying_role_values = *a
|
223
|
+
# puts "reasserting #{klass.basename}(#{identifying_role_values.inspect})"
|
224
|
+
@constellation.assert(klass, identifying_role_values)
|
225
|
+
|
226
|
+
when :assign
|
227
|
+
action, klass, role, object_id, old, new = *a
|
228
|
+
# puts "de-assigning #{klass.basename}(#{object_id.inspect}).#{role.name} from #{new.inspect} back to #{old.inspect}"
|
229
|
+
object = @constellation.instances[klass][object_id]
|
230
|
+
object.send(role.setter, old)
|
231
|
+
|
232
|
+
else raise "Unexpected log action #{a[0]}"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
@undoing = false
|
236
|
+
end
|
237
|
+
|
155
238
|
def compile_definition ast
|
156
239
|
# Accumulate the results:
|
157
240
|
p ast if @show_tree
|
158
241
|
puts highlight_tree(ast.tree) if @show_highlight
|
159
|
-
|
242
|
+
begin
|
243
|
+
start_log
|
244
|
+
result = super
|
245
|
+
rescue => e
|
246
|
+
undo
|
247
|
+
raise
|
248
|
+
ensure
|
249
|
+
@constellation.loggers.pop
|
250
|
+
end
|
160
251
|
@results += Array(result)
|
161
252
|
result
|
162
253
|
end
|
@@ -196,7 +287,7 @@ Meta-commands are:
|
|
196
287
|
/root rule\t\tParse just a fragment of a CQL statement, matching syntax rule only
|
197
288
|
/timings\t\t\tDisplay the elapsed time to execute each command
|
198
289
|
/tree\t\t\tDisplay the abstract syntax tree from each statement parsed
|
199
|
-
/trace key\t\tToggle
|
290
|
+
/trace key\t\tToggle tracing key, or list available keys
|
200
291
|
}
|
201
292
|
else
|
202
293
|
words.each do |word|
|
data/examples/CQL/Insurance.cql
CHANGED
@@ -218,14 +218,14 @@ Person was Driving,
|
|
218
218
|
Driving was by one Person;
|
219
219
|
|
220
220
|
Driving Charge is where
|
221
|
-
Driving resulted in one Charge;
|
221
|
+
Driving resulted in at most one Charge;
|
222
222
|
Driving Charge is a warning;
|
223
223
|
|
224
224
|
Finance Institution is a kind of Company;
|
225
225
|
Vehicle is subject to finance with at most one Finance Institution;
|
226
226
|
|
227
227
|
Hospitalization is where
|
228
|
-
Driving resulted in driver taken to one Hospital;
|
228
|
+
Driving resulted in driver taken to at most one Hospital;
|
229
229
|
Hospitalization resulted in at most one blood-Test Result;
|
230
230
|
|
231
231
|
Insured is a kind of Party;
|
data/examples/CQL/Metamodel.cql
CHANGED
@@ -173,9 +173,9 @@ Name is of at most one Unit,
|
|
173
173
|
Unit is called one Name;
|
174
174
|
Unit has at most one Coefficient;
|
175
175
|
Unit has at most one Offset;
|
176
|
-
Unit has plural-Name; // Avoid ambiguity; this is a new fact type
|
177
|
-
Unit has at most one plural-Name,
|
178
|
-
plural-Name is of at most one Unit;
|
176
|
+
Unit (as Plural Named Unit) has plural-Name; // Avoid ambiguity; this is a new fact type
|
177
|
+
Unit (as Plural Named Unit) has at most one plural-Name,
|
178
|
+
plural-Name is of at most one Plural Named Unit;
|
179
179
|
Unit is fundamental;
|
180
180
|
|
181
181
|
Value is identified by Literal and Value is literal string and Unit where
|
@@ -22,7 +22,7 @@ Student is enrolled in one School;
|
|
22
22
|
|
23
23
|
Student Participation is where
|
24
24
|
Student represents School in Activity,
|
25
|
-
Student participates in Activity
|
25
|
+
Student participates in Activity as representative of one School;
|
26
26
|
|
27
27
|
/*
|
28
28
|
* Constraints:
|
@@ -82,19 +82,20 @@ Transfer Request is from one Warehouse (as From Warehouse);
|
|
82
82
|
Transfer Request is to one Warehouse (as To Warehouse);
|
83
83
|
Warehouse contains at least one Bin;
|
84
84
|
|
85
|
+
Back Order Allocation is where
|
86
|
+
Purchase Order Item is allocated to Sales Order Item;
|
87
|
+
Back Order Allocation is for one Quantity;
|
88
|
+
|
85
89
|
Customer is a kind of Party;
|
86
90
|
Customer made Sales Order,
|
87
91
|
Sales Order was made by one Customer;
|
88
92
|
|
89
|
-
Direct Order Match is where
|
90
|
-
Purchase Order Item matches Sales Order Item;
|
91
|
-
|
92
93
|
/*
|
93
94
|
* Constraints:
|
94
95
|
*/
|
95
96
|
either Dispatch Item is for Transfer Request or Dispatch Item is for Sales Order Item but not both;
|
96
97
|
either Received Item is for Purchase Order Item or Received Item is for Transfer Request but not both;
|
97
|
-
Purchase Order Item
|
98
|
+
Purchase Order Item is allocated to Sales Order Item
|
98
99
|
only if Purchase Order Item is for Product that is in Sales Order Item;
|
99
100
|
each Bin occurs at most one time in
|
100
101
|
Warehouse contains Bin;
|
data/lib/activefacts/cql.rb
CHANGED
@@ -155,6 +155,7 @@ module ActiveFacts
|
|
155
155
|
# "that" on the other hand means that this role player was *previously* designated using "some".
|
156
156
|
# These distinctions are lost here
|
157
157
|
/ that s { def value; nil; end }
|
158
|
+
/ which s { def value; nil; end }
|
158
159
|
/ one s { def value; [1, 1]; end }
|
159
160
|
/ no s { def value; [0, 0]; end }
|
160
161
|
/ exactly s quantity { def value; q = quantity.value; [q, q]; end }
|
@@ -264,7 +265,7 @@ module ActiveFacts
|
|
264
265
|
rule in 'in' !alphanumeric end
|
265
266
|
rule import 'import' !alphanumeric end
|
266
267
|
rule independent 'independent' !alphanumeric end
|
267
|
-
rule stronglyintransitive '
|
268
|
+
rule stronglyintransitive 'strongly' s 'intransitive' !alphanumeric end
|
268
269
|
rule intransitive 'intransitive' !alphanumeric end
|
269
270
|
rule irreflexive 'irreflexive' !alphanumeric end
|
270
271
|
rule is 'is' !alphanumeric end
|
@@ -29,7 +29,7 @@ module ActiveFacts
|
|
29
29
|
super *a
|
30
30
|
@constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
|
31
31
|
@language = nil
|
32
|
-
|
32
|
+
trace :file, "Parsing '#{@filename}'"
|
33
33
|
end
|
34
34
|
|
35
35
|
def compile_file filename
|
@@ -64,16 +64,16 @@ module ActiveFacts
|
|
64
64
|
# The syntax tree created from each parsed CQL statement gets passed to the block.
|
65
65
|
# parse_all returns an array of the block's non-nil return values.
|
66
66
|
ok = parse_all(@string, :definition) do |node|
|
67
|
-
|
67
|
+
trace :parse, "Parsed '#{node.text_value.gsub(/\s+/,' ').strip}'" do
|
68
68
|
begin
|
69
69
|
ast = node.ast
|
70
70
|
next unless ast
|
71
|
-
|
71
|
+
trace :ast, ast.inspect
|
72
72
|
ast.tree = node
|
73
73
|
ast.constellation = @constellation
|
74
74
|
ast.vocabulary = @vocabulary
|
75
75
|
value = compile_definition ast
|
76
|
-
|
76
|
+
trace :definition, "Compiled to #{value.is_a?(Array) ? value.map{|v| v.verbalise}*', ' : value.verbalise}" if value
|
77
77
|
@vocabulary = value if ast.is_a?(Compiler::Vocabulary)
|
78
78
|
rescue => e
|
79
79
|
# Augment the exception message, but preserve the backtrace
|
@@ -122,8 +122,8 @@ module ActiveFacts
|
|
122
122
|
|
123
123
|
def unit? s
|
124
124
|
name = @constellation.Name[s]
|
125
|
-
units = (!name ? [] : Array(name.unit) + Array(name.
|
126
|
-
|
125
|
+
units = (!name ? [] : Array(name.unit) + Array(name.plural_named_unit)).uniq
|
126
|
+
trace :units, "Looking for unit #{s}, got #{units.map{|u|u.name}.inspect}"
|
127
127
|
units.size > 0
|
128
128
|
end
|
129
129
|
|
@@ -108,6 +108,8 @@ module ActiveFacts
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
|
+
# This method is used in matching unary fact types in entity identification
|
112
|
+
# It disregards literals, which are not allowed in this context.
|
111
113
|
def phrases_match(phrases)
|
112
114
|
@phrases.zip(phrases).each do |mine, theirs|
|
113
115
|
return false if mine.is_a?(Reference) != theirs.is_a?(Reference)
|
@@ -135,7 +137,7 @@ module ActiveFacts
|
|
135
137
|
changed_conjunction = (lcc = context.left_contraction_conjunction) && lcc != conjunction
|
136
138
|
if context.left_contraction_allowed && (new_conjunction || changed_conjunction)
|
137
139
|
# Conjunctions are that/who, where, comparison-operator, ','
|
138
|
-
|
140
|
+
trace :matching, "A left contraction will be against #{self.inspect}, conjunction is #{conjunction.inspect}"
|
139
141
|
context.left_contractable_clause = self
|
140
142
|
left_contract_this_onto = nil # Can't left-contract this clause
|
141
143
|
end
|
@@ -162,7 +164,7 @@ module ActiveFacts
|
|
162
164
|
vrs.unshift contracted_role
|
163
165
|
contracted_left = true
|
164
166
|
phrases = [contracted_role]+phrases
|
165
|
-
|
167
|
+
trace :matching, "Failed to match #{inspect}. Trying again using left contraction onto #{contraction_player.name}"
|
166
168
|
end
|
167
169
|
|
168
170
|
contract_right = proc do
|
@@ -176,7 +178,7 @@ module ActiveFacts
|
|
176
178
|
contracted_role.bind(context)
|
177
179
|
vrs.push contracted_role
|
178
180
|
phrases = phrases+[contracted_role]
|
179
|
-
|
181
|
+
trace :matching, "Failed to match #{inspect}. Trying again using right contraction onto #{contraction_player.name}"
|
180
182
|
end
|
181
183
|
|
182
184
|
begin
|
@@ -193,8 +195,8 @@ module ActiveFacts
|
|
193
195
|
|
194
196
|
player_names = players.map{|p| p.name}
|
195
197
|
|
196
|
-
|
197
|
-
|
198
|
+
trace :matching, "Looking for existing #{players.size}-ary fact types matching '#{inspect(phrases)}'" do
|
199
|
+
trace :matching, "Players are '#{player_names.inspect}'"
|
198
200
|
|
199
201
|
# Match existing fact types in nested clauses first:
|
200
202
|
# (not for contractions) REVISIT: Why not?
|
@@ -223,7 +225,7 @@ module ActiveFacts
|
|
223
225
|
player.subtypes_transitive).uniq
|
224
226
|
end
|
225
227
|
|
226
|
-
|
228
|
+
trace :matching, "Players must match '#{player_related_types.map{|pa| pa.map{|p|p.name}}.inspect}'"
|
227
229
|
|
228
230
|
start_obj = player_related_types[0] || [left_contract_this_onto.refs[-1].player]
|
229
231
|
# The candidate fact types have the right number of role players of related types.
|
@@ -238,7 +240,7 @@ module ActiveFacts
|
|
238
240
|
|
239
241
|
compatible_readings = role.fact_type.compatible_readings(player_related_types)
|
240
242
|
next unless compatible_readings.size > 0
|
241
|
-
|
243
|
+
trace :matching_fails, "These readings are compatible: #{compatible_readings.map(&:expand).inspect}"
|
242
244
|
true
|
243
245
|
end.
|
244
246
|
map{ |role| role.fact_type}
|
@@ -246,7 +248,7 @@ module ActiveFacts
|
|
246
248
|
|
247
249
|
# If there is more than one possible exact match (same adjectives) with different subyping, the implicit query is ambiguous and is not allowed
|
248
250
|
|
249
|
-
|
251
|
+
trace :matching, "Looking amongst #{candidate_fact_types.size} existing fact types for one matching #{left_insertion}'#{inspect}'#{right_insertion}" do
|
250
252
|
matches = {}
|
251
253
|
candidate_fact_types.map do |fact_type|
|
252
254
|
fact_type.all_reading.map do |reading|
|
@@ -266,7 +268,7 @@ module ActiveFacts
|
|
266
268
|
# Between equivalents, prefer the one without steps on the first role
|
267
269
|
(m = matches[match]).cost*2 + ((!(e = m.role_side_effects[0]) || e.cost) == 0 ? 0 : 1)
|
268
270
|
}
|
269
|
-
|
271
|
+
trace :matching_fails, "Found #{matches.size} valid matches#{matches.size > 0 ? ', best is '+best_matches[0].expand : ''}"
|
270
272
|
|
271
273
|
if matches.size > 1
|
272
274
|
first = matches[best_matches[0]]
|
@@ -284,14 +286,14 @@ module ActiveFacts
|
|
284
286
|
@reading = best_matches[0]
|
285
287
|
@side_effects = matches[@reading]
|
286
288
|
@fact_type = @side_effects.fact_type
|
287
|
-
|
289
|
+
trace :matching, "Matched '#{@fact_type.default_reading}'"
|
288
290
|
@phrases = phrases
|
289
291
|
apply_side_effects(context, @side_effects)
|
290
292
|
return @fact_type
|
291
293
|
end
|
292
294
|
|
293
295
|
end
|
294
|
-
|
296
|
+
trace :matching, "No fact type matched, candidates were '#{candidate_fact_types.map{|ft| ft.default_reading}*"', '"}'"
|
295
297
|
end
|
296
298
|
if left_contract_this_onto
|
297
299
|
if !contracted_left
|
@@ -341,21 +343,21 @@ module ActiveFacts
|
|
341
343
|
# REVISIT: the verbaliser will need to know about a negated step to a free variable
|
342
344
|
implicitly_negated = true if refs.detect{|ref| q = ref.quantifier and q.is_zero }
|
343
345
|
|
344
|
-
|
346
|
+
trace :matching_fails, "Does '#{phrases.inspect}' match '#{reading.expand}'" do
|
345
347
|
phrase_num = 0
|
346
348
|
reading_parts = reading.text.split(/\s+/)
|
347
349
|
reading_parts.each do |element|
|
348
350
|
phrase = phrases[phrase_num]
|
349
351
|
if phrase == 'not'
|
350
352
|
raise "Stop playing games with your double negatives: #{phrases.inspect}" if implicitly_negated
|
351
|
-
|
353
|
+
trace :matching, "Negation detected"
|
352
354
|
implicitly_negated = true
|
353
355
|
phrase = phrases[phrase_num += 1]
|
354
356
|
end
|
355
357
|
if element !~ /\{(\d+)\}/
|
356
358
|
# Just a word; it must match
|
357
359
|
unless phrase == element
|
358
|
-
|
360
|
+
trace :matching_fails, "Mismatched ordinary word #{phrases[phrase_num].inspect} (wanted #{element})"
|
359
361
|
return nil
|
360
362
|
end
|
361
363
|
phrase_num += 1
|
@@ -388,11 +390,11 @@ module ActiveFacts
|
|
388
390
|
# This relies on the supertypes being in breadth-first order:
|
389
391
|
common_supertype = (next_player.supertypes_transitive & player.supertypes_transitive)[0]
|
390
392
|
if !common_supertype
|
391
|
-
|
393
|
+
trace :matching_fails, "Reading discounted because next player #{player.name} doesn't match #{next_player.name}"
|
392
394
|
return nil
|
393
395
|
end
|
394
396
|
|
395
|
-
|
397
|
+
trace :matching_fails, "Subtype step is required between #{player.name} and #{next_player_phrase.player.name} via common supertype #{common_supertype.name}"
|
396
398
|
else
|
397
399
|
if !next_player_phrase
|
398
400
|
next # Contraction succeeded so far
|
@@ -460,7 +462,7 @@ module ActiveFacts
|
|
460
462
|
if a = (!phrase.role_name.is_a?(Integer) && phrase.role_name) and
|
461
463
|
e = role_ref.role.role_name and
|
462
464
|
a != e
|
463
|
-
|
465
|
+
trace :matching, "Role names #{e.inspect} for #{player.name} and #{a.inspect} for #{next_player_phrase.player.name} don't match"
|
464
466
|
return nil
|
465
467
|
end
|
466
468
|
=end
|
@@ -469,7 +471,7 @@ module ActiveFacts
|
|
469
471
|
if residual_adjectives && next_player_phrase.binding.refs.size == 1
|
470
472
|
# This makes matching order-dependent, because there may be no "other purpose"
|
471
473
|
# until another reading has been matched and the roles rebound.
|
472
|
-
|
474
|
+
trace :matching_fails, "Residual adjectives have no other purpose, so this match fails"
|
473
475
|
return nil
|
474
476
|
end
|
475
477
|
|
@@ -478,7 +480,7 @@ module ActiveFacts
|
|
478
480
|
end
|
479
481
|
|
480
482
|
if phrase_num != phrases.size || !intervening_words.empty?
|
481
|
-
|
483
|
+
trace :matching_fails, "Extra words #{(intervening_words + phrases[phrase_num..-1]).inspect}"
|
482
484
|
return nil
|
483
485
|
end
|
484
486
|
|
@@ -486,7 +488,7 @@ module ActiveFacts
|
|
486
488
|
# There may be only one subtyping step on a TypeInheritance fact type.
|
487
489
|
ti_steps = side_effects.select{|side_effect| side_effect.common_supertype}
|
488
490
|
if ti_steps.size > 1 # Not allowed
|
489
|
-
|
491
|
+
trace :matching_fails, "Can't have more than one subtyping step on a TypeInheritance fact type"
|
490
492
|
return nil
|
491
493
|
end
|
492
494
|
|
@@ -496,13 +498,13 @@ module ActiveFacts
|
|
496
498
|
fact_type.subtype.supertypes_transitive :
|
497
499
|
fact_type.supertype.subtypes_transitive
|
498
500
|
if !allowed.include?(ti.common_supertype)
|
499
|
-
|
501
|
+
trace :matching_fails, "Implicit subtyping step extends in the wrong direction"
|
500
502
|
return nil
|
501
503
|
end
|
502
504
|
end
|
503
505
|
end
|
504
506
|
|
505
|
-
|
507
|
+
trace :matching, "Matched reading '#{reading.expand}' (with #{
|
506
508
|
side_effects.map{|side_effect|
|
507
509
|
side_effect.absorbed_precursors+side_effect.absorbed_followers + (side_effect.common_supertype ? 1 : 0)
|
508
510
|
}.inspect
|
@@ -518,12 +520,12 @@ module ActiveFacts
|
|
518
520
|
@applied_side_effects = side_effects
|
519
521
|
# Enact the side-effects of this match (delete the consumed adjectives):
|
520
522
|
# Since this deletes words from the phrases, we do it in reverse order.
|
521
|
-
|
523
|
+
trace :matching, "Apply side-effects" do
|
522
524
|
side_effects.apply_all do |side_effect|
|
523
525
|
phrase = side_effect.phrase
|
524
526
|
|
525
527
|
# We re-use the role_ref if possible (no extra adjectives were used, no rolename or step, etc).
|
526
|
-
|
528
|
+
trace :matching, "side-effect means binding #{phrase.inspect} matches role ref #{side_effect.role_ref.role.object_type.name}"
|
527
529
|
phrase.role_ref = side_effect.role_ref
|
528
530
|
|
529
531
|
changed = false
|
@@ -532,7 +534,7 @@ module ActiveFacts
|
|
532
534
|
# the role_ref, those must be local, and we'll need to extract them.
|
533
535
|
|
534
536
|
if rra = side_effect.role_ref.trailing_adjective
|
535
|
-
|
537
|
+
trace :matching, "Deleting matched trailing adjective '#{rra}'#{side_effect.absorbed_followers>0 ? " in #{side_effect.absorbed_followers} followers" : ""}, cost is #{side_effect.cost}"
|
536
538
|
side_effect.cancel_cost side_effect.absorbed_followers
|
537
539
|
|
538
540
|
# These adjective(s) matched either an adjective here, or a follower word, or both.
|
@@ -552,7 +554,7 @@ module ActiveFacts
|
|
552
554
|
end
|
553
555
|
|
554
556
|
if rra = side_effect.role_ref.leading_adjective
|
555
|
-
|
557
|
+
trace :matching, "Deleting matched leading adjective '#{rra}'#{side_effect.absorbed_precursors>0 ? " in #{side_effect.absorbed_precursors} precursors" : ""}, cost is #{side_effect.cost}"
|
556
558
|
side_effect.cancel_cost side_effect.absorbed_precursors
|
557
559
|
|
558
560
|
# These adjective(s) matched either an adjective here, or a precursor word, or both.
|
@@ -582,7 +584,7 @@ module ActiveFacts
|
|
582
584
|
# Don't assign @fact_type; that will happen when the reading is added
|
583
585
|
def make_fact_type vocabulary
|
584
586
|
fact_type = vocabulary.constellation.FactType(:new)
|
585
|
-
|
587
|
+
trace :matching, "Making new fact type for #{@phrases.inspect}" do
|
586
588
|
@phrases.each do |phrase|
|
587
589
|
next unless phrase.respond_to?(:player)
|
588
590
|
phrase.role = vocabulary.constellation.Role(fact_type, fact_type.all_role.size, :object_type => phrase.player, :concept => :new)
|
@@ -598,7 +600,7 @@ module ActiveFacts
|
|
598
600
|
@role_sequence = constellation.RoleSequence(:new)
|
599
601
|
reading_words = @phrases.clone
|
600
602
|
index = 0
|
601
|
-
|
603
|
+
trace :matching, "Making new reading for #{@phrases.inspect}" do
|
602
604
|
reading_words.map! do |phrase|
|
603
605
|
if phrase.respond_to?(:player)
|
604
606
|
# phrase.role will be set if this reading was used to make_fact_type.
|
@@ -662,7 +664,7 @@ module ActiveFacts
|
|
662
664
|
if phrase.role_name != phrase.role_ref.role.role_name ||
|
663
665
|
phrase.leading_adjective ||
|
664
666
|
phrase.trailing_adjective
|
665
|
-
|
667
|
+
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
|
666
668
|
new_role_sequence_needed = true
|
667
669
|
end
|
668
670
|
else
|
@@ -671,7 +673,7 @@ module ActiveFacts
|
|
671
673
|
end
|
672
674
|
end
|
673
675
|
|
674
|
-
|
676
|
+
trace :matching, "Clause '#{reading_words*' '}' #{new_role_sequence_needed ? 'requires' : 'does not require'} a new Role Sequence"
|
675
677
|
|
676
678
|
constellation = @fact_type.constellation
|
677
679
|
reading_text = reading_words*" "
|
@@ -693,15 +695,15 @@ module ActiveFacts
|
|
693
695
|
extra_adjectives << "(as #{a})"
|
694
696
|
end
|
695
697
|
end
|
696
|
-
|
698
|
+
trace :matching, "Making new role sequence for new reading #{reading_text} due to #{extra_adjectives.inspect}"
|
697
699
|
else
|
698
700
|
# Use existing RoleSequence
|
699
701
|
@role_sequence = role_phrases[0].role_ref.role_sequence
|
700
702
|
if @role_sequence.all_reading.detect{|r| r.text == reading_text }
|
701
|
-
|
703
|
+
trace :matching, "No need to re-create identical reading for #{reading_text}"
|
702
704
|
return @role_sequence
|
703
705
|
else
|
704
|
-
|
706
|
+
trace :matching, "Using existing role sequence for new reading '#{reading_text}'"
|
705
707
|
end
|
706
708
|
end
|
707
709
|
if @fact_type.all_reading.
|
@@ -738,7 +740,7 @@ module ActiveFacts
|
|
738
740
|
end
|
739
741
|
|
740
742
|
# REVISIT: Check maybe and other qualifiers:
|
741
|
-
|
743
|
+
trace :constraint, "Need to make constraints for #{@qualifiers.inspect}" if @qualifiers.size > 0 or @certainty != true
|
742
744
|
end
|
743
745
|
end
|
744
746
|
|
@@ -762,7 +764,7 @@ module ActiveFacts
|
|
762
764
|
@common_supertype = common_supertype
|
763
765
|
@residual_adjectives = residual_adjectives
|
764
766
|
@cancelled_cost = 0
|
765
|
-
|
767
|
+
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
|
766
768
|
end
|
767
769
|
|
768
770
|
def cost
|
@@ -921,8 +923,9 @@ module ActiveFacts
|
|
921
923
|
l = @leading_adjective
|
922
924
|
t = @trailing_adjective
|
923
925
|
key = [!l || l.empty? ? nil : l, @term, !t || t.empty? ? nil : t]
|
924
|
-
key
|
925
926
|
end
|
927
|
+
key += [:literal, literal.literal] if @literal
|
928
|
+
key
|
926
929
|
end
|
927
930
|
|
928
931
|
def bind context
|
@@ -940,14 +943,34 @@ module ActiveFacts
|
|
940
943
|
role_name = @term
|
941
944
|
end
|
942
945
|
end
|
943
|
-
|
944
|
-
|
946
|
+
k = key
|
947
|
+
@binding = context.bindings[k]
|
948
|
+
if !@binding
|
949
|
+
if !literal
|
950
|
+
# Find a binding that has a literal, and bind to it if it's the only one
|
951
|
+
candidates = context.bindings.map do |binding_key, binding|
|
952
|
+
binding_key[0...k.size] == k &&
|
953
|
+
binding_key[-2] == :literal ? binding : nil
|
954
|
+
end.compact
|
955
|
+
raise "Uncertain binding reference for #{to_s}, could be any of #{candidates.inspect}" if candidates.size > 1
|
956
|
+
@binding = candidates[0]
|
957
|
+
else
|
958
|
+
# New binding has a literal, look for one without:
|
959
|
+
@binding = context.bindings[k[0...-2]]
|
960
|
+
end
|
961
|
+
end
|
962
|
+
|
963
|
+
if !@binding
|
964
|
+
@binding = Binding.new(@player, role_name)
|
965
|
+
context.bindings[k] = @binding
|
966
|
+
end
|
967
|
+
@binding.add_ref self
|
945
968
|
@binding
|
946
969
|
end
|
947
970
|
|
948
971
|
def unbind context
|
949
972
|
# The key has changed.
|
950
|
-
@binding.
|
973
|
+
@binding.delete_ref self
|
951
974
|
if @binding.refs.empty?
|
952
975
|
# Remove the binding from the context if this was the last reference
|
953
976
|
context.bindings.delete_if {|k,v| v == @binding }
|
@@ -961,7 +984,7 @@ module ActiveFacts
|
|
961
984
|
end
|
962
985
|
|
963
986
|
def rebind_to(context, other_ref)
|
964
|
-
|
987
|
+
trace :binding, "Rebinding #{inspect} to #{other_ref.inspect}"
|
965
988
|
|
966
989
|
old_binding = binding # Remember to move all refs across
|
967
990
|
unbind(context)
|
@@ -969,7 +992,7 @@ module ActiveFacts
|
|
969
992
|
new_binding = other_ref.binding
|
970
993
|
[self, *old_binding.refs].each do |ref|
|
971
994
|
ref.binding = new_binding
|
972
|
-
new_binding.
|
995
|
+
new_binding.add_ref ref
|
973
996
|
end
|
974
997
|
old_binding.rebound_to = new_binding
|
975
998
|
end
|
@@ -999,20 +1022,20 @@ module ActiveFacts
|
|
999
1022
|
fact_type = @role_ref.role.fact_type
|
1000
1023
|
constellation = vocabulary.constellation
|
1001
1024
|
|
1002
|
-
|
1025
|
+
trace :constraint, "Processing embedded constraint #{@quantifier.inspect} on #{@role_ref.role.object_type.name} in #{fact_type.describe}" do
|
1003
1026
|
# Preserve the role order of the clause, excluding this role:
|
1004
1027
|
constrained_roles = (@clause.refs-[self]).map{|vr| vr.role_ref.role}
|
1005
1028
|
if constrained_roles.empty?
|
1006
|
-
|
1029
|
+
trace :constraint, "Quantifier over unary role has no effect"
|
1007
1030
|
return
|
1008
1031
|
end
|
1009
1032
|
constraint = find_pc_over_roles(constrained_roles)
|
1010
1033
|
if constraint
|
1011
1034
|
raise "Conflicting maximum frequency for constraint" if constraint.max_frequency && constraint.max_frequency != @quantifier.max
|
1012
|
-
|
1035
|
+
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
|
1013
1036
|
constraint.max_frequency = @quantifier.max
|
1014
1037
|
raise "Conflicting minimum frequency for constraint" if constraint.min_frequency && constraint.min_frequency != @quantifier.min
|
1015
|
-
|
1038
|
+
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
|
1016
1039
|
constraint.min_frequency = @quantifier.min
|
1017
1040
|
else
|
1018
1041
|
role_sequence = constellation.RoleSequence(:new)
|
@@ -1027,7 +1050,7 @@ module ActiveFacts
|
|
1027
1050
|
:max_frequency => @quantifier.max,
|
1028
1051
|
:min_frequency => @quantifier.min
|
1029
1052
|
)
|
1030
|
-
|
1053
|
+
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}"
|
1031
1054
|
@quantifier.enforcement.compile(constellation, constraint) if @quantifier.enforcement
|
1032
1055
|
@embedded_presence_constraint = constraint
|
1033
1056
|
end
|