activefacts 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +5 -13
  2. data/Rakefile +2 -2
  3. data/bin/afgen +1 -1
  4. data/bin/cql +118 -27
  5. data/examples/CQL/Insurance.cql +2 -2
  6. data/examples/CQL/Metamodel.cql +3 -3
  7. data/examples/CQL/SchoolActivities.cql +1 -1
  8. data/examples/CQL/Warehousing.cql +5 -4
  9. data/lib/activefacts/cql.rb +1 -1
  10. data/lib/activefacts/cql/Language/English.treetop +2 -1
  11. data/lib/activefacts/cql/compiler.rb +6 -6
  12. data/lib/activefacts/cql/compiler/clause.rb +69 -46
  13. data/lib/activefacts/cql/compiler/constraint.rb +14 -14
  14. data/lib/activefacts/cql/compiler/entity_type.rb +24 -24
  15. data/lib/activefacts/cql/compiler/fact.rb +40 -27
  16. data/lib/activefacts/cql/compiler/fact_type.rb +16 -16
  17. data/lib/activefacts/cql/compiler/query.rb +12 -12
  18. data/lib/activefacts/cql/compiler/shared.rb +9 -0
  19. data/lib/activefacts/cql/compiler/value_type.rb +4 -4
  20. data/lib/activefacts/cql/parser.rb +9 -9
  21. data/lib/activefacts/generate/cql.rb +41 -20
  22. data/lib/activefacts/generate/helpers/oo.rb +33 -70
  23. data/lib/activefacts/generate/helpers/ordered.rb +61 -87
  24. data/lib/activefacts/generate/ruby.rb +12 -72
  25. data/lib/activefacts/generate/transform/surrogate.rb +13 -13
  26. data/lib/activefacts/input/orm.rb +72 -71
  27. data/lib/activefacts/persistence/columns.rb +66 -31
  28. data/lib/activefacts/persistence/foreignkey.rb +6 -6
  29. data/lib/activefacts/persistence/index.rb +12 -12
  30. data/lib/activefacts/persistence/object_type.rb +15 -12
  31. data/lib/activefacts/persistence/reference.rb +20 -18
  32. data/lib/activefacts/persistence/tables.rb +40 -36
  33. data/lib/activefacts/support.rb +69 -123
  34. data/lib/activefacts/version.rb +2 -2
  35. data/lib/activefacts/vocabulary/extensions.rb +42 -39
  36. data/lib/activefacts/vocabulary/metamodel.rb +11 -1
  37. data/lib/activefacts/vocabulary/verbaliser.rb +28 -28
  38. data/spec/cql/contractions_spec.rb +1 -1
  39. data/spec/cql/entity_type_spec.rb +1 -1
  40. data/spec/cql/fact_type_matching_spec.rb +3 -3
  41. data/spec/cql/role_matching_spec.rb +4 -4
  42. data/spec/cql/samples_spec.rb +2 -2
  43. data/spec/cql_cql_spec.rb +1 -1
  44. data/spec/helpers/array_matcher.rb +1 -1
  45. data/spec/norma_ruby_sql_spec.rb +2 -2
  46. data/spec/norma_tables_spec.rb +3 -2
  47. metadata +47 -68
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NzcwY2M0NGUyNTljNzZmMDBlN2ExOTMyNTI3MDE0ZTMwYjU4MWY3Yw==
5
- data.tar.gz: !binary |-
6
- Yzk1MmFlZTJhZTZiOTY0OGMyZTNjYzExM2Q5OTJhMzQwNTNhNjQ2Nw==
2
+ SHA1:
3
+ metadata.gz: 6e791a369dca69a80ccd666e7eec9441e9f00483
4
+ data.tar.gz: d894b07ab82cfdf942a8e93561fa86a8a3bedd8a
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- MTI2OTFhNWU0YjA2NWNjNmJkNDI0NzJkYWZkZGVhNGNiZDIzNTBjNWQ1MjQz
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 = ['-S'] +
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
@@ -64,6 +64,6 @@ begin
64
64
  rescue => e
65
65
  $stderr.puts "#{e.message}"
66
66
  # puts "\t#{e.backtrace*"\n\t"}"
67
- $stderr.puts "\t#{e.backtrace*"\n\t"}" if debug :exception
67
+ $stderr.puts "\t#{e.backtrace*"\n\t"}" if trace :exception
68
68
  exit 1
69
69
  end
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} #{debug_toggle(word) ? "en" : "dis"}abled"
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 debug_keys*", "
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
- if cmd == "/help" or cmd == "/"
108
- help words
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 debug :exception
129
+ puts "\t#{e.backtrace*"\n\t"}" if trace :exception
128
130
  end
129
131
  end
130
132
 
131
- def process_query(join)
132
- if join.all_join_node.size == 1 and join.all_join_step.size == 0
133
- list_instances(join.all_join_node.single.object_type)
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 Join Nodes, and do a nested-loops iteration checking all Steps
136
- puts "Can't yet process complex queries (involving #{join.all_join_node.map{|jn|jn.object_type.name}*', '})"
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 ENV["DEBUG"] =~ /exception/
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
- result = super
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 debug tracing key, or list available keys
290
+ /trace key\t\tToggle tracing key, or list available keys
200
291
  }
201
292
  else
202
293
  words.each do |word|
@@ -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;
@@ -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 which is sanctioned by one School;
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 matches Sales 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;
@@ -19,7 +19,7 @@ module ActiveFacts
19
19
  # require 'activefacts/cql'
20
20
  class CQLLoader
21
21
  def self.load(file) #:nodoc:
22
- debug "Loading #{file}" do
22
+ trace "Loading #{file}" do
23
23
  vocabulary = ActiveFacts::Input::CQL.readfile(file)
