activefacts 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|