24
24
 
25
25
  ruby = StringIO.new
@@ -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 'stronglyintransitive' !alphanumeric end
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
- debug :file, "Parsing '#{@filename}'"
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
- debug :parse, "Parsed '#{node.text_value.gsub(/\s+/,' ').strip}'" do
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
- debug :ast, ast.inspect
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
- debug :definition, "Compiled to #{value.is_a?(Array) ? value.map{|v| v.verbalise}*', ' : value.verbalise}" if value
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.unit_as_plural_name)).uniq
126
- debug :units, "Looking for unit #{s}, got #{units.map{|u|u.name}.inspect}"
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
- debug :matching, "A left contraction will be against #{self.inspect}, conjunction is #{conjunction.inspect}"
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
- debug :matching, "Failed to match #{inspect}. Trying again using left contraction onto #{contraction_player.name}"
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
- debug :matching, "Failed to match #{inspect}. Trying again using right contraction onto #{contraction_player.name}"
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
- debug :matching, "Looking for existing #{players.size}-ary fact types matching '#{inspect(phrases)}'" do
197
- debug :matching, "Players are '#{player_names.inspect}'"
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
- debug :matching, "Players must match '#{player_related_types.map{|pa| pa.map{|p|p.name}}.inspect}'"
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
- debug :matching_fails, "These readings are compatible: #{compatible_readings.map(&:expand).inspect}"
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
- debug :matching, "Looking amongst #{candidate_fact_types.size} existing fact types for one matching #{left_insertion}'#{inspect}'#{right_insertion}" do
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
- debug :matching_fails, "Found #{matches.size} valid matches#{matches.size > 0 ? ', best is '+best_matches[0].expand : ''}"
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
- debug :matching, "Matched '#{@fact_type.default_reading}'"
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
- debug :matching, "No fact type matched, candidates were '#{candidate_fact_types.map{|ft| ft.default_reading}*"', '"}'"
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
- debug :matching_fails, "Does '#{phrases.inspect}' match '#{reading.expand}'" do
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
- debug :matching, "Negation detected"
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
- debug :matching_fails, "Mismatched ordinary word #{phrases[phrase_num].inspect} (wanted #{element})"
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
- debug :matching_fails, "Reading discounted because next player #{player.name} doesn't match #{next_player.name}"
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
- debug :matching_fails, "Subtype step is required between #{player.name} and #{next_player_phrase.player.name} via common supertype #{common_supertype.name}"
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
- debug :matching, "Role names #{e.inspect} for #{player.name} and #{a.inspect} for #{next_player_phrase.player.name} don't match"
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
- debug :matching_fails, "Residual adjectives have no other purpose, so this match fails"
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
- debug :matching_fails, "Extra words #{(intervening_words + phrases[phrase_num..-1]).inspect}"
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
- debug :matching_fails, "Can't have more than one subtyping step on a TypeInheritance fact type"
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
- debug :matching_fails, "Implicit subtyping step extends in the wrong direction"
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
- debug :matching, "Matched reading '#{reading.expand}' (with #{
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
- debug :matching, "Apply side-effects" do
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
- debug :matching, "side-effect means binding #{phrase.inspect} matches role ref #{side_effect.role_ref.role.object_type.name}"
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
- debug :matching, "Deleting matched trailing adjective '#{rra}'#{side_effect.absorbed_followers>0 ? " in #{side_effect.absorbed_followers} followers" : ""}, cost is #{side_effect.cost}"
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
- debug :matching, "Deleting matched leading adjective '#{rra}'#{side_effect.absorbed_precursors>0 ? " in #{side_effect.absorbed_precursors} precursors" : ""}, cost is #{side_effect.cost}"
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
- debug :matching, "Making new fact type for #{@phrases.inspect}" do
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
- debug :matching, "Making new reading for #{@phrases.inspect}" do
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
- debug :matching, "phrase in matched clause has residual adjectives or role name, so needs a new role_sequence" if @fact_type.all_reading.size > 0
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
- debug :matching, "Clause '#{reading_words*' '}' #{new_role_sequence_needed ? 'requires' : 'does not require'} a new Role Sequence"
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
- debug :matching, "Making new role sequence for new reading #{reading_text} due to #{extra_adjectives.inspect}"
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
- debug :matching, "No need to re-create identical reading for #{reading_text}"
703
+ trace :matching, "No need to re-create identical reading for #{reading_text}"
702
704
  return @role_sequence
703
705
  else
704
- debug :matching, "Using existing role sequence for new reading '#{reading_text}'"
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
- debug :constraint, "Need to make constraints for #{@qualifiers.inspect}" if @qualifiers.size > 0 or @certainty != true
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
- debug :matching_fails, "Saving side effects for #{@phrase.term}, absorbs #{@absorbed_precursors}/#{@absorbed_followers}#{@common_supertype ? ', step over supertype '+ @common_supertype.name : ''}" if @absorbed_precursors+@absorbed_followers+(@common_supertype ? 1 : 0) > 0
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
- @binding = (context.bindings[key] ||= Binding.new(@player, role_name))
944
- @binding.refs << self
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.refs.delete(self)
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
- debug :binding, "Rebinding #{inspect} to #{other_ref.inspect}"
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.refs << ref
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
- debug :constraint, "Processing embedded constraint #{@quantifier.inspect} on #{@role_ref.role.object_type.name} in #{fact_type.describe}" do
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
- debug :constraint, "Quantifier over unary role has no effect"
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
- debug :constraint, "Setting max frequency to #{@quantifier.max} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}" unless constraint.max_frequency
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
- debug :constraint, "Setting min frequency to #{@quantifier.min} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}" unless constraint.min_frequency
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
- debug :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}"
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