activefacts 0.8.9 → 0.8.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. data/.gemtest +0 -0
  2. data/Manifest.txt +28 -33
  3. data/Rakefile +11 -12
  4. data/bin/cql +90 -46
  5. data/examples/CQL/Blog.cql +2 -1
  6. data/examples/CQL/CompanyDirectorEmployee.cql +2 -2
  7. data/examples/CQL/Death.cql +1 -1
  8. data/examples/CQL/Diplomacy.cql +9 -9
  9. data/examples/CQL/Genealogy.cql +3 -2
  10. data/examples/CQL/Insurance.cql +10 -7
  11. data/examples/CQL/JoinEquality.cql +2 -2
  12. data/examples/CQL/Marriage.cql +1 -1
  13. data/examples/CQL/Metamodel.cql +73 -53
  14. data/examples/CQL/MetamodelNext.cql +89 -67
  15. data/examples/CQL/OneToOnes.cql +2 -2
  16. data/examples/CQL/ServiceDirector.cql +10 -5
  17. data/examples/CQL/Supervision.cql +3 -3
  18. data/examples/CQL/Tests.Test5.Load.cql +1 -1
  19. data/examples/CQL/Warehousing.cql +4 -2
  20. data/lib/activefacts/cql/CQLParser.treetop +26 -60
  21. data/lib/activefacts/cql/Context.treetop +12 -2
  22. data/lib/activefacts/cql/Expressions.treetop +14 -30
  23. data/lib/activefacts/cql/FactTypes.treetop +165 -110
  24. data/lib/activefacts/cql/Language/English.treetop +167 -54
  25. data/lib/activefacts/cql/LexicalRules.treetop +16 -2
  26. data/lib/activefacts/cql/{Concepts.treetop → ObjectTypes.treetop} +36 -37
  27. data/lib/activefacts/cql/Terms.treetop +57 -27
  28. data/lib/activefacts/cql/ValueTypes.treetop +39 -13
  29. data/lib/activefacts/cql/compiler.rb +5 -3
  30. data/lib/activefacts/cql/compiler/{reading.rb → clause.rb} +407 -285
  31. data/lib/activefacts/cql/compiler/constraint.rb +178 -275
  32. data/lib/activefacts/cql/compiler/entity_type.rb +73 -64
  33. data/lib/activefacts/cql/compiler/expression.rb +418 -0
  34. data/lib/activefacts/cql/compiler/fact.rb +146 -145
  35. data/lib/activefacts/cql/compiler/fact_type.rb +197 -80
  36. data/lib/activefacts/cql/compiler/join.rb +159 -0
  37. data/lib/activefacts/cql/compiler/shared.rb +51 -23
  38. data/lib/activefacts/cql/compiler/value_type.rb +56 -2
  39. data/lib/activefacts/cql/parser.rb +15 -4
  40. data/lib/activefacts/generate/absorption.rb +7 -7
  41. data/lib/activefacts/generate/cql.rb +100 -37
  42. data/lib/activefacts/generate/oo.rb +28 -51
  43. data/lib/activefacts/generate/ordered.rb +60 -36
  44. data/lib/activefacts/generate/ruby.rb +6 -6
  45. data/lib/activefacts/generate/sql/server.rb +4 -4
  46. data/lib/activefacts/input/orm.rb +71 -53
  47. data/lib/activefacts/persistence.rb +1 -1
  48. data/lib/activefacts/persistence/columns.rb +27 -23
  49. data/lib/activefacts/persistence/foreignkey.rb +6 -6
  50. data/lib/activefacts/persistence/index.rb +17 -17
  51. data/lib/activefacts/persistence/{concept.rb → object_type.rb} +9 -9
  52. data/lib/activefacts/persistence/reference.rb +61 -36
  53. data/lib/activefacts/persistence/tables.rb +61 -59
  54. data/lib/activefacts/support.rb +54 -29
  55. data/lib/activefacts/version.rb +1 -1
  56. data/lib/activefacts/vocabulary/extensions.rb +99 -54
  57. data/lib/activefacts/vocabulary/metamodel.rb +43 -37
  58. data/lib/activefacts/vocabulary/verbaliser.rb +134 -109
  59. data/spec/absorption_spec.rb +8 -8
  60. data/spec/cql/comparison_spec.rb +91 -0
  61. data/spec/cql/contractions_spec.rb +251 -0
  62. data/spec/cql/entity_type_spec.rb +319 -0
  63. data/spec/cql/expressions_spec.rb +63 -0
  64. data/spec/cql/fact_type_matching_spec.rb +283 -0
  65. data/spec/cql/french_spec.rb +21 -0
  66. data/spec/cql/parser/bad_literals_spec.rb +86 -0
  67. data/spec/cql/parser/constraints_spec.rb +19 -0
  68. data/spec/cql/parser/entity_types_spec.rb +106 -0
  69. data/spec/cql/parser/expressions_spec.rb +179 -0
  70. data/spec/cql/parser/fact_types_spec.rb +41 -0
  71. data/spec/cql/parser/literals_spec.rb +312 -0
  72. data/spec/cql/parser/pragmas_spec.rb +89 -0
  73. data/spec/cql/parser/value_types_spec.rb +42 -0
  74. data/spec/cql/role_matching_spec.rb +147 -0
  75. data/spec/cql/samples_spec.rb +9 -9
  76. data/spec/cql_cql_spec.rb +1 -1
  77. data/spec/cql_dm_spec.rb +116 -0
  78. data/spec/cql_mysql_spec.rb +1 -1
  79. data/spec/cql_ruby_spec.rb +1 -1
  80. data/spec/cql_sql_spec.rb +3 -3
  81. data/spec/cql_symbol_tables_spec.rb +30 -30
  82. data/spec/cqldump_spec.rb +4 -4
  83. data/spec/helpers/array_matcher.rb +32 -27
  84. data/spec/helpers/diff_matcher.rb +6 -26
  85. data/spec/helpers/file_matcher.rb +41 -32
  86. data/spec/helpers/parse_to_ast_matcher.rb +76 -0
  87. data/spec/helpers/string_matcher.rb +32 -31
  88. data/spec/norma_cql_spec.rb +1 -1
  89. data/spec/norma_ruby_spec.rb +1 -1
  90. data/spec/norma_ruby_sql_spec.rb +1 -1
  91. data/spec/norma_sql_spec.rb +3 -1
  92. data/spec/norma_tables_spec.rb +1 -1
  93. data/spec/ruby_api_spec.rb +23 -0
  94. data/spec/spec_helper.rb +5 -4
  95. metadata +66 -66
  96. data/examples/CQL/OrienteeringER.cql +0 -58
  97. data/lib/activefacts/api.rb +0 -44
  98. data/lib/activefacts/api/concept.rb +0 -410
  99. data/lib/activefacts/api/constellation.rb +0 -128
  100. data/lib/activefacts/api/entity.rb +0 -256
  101. data/lib/activefacts/api/instance.rb +0 -60
  102. data/lib/activefacts/api/instance_index.rb +0 -80
  103. data/lib/activefacts/api/numeric.rb +0 -167
  104. data/lib/activefacts/api/role.rb +0 -80
  105. data/lib/activefacts/api/role_proxy.rb +0 -70
  106. data/lib/activefacts/api/role_values.rb +0 -117
  107. data/lib/activefacts/api/standard_types.rb +0 -87
  108. data/lib/activefacts/api/support.rb +0 -65
  109. data/lib/activefacts/api/value.rb +0 -135
  110. data/lib/activefacts/api/vocabulary.rb +0 -82
  111. data/spec/api/autocounter.rb +0 -82
  112. data/spec/api/constellation.rb +0 -130
  113. data/spec/api/entity_type.rb +0 -103
  114. data/spec/api/instance.rb +0 -461
  115. data/spec/api/roles.rb +0 -124
  116. data/spec/api/value_type.rb +0 -112
  117. data/spec/api_spec.rb +0 -13
  118. data/spec/cql/matching_spec.rb +0 -517
  119. data/spec/cql/unit_spec.rb +0 -394
  120. data/spec/spec.opts +0 -1
@@ -16,38 +16,41 @@ module ActiveFacts
16
16
  id.value
17
17
  ]){|a, e| a << e.id.value}*' '
18
18
  end
19
+
20
+ def node_type
21
+ :term
22
+ end
19
23
  }
20
24
  end
21
25
 
22
26
  rule non_term_def
23
- entity_prefix
24
- / written_as # Value type
25
- / is s mapping_pragmas where # Objectified type
26
- / non_role_word
27
- / identified_by # as in: "a kind of X identified by..."
27
+ mapping_pragmas entity_prefix
28
+ / mapping_pragmas written_as # Value type
29
+ / mapping_pragmas is_where # Objectified type
30
+ / non_phrase
31
+ / identified_by # as in: "a kind of X identified by..."
28
32
  / unit
29
33
  end
30
34
 
31
35
  rule entity_prefix
32
- is s identified_by
36
+ is s (independent s )? identified_by
33
37
  /
34
- subtype_prefix term_definition_name
35
- &{|e| input.context.object_type(e[1].value, "subtype"); true }
38
+ subtype_prefix (independent s )? term_definition_name
39
+ &{|e| input.context.object_type(e[2].value, "subtype") }
36
40
  end
37
41
 
38
42
  rule prescan
39
43
  s (
40
- term_definition_name s entity_prefix
41
- &{|e| input.context.object_type(e[0].value, "entity type"); true }
44
+ term_definition_name mapping_pragmas entity_prefix
45
+ &{|e| input.context.object_type(e[0].value, "entity type") }
42
46
  /
43
- t1:term_definition_name written_as t2:term_definition_name
47
+ t1:term_definition_name mapping_pragmas written_as t2:term_definition_name
44
48
  &{|e| input.context.object_type(e[0].value, "value type")
45
- input.context.object_type(e[2].value, "value type")
46
- true
49
+ input.context.object_type(e[3].value, "value type")
47
50
  }
48
51
  /
49
- term_definition_name s is s mapping_pragmas where
50
- &{|e| input.context.object_type(e[0].value, "objectified_fact_type"); true }
52
+ term_definition_name s mapping_pragmas is s (independent s )? where
53
+ &{|e| input.context.object_type(e[0].value, "objectified_fact_type") }
51
54
  )?
52
55
  prescan_rest
53
56
  &{|s|
@@ -63,13 +66,13 @@ module ActiveFacts
63
66
  # Do a first-pass mainly lexical analysis, looking for role name definitions and adjectives,
64
67
  # for use in detecting terms later.
65
68
  rule prescan_rest
66
- &{ input.context.reset_role_names }
69
+ &{|s| input.context.reset_role_names }
67
70
  (
68
71
  context_note # Context notes have different lexical conventions
69
72
  / '(' as S term_definition_name s ')' s # Prescan for a Role Name
70
73
  &{|s| input.context.role_name(s[3].value) }
71
- # Adjective definitions
72
- / new_adjective_term
74
+ / new_derived_value
75
+ / new_adjective_term # Adjective definitions
73
76
  / global_term # If we see A B - C D, don't recognise B as a new adjective for C D.
74
77
  / id
75
78
  / range # Covers all numbers and strings
@@ -79,6 +82,29 @@ module ActiveFacts
79
82
  )* [?;] s
80
83
  end
81
84
 
85
+ rule derived_value_continuation
86
+ s '-' tail:(s !global_term !(that/who) id)*
87
+ {
88
+ def value
89
+ tail.elements.map{|e| e.id.text_value}
90
+ end
91
+ }
92
+ end
93
+
94
+ rule new_derived_value
95
+ !global_term id derived_value_continuation? s '='
96
+ &{|s|
97
+ name = [s[1].text_value] + (s[2].empty? ? [] : s[2].value)
98
+ input.context.object_type(name*' ', "derived value type")
99
+ }
100
+ /
101
+ '=' s !global_term id derived_value_continuation? s (that/who)
102
+ &{|s|
103
+ name = [s[3].text_value] + (s[4].empty? ? [] : s[4].value)
104
+ input.context.object_type(name*' ', "derived value type")
105
+ }
106
+ end
107
+
82
108
  rule new_adjective_term
83
109
  !global_term adj:id '-' lead_intervening s global_term # Definitely a new leading adjective for this term
84
110
  &{|s| input.context.new_leading_adjective_term([s[1].text_value, s[3].value].compact*" ", s[5].text_value) }
@@ -111,26 +137,28 @@ module ActiveFacts
111
137
  tail:(s '-'? s w:id &{|s| w = s[3].text_value; input.context.term_continues?(w) })*
112
138
  &{|s| input.context.term_complete? }
113
139
  {
114
- def ast quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, objectification_join = nil
140
+ def ast quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, nested_clauses = nil
115
141
  t = x.context[:term]
116
142
  gt = x.context[:global_term]
117
143
  leading_adjective = t[0...-gt.size-1] if t.size > gt.size and t[-gt.size..-1] == gt
118
144
  trailing_adjective = t[gt.size+1..-1] if t.size > gt.size and t[0...gt.size] == gt
119
- Compiler::RoleRef.new(gt, leading_adjective, trailing_adjective, quantifier, function_call, role_name, value_constraint, literal, objectification_join)
145
+ Compiler::VarRef.new(gt, leading_adjective, trailing_adjective, quantifier, function_call, role_name, value_constraint, literal, nested_clauses)
120
146
  end
121
147
 
122
148
  def value # Sometimes we just want the full term name
123
149
  x.context[:term]
124
150
  end
151
+ def node_type; :term; end
125
152
  }
126
153
  /
127
154
  s head:id '-' s term &{|s| s[4].ast.leading_adjective == nil }
128
155
  {
129
- def ast quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, objectification_join = nil
130
- ast = term.ast(quantifier, function_call, role_name, value_constraint, literal, objectification_join)
156
+ def ast quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, nested_clauses = nil
157
+ ast = term.ast(quantifier, function_call, role_name, value_constraint, literal, nested_clauses)
131
158
  ast.leading_adjective = head.text_value
132
159
  ast
133
160
  end
161
+ def node_type; :term; end
134
162
  }
135
163
  end
136
164
 
@@ -148,17 +176,19 @@ module ActiveFacts
148
176
  }
149
177
  end
150
178
 
151
- rule non_role_word
152
- # These words are illegal in (but maybe ok following) a reading where a role word is expected:
179
+ rule non_phrase
180
+ # These words are illegal in (but maybe ok following) a clause where a phrase is expected:
153
181
  and
154
- / where
182
+ / but
155
183
  / if
184
+ / role_list_constraint_followers
156
185
  / only
157
186
  / or
158
187
  / quantifier
188
+ / returning
189
+ / then
159
190
  / value_constraint
160
- / but
161
- / 'occurs' s quantifier s 'time'
191
+ / where
162
192
  end
163
193
 
164
194
  end
@@ -9,25 +9,37 @@ module ActiveFacts
9
9
  grammar ValueTypes
10
10
  rule value_type
11
11
  s term_definition_name
12
+ m1:mapping_pragmas
12
13
  # REVISIT: ORM2 would allow (subtype_prefix term)?
13
14
  written_as
14
- base:(term/id) s
15
+ base:(term/implicit_value_type_name) s
15
16
  value_type_parameters
16
17
  u:units?
17
18
  r:(value_constraint enforcement)?
18
- mapping_pragmas
19
+ m2:mapping_pragmas
19
20
  s ';' s
20
21
  {
21
22
  def ast
22
23
  name = term_definition_name.value
23
24
  params = value_type_parameters.values
24
- value_constraint = r.empty? ? nil : Compiler::ValueConstraint.new(r.value_constraint.ranges, r.enforcement.ast)
25
+ value_constraint = nil
26
+ unless r.empty?
27
+ value_constraint = Compiler::ValueConstraint.new(r.value_constraint.ranges, r.value_constraint.units, r.enforcement.ast)
28
+ end
25
29
  units = u.empty? ? [] : u.value
26
- Compiler::ValueType.new name, base.value, params, units, value_constraint, mapping_pragmas.value
30
+ pragmas = m1.value+m2.value
31
+ Compiler::ValueType.new name, base.value, params, units, value_constraint, pragmas
27
32
  end
28
33
  }
29
34
  end
30
35
 
36
+ rule implicit_value_type_name
37
+ id
38
+ {
39
+ def node_type; :term; end
40
+ }
41
+ end
42
+
31
43
  rule value_type_parameters
32
44
  '(' s tpl:type_parameter_list? ')' s
33
45
  { def values; tpl.empty? ? [] : tpl.values; end }
@@ -46,12 +58,12 @@ module ActiveFacts
46
58
 
47
59
  rule unit_definition
48
60
  u:(
49
- coeff:unit_coefficient? base:units? s o:unit_offset?
50
- converts s a:(approximately s)? to s
51
- singular:id s plural:('/' s p:id s)?
61
+ s coeff:unit_coefficient? base:units? s o:unit_offset?
62
+ conversion
63
+ singular:unit_name s plural:('/' s p:unit_name s)?
52
64
  /
53
- singular:id s plural:('/' s p:id s)?
54
- converts s a:(approximately s)? to s
65
+ s singular:unit_name s plural:('/' s p:unit_name s)?
66
+ conversion
55
67
  coeff:unit_coefficient? base:units? s o:unit_offset?
56
68
  )
57
69
  q:(approximately '' / ephemera s url )? s
@@ -68,13 +80,21 @@ module ActiveFacts
68
80
  end
69
81
  offset = u.o.text_value.empty? ? 0 : u.o.value
70
82
  bases = u.base.empty? ? [] : u.base.value
71
- approximately = q.respond_to? :approximately
83
+ approximately = q.respond_to?(:approximately) || u.conversion.approximate?
72
84
  ephemera = q.respond_to?(:ephemera) ? q.url.text_value : nil
73
85
  Compiler::Unit.new singular, plural, numerator, denominator, offset, bases, approximately, ephemera
74
86
  end
75
87
  }
76
88
  end
77
89
 
90
+ rule unit_name
91
+ id
92
+ {
93
+ def node_type; :unit; end
94
+ }
95
+ end
96
+
97
+
78
98
  rule unit_coefficient
79
99
  numerator:number denominator:(s '/' s number)? s
80
100
  {
@@ -105,7 +125,7 @@ module ActiveFacts
105
125
  end
106
126
 
107
127
  rule non_unit
108
- restricted / converts / approximately / ephemera
128
+ restricted / conversion / approximately / ephemera
109
129
  end
110
130
 
111
131
  rule unit
@@ -113,7 +133,7 @@ module ActiveFacts
113
133
  end
114
134
 
115
135
  rule maybe_unit
116
- unit_name:id pow:('^' '-'? [0-9])?
136
+ unit_name pow:('^' '-'? [0-9])?
117
137
  { def value
118
138
  [unit_name.text_value, pow.text_value.empty? ? 1 : Integer(pow.text_value[1..-1])]
119
139
  end
@@ -129,12 +149,18 @@ module ActiveFacts
129
149
  restricted s to s range_list s u:units?
130
150
  {
131
151
  def ranges
132
- raise "units on value constraints are not yet processed" unless u.empty? # REVISIT: Use the units here
133
152
  range_list.ranges
134
153
  end
154
+
155
+ def units
156
+ u.empty? ? nil : u.value
157
+ end
135
158
  }
159
+ # REVISIT: "where the possible value/s of that <Term> is/are value (, ...)"
136
160
  end
137
161
 
162
+ # REVISIT: Value constraint for ValueType or EntityType&Refmode: "the possible values of <TYPE> are value (, ...)"
163
+
138
164
  rule range_list
139
165
  '{' s
140
166
  head:range s tail:( ',' s range )*
@@ -4,14 +4,15 @@
4
4
  #
5
5
  require 'activefacts/vocabulary'
6
6
  require 'activefacts/cql/parser'
7
-
8
7
  require 'activefacts/cql/compiler/shared'
9
8
  require 'activefacts/cql/compiler/value_type'
10
9
  require 'activefacts/cql/compiler/entity_type'
11
- require 'activefacts/cql/compiler/reading'
10
+ require 'activefacts/cql/compiler/clause'
12
11
  require 'activefacts/cql/compiler/fact_type'
12
+ require 'activefacts/cql/compiler/expression'
13
13
  require 'activefacts/cql/compiler/fact'
14
14
  require 'activefacts/cql/compiler/constraint'
15
+ require 'activefacts/cql/compiler/join'
15
16
 
16
17
  module ActiveFacts
17
18
  module CQL
@@ -31,6 +32,7 @@ module ActiveFacts
31
32
  compile(f.read)
32
33
  end
33
34
  @filename = old_filename
35
+ @vocabulary
34
36
  end
35
37
 
36
38
  def compile input
@@ -43,7 +45,7 @@ module ActiveFacts
43
45
  ast = node.ast
44
46
  next unless ast
45
47
  debug :ast, ast.inspect
46
- ast.source = node.body
48
+ ast.tree = node
47
49
  ast.constellation = @constellation
48
50
  ast.vocabulary = @vocabulary
49
51
  value = compile_definition ast
@@ -2,88 +2,107 @@ module ActiveFacts
2
2
  module CQL
3
3
  class Compiler < ActiveFacts::CQL::Parser
4
4
 
5
- class Reading
5
+ class Clause
6
6
  attr_reader :phrases
7
7
  attr_accessor :qualifiers, :context_note
8
- attr_reader :fact_type, :reading, :role_sequence # These are the Metamodel objects
9
- attr_reader :side_effects
10
- attr_writer :fact_type # Assigned for a bare (existential) objectification fact
8
+ attr_accessor :conjunction # one of {nil, 'and', ',', 'or', 'where'}
9
+ attr_accessor :fact_type
10
+ attr_reader :reading, :role_sequence # These are the Metamodel objects
11
+ attr_reader :side_effects # How to adjust the phrases if this fact_type match is accepted
11
12
  attr_accessor :fact # When binding fact instances the fact goes here
12
- attr_accessor :objectified_as # The Reading::RoleRef which objectified this fact type
13
+ attr_accessor :objectified_as # The VarRef which objectified this fact type
13
14
 
14
- def initialize role_refs_and_words, qualifiers = [], context_note = nil
15
- @phrases = role_refs_and_words
16
- role_refs.each { |role_ref| role_ref.reading = self }
15
+ def initialize phrases, qualifiers = [], context_note = nil
16
+ @phrases = phrases
17
+ var_refs.each { |var_ref| var_ref.clause = self }
17
18
  @qualifiers = qualifiers
18
19
  @context_note = context_note
19
20
  end
20
21
 
21
- def role_refs
22
- @phrases.select{|r| r.is_a?(RoleRef)}
22
+ def var_refs
23
+ @phrases.select{|r| r.respond_to?(:player)}
23
24
  end
24
25
 
25
- # A reading that contains only the name of a Concept and no literal or reading text
26
- # refers only to the existence of that Concept (as opposed to an instance of the concept).
26
+ # A clause that contains only the name of a ObjectType and no literal or reading text
27
+ # refers only to the existence of that ObjectType (as opposed to an instance of the object_type).
27
28
  def is_existential_type
28
29
  @phrases.size == 1 and
29
- @phrases[0].is_a?(RoleRef) and
30
+ @phrases[0].is_a?(VarRef) and
30
31
  !@phrases[0].literal
31
32
  end
32
33
 
33
34
  def display
35
+ to_s
36
+ end
37
+
38
+ def inspect
39
+ to_s
40
+ end
41
+
42
+ def to_s
34
43
  "#{
35
- @qualifiers && @qualifiers.size > 0 ? @qualifiers.inspect+' ' : nil
44
+ @qualifiers && @qualifiers.size > 0 ? @qualifiers.sort.inspect+' ' : nil
36
45
  }#{
37
- @phrases.map{|p| p.to_s }*" "
46
+ quotes = false
47
+ @phrases.inject(""){|s, p|
48
+ if String === p
49
+ s + (quotes ? '' : (quotes = true; '"')) + p.to_s + ' '
50
+ # REVISIT: Add something here when I re-add functions
51
+ # elsif FunctionCallChain === p
52
+ # s[0..-2] + (quotes ? (quotes = false; '" ') : '') + p.to_s
53
+ else # if VarRef === p
54
+ s[0..-2] + (quotes ? (quotes = false; '" ') : '') + p.to_s +
55
+ ((oj = p.nested_clauses) ? ' ('+ oj.map{|c| ((j=c.conjunction) ? j+' ' : '') + c.to_s}*' ' + ')' : '') +
56
+ ' '
57
+ # else
58
+ # raise "Unexpected phrase type in clause: #{p.to_s}"
59
+ end
60
+ }.sub(/ $/,'') + (quotes ? '"' : '')
38
61
  }#{
39
- @context_note && ' '+@context_note.inspect
62
+ @context_note && ' ' + @context_note.inspect
40
63
  }"
41
64
  end
42
65
 
43
- def inspect
44
- "#{@qualifiers && @qualifiers.size > 0 ? @qualifiers.inspect+' ' : nil}#{@phrases.map{|p|p.inspect}*" "}#{@context_note && ' '+@context_note.inspect}"
45
- end
46
-
47
66
  def identify_players_with_role_name context
48
- role_refs.each do |role_ref|
49
- role_ref.identify_player(context) if role_ref.role_name
50
- # Include players in an objectification join, if any
51
- role_ref.objectification_join.each{|reading| reading.identify_players_with_role_name(context)} if role_ref.objectification_join
67
+ var_refs.each do |var_ref|
68
+ var_ref.identify_players_with_role_name(context)
52
69
  end
53
70
  end
54
71
 
55
72
  def identify_other_players context
56
- role_refs.each do |role_ref|
57
- role_ref.identify_player(context) unless role_ref.player
58
- # Include players in an objectification join, if any
59
- role_ref.objectification_join.each{|reading| reading.identify_other_players(context)} if role_ref.objectification_join
73
+ var_refs.each do |var_ref|
74
+ var_ref.identify_other_players(context)
75
+ # Include players in nested clauses, if any
76
+ var_ref.nested_clauses.each{|clause| clause.identify_other_players(context)} if var_ref.nested_clauses
60
77
  end
61
78
  end
62
79
 
63
80
  def includes_literals
64
- role_refs.detect{|role_ref| role_ref.literal || (ja = role_ref.objectification_join and ja.detect{|jr| jr.includes_literals })}
81
+ var_refs.detect{|var_ref| var_ref.literal || (ja = var_ref.nested_clauses and ja.detect{|jr| jr.includes_literals })}
65
82
  end
66
83
 
67
- def bind_roles context
68
- role_names = role_refs.map{ |role_ref| role_ref.role_name }.compact
84
+ def is_equality_comparison
85
+ false
86
+ end
69
87
 
70
- # Check uniqueness of role names and subscripts within this reading:
88
+ def bind context
89
+ role_names = var_refs.map{ |var_ref| var_ref.role_name }.compact
90
+
91
+ # Check uniqueness of role names and subscripts within this clause:
71
92
  role_names.each do |rn|
72
93
  next if role_names.select{|rn2| rn2 == rn}.size == 1
73
- raise "Duplicate role #{rn.is_a?(Integer) ? "subscript" : "name"} '#{rn}' in reading"
94
+ raise "Duplicate role #{rn.is_a?(Integer) ? "subscript" : "name"} '#{rn}' in clause"
74
95
  end
75
96
 
76
- role_refs.each do |role_ref|
77
- role_ref.bind context
78
- # Include players in an objectification join, if any
79
- role_ref.objectification_join.each{|reading| reading.bind_roles(context)} if role_ref.objectification_join
97
+ var_refs.each do |var_ref|
98
+ var_ref.bind context
80
99
  end
81
100
  end
82
101
 
83
102
  def phrases_match(phrases)
84
103
  @phrases.zip(phrases).each do |mine, theirs|
85
- return false if mine.is_a?(RoleRef) != theirs.is_a?(RoleRef)
86
- if mine.is_a?(RoleRef)
104
+ return false if mine.is_a?(VarRef) != theirs.is_a?(VarRef)
105
+ if mine.is_a?(VarRef)
87
106
  return false unless mine.key == theirs.key
88
107
  else
89
108
  return false unless mine == theirs
@@ -93,134 +112,170 @@ module ActiveFacts
93
112
  end
94
113
 
95
114
  # This method chooses the existing fact type which matches most closely.
96
- # It returns nil if there is none, or a ReadingMatchSideEffects object if matched.
115
+ # It returns nil if there is none, or a ClauseMatchSideEffects object if matched.
97
116
  #
98
117
  # As this match may not necessarily be used (depending on the side effects),
99
- # no change is made to this Reading object - those will be done later.
100
- def match_existing_fact_type context
101
- raise "Internal error, reading already matched, should not match again" if @fact_type
102
- rrs = role_refs
103
- players = rrs.map{|rr| rr.player}
104
- raise "Must identify players before matching fact types" if players.include? nil
105
- raise "A fact type must involve at least one object type, but there are none in '#{inspect}'" if players.size == 0
106
-
107
- player_names = players.map{|p| p.name}
108
-
109
- debug :matching, "Looking for existing #{players.size}-ary fact types matching '#{inspect}'" do
110
- debug :matching, "Players are '#{player_names.inspect}'"
111
-
112
- # Match existing fact types in objectification joins first:
113
- rrs.each do |role_ref|
114
- next unless joins = role_ref.objectification_join and !joins.empty?
115
- role_ref.objectification_join.each do |oj|
116
- ft = oj.match_existing_fact_type(context)
117
- raise "Unrecognised fact type #{oj.display}" unless ft
118
- if (ft && ft.entity_type == role_ref.player)
119
- role_ref.objectification_of = ft
120
- oj.objectified_as = role_ref
118
+ # no change is made to this Clause object - those will be done later.
119
+ #
120
+ def match_existing_fact_type context, options = {}
121
+ raise "Internal error, clause already matched, should not match again" if @fact_type
122
+ # If we fail to match, try to a left contraction (or save this for a subsequent left contraction):
123
+ left_contract_this_onto = context.left_contractable_clause
124
+ new_conjunction = (conjunction == nil || conjunction == ',')
125
+ changed_conjunction = (lcc = context.left_contraction_conjunction) && lcc != conjunction
126
+ if context.left_contraction_allowed && (new_conjunction || changed_conjunction)
127
+ # Conjunctions are that/who, where, comparison-operator, ','
128
+ debug :matching, "A left contraction will be against #{self.inspect}, conjunction is #{conjunction.inspect}"
129
+ context.left_contractable_clause = self
130
+ left_contract_this_onto = nil # Can't left-contract this clause
131
+ end
132
+ context.left_contraction_conjunction = new_conjunction ? nil : @conjunction
133
+
134
+ contracted_role = nil
135
+
136
+ vrs = []+var_refs
137
+ begin
138
+ players = vrs.map{|vr| vr.player}
139
+ raise "Must identify players before matching fact types" if players.include? nil
140
+ raise "A fact type must involve at least one object type, but there are none in '#{inspect}'" if players.size == 0 && !left_contract_this_onto
141
+
142
+ player_names = players.map{|p| p.name}
143
+
144
+ debug :matching, "Looking for existing #{players.size}-ary fact types matching '#{contracted_role && contracted_role.inspect+' '}#{inspect}'" do
145
+ debug :matching, "Players are '#{player_names.inspect}'"
146
+
147
+ # Match existing fact types in nested clauses first (not for contractions):
148
+ if !contracted_role
149
+ vrs.each do |var_ref|
150
+ next if var_ref.is_a?(Operation)
151
+ next unless joins = var_ref.nested_clauses and !joins.empty?
152
+ var_ref.nested_clauses.each do |oj|
153
+ ft = oj.match_existing_fact_type(context)
154
+ raise "Unrecognised fact type #{oj.display}" unless ft
155
+ if (ft && ft.entity_type == var_ref.player)
156
+ var_ref.objectification_of = ft
157
+ oj.objectified_as = var_ref
158
+ end
159
+ end
160
+ raise "#{var_ref.inspect} contains objectification joins that do not objectify it" unless var_ref.objectification_of
121
161
  end
122
162
  end
123
- raise "#{role_ref.inspect} contains objectification joins that do not objectify it" unless role_ref.objectification_of
124
- end
125
163
 
126
- # For each role player, find the compatible types (the set of all subtypes and supertypes).
127
- # For a player that's an objectification, we don't allow implicit supertype joins
128
- player_related_types =
129
- rrs.map do |role_ref|
130
- player = role_ref.player
131
- ((role_ref.objectification_of ? [] : player.supertypes_transitive) +
132
- player.subtypes_transitive).uniq
133
- end
164
+ # For each role player, find the compatible types (the set of all subtypes and supertypes).
165
+ # For a player that's an objectification, we don't allow implicit supertype joins
166
+ player_related_types =
167
+ vrs.zip(players).map do |var_ref, player|
168
+ disallow_subtyping = var_ref && var_ref.objectification_of || options[:exact_type]
169
+ ((disallow_subtyping ? [] : player.supertypes_transitive) +
170
+ player.subtypes_transitive).uniq
171
+ end
134
172
 
135
- debug :matching, "Players must match '#{player_related_types.map{|pa| pa.map{|p|p.name}}.inspect}'"
136
-
137
- # The candidate fact types have the right number of role players of related types.
138
- # If any role is played by a supertype or subtype of the required type, there's an implicit subtyping join
139
- candidate_fact_types =
140
- player_related_types[0].map do |related_type|
141
- related_type.all_role.select do |role|
142
- all_roles = role.fact_type.all_role
143
- next if all_roles.size != players.size # Wrong number of players
144
- next if role.fact_type.is_a?(ActiveFacts::Metamodel::ImplicitFactType)
145
-
146
- all_players = all_roles.map{|r| r.concept} # All the players of this candidate fact type
147
-
148
- next if player_related_types[1..-1]. # We know the first player is compatible, check the rest
149
- detect do |player_types| # Make sure that there remains a compatible player
150
- # player_types is an array of the types compatible with the Nth player
151
- compatible_player = nil
152
- all_players.each_with_index do |p, i|
153
- if player_types.include?(p)
154
- compatible_player = p
155
- all_players.delete_at(i)
156
- break
173
+ debug :matching, "Players must match '#{player_related_types.map{|pa| pa.map{|p|p.name}}.inspect}'"
174
+
175
+ # The candidate fact types have the right number of role players of related types.
176
+ # If any role is played by a supertype or subtype of the required type, there's an implicit subtyping join
177
+ candidate_fact_types =
178
+ player_related_types[0].map do |related_type|
179
+ related_type.all_role.select do |role|
180
+ all_roles = role.fact_type.all_role
181
+ next if all_roles.size != players.size # Wrong number of players
182
+ next if role.fact_type.is_a?(ActiveFacts::Metamodel::ImplicitFactType)
183
+
184
+ all_players = all_roles.map{|r| r.object_type} # All the players of this candidate fact type
185
+
186
+ next if player_related_types[1..-1]. # We know the first player is compatible, check the rest
187
+ detect do |player_types| # Make sure that there remains a compatible player
188
+ # player_types is an array of the types compatible with the Nth player
189
+ compatible_player = nil
190
+ all_players.each_with_index do |p, i|
191
+ if player_types.include?(p)
192
+ compatible_player = p
193
+ all_players.delete_at(i)
194
+ break
195
+ end
157
196
  end
197
+ !compatible_player
158
198
  end
159
- !compatible_player
160
- end
161
199
 
162
- true
163
- end.
164
- map{ |role| role.fact_type}
165
- end.flatten.uniq
200
+ true
201
+ end.
202
+ map{ |role| role.fact_type}
203
+ end.flatten.uniq
166
204
 
167
- # If there is more than one possible exact match (same adjectives) with different subyping, the implicit join is ambiguous and is not allowed
205
+ # If there is more than one possible exact match (same adjectives) with different subyping, the implicit join is ambiguous and is not allowed
168
206
 
169
- debug :matching, "Looking amongst #{candidate_fact_types.size} existing fact types for one matching '#{inspect}'" do
170
- matches = {}
171
- candidate_fact_types.map do |fact_type|
172
- fact_type.all_reading.map do |reading|
173
- next unless side_effects = reading_matches(fact_type, reading)
174
- matches[reading] = side_effects if side_effects
207
+ debug :matching, "Looking amongst #{candidate_fact_types.size} existing fact types for one matching '#{contracted_role && contracted_role.inspect+' '}#{inspect}'" do
208
+ matches = {}
209
+ candidate_fact_types.map do |fact_type|
210
+ fact_type.all_reading.map do |reading|
211
+ next unless side_effects = clause_matches(fact_type, reading, contracted_role)
212
+ matches[reading] = side_effects if side_effects
213
+ end
175
214
  end
176
- end
177
215
 
178
- # REVISIT: Side effects that leave extra adjectives should only be allowed if the
179
- # same extra adjectives exist in some other reading in the same declaration.
180
- # The extra adjectives are then necessary to associate the two role players
181
- # when consumed adjectives were required to bind to the underlying fact types.
182
- # This requires the final decision on fact type matching to be postponed until
183
- # the whole declaration has been processed and the extra adjectives can be matched.
184
-
185
- best_matches = matches.keys.sort_by{|match|
186
- # Between equivalents, prefer the one without a join on the first role
187
- (m = matches[match]).cost*2 + ((!(e = m.role_side_effects[0]) || e.cost) == 0 ? 0 : 1)
188
- }
189
- debug :matching_fails, "Found #{matches.size} valid matches#{matches.size > 0 ? ', best is '+best_matches[0].expand : ''}"
190
-
191
- if matches.size > 1
192
- first = matches[best_matches[0]]
193
- cost = first.cost
194
- equal_best = matches.select{|k,m| m.cost == cost}
216
+ # REVISIT: Side effects that leave extra adjectives should only be allowed if the
217
+ # same extra adjectives exist in some other clause in the same declaration.
218
+ # The extra adjectives are then necessary to associate the two role players
219
+ # when consumed adjectives were required to bind to the underlying fact types.
220
+ # This requires the final decision on fact type matching to be postponed until
221
+ # the whole declaration has been processed and the extra adjectives can be matched.
222
+
223
+ best_matches = matches.keys.sort_by{|match|
224
+ # Between equivalents, prefer the one without a join on the first role
225
+ (m = matches[match]).cost*2 + ((!(e = m.role_side_effects[0]) || e.cost) == 0 ? 0 : 1)
226
+ }
227
+ debug :matching_fails, "Found #{matches.size} valid matches#{matches.size > 0 ? ', best is '+best_matches[0].expand : ''}"
228
+
229
+ if matches.size > 1
230
+ first = matches[best_matches[0]]
231
+ cost = first.cost
232
+ equal_best = matches.select{|k,m| m.cost == cost}
233
+
234
+ if equal_best.size > 1 and equal_best.detect{|k,m| !m.fact_type.is_a?(Metamodel::TypeInheritance)}
235
+ # Complain if there's more than one equivalent cost match (unless all are TypeInheritance):
236
+ raise "#{@phrases.inspect} could match any of the following:\n\t"+
237
+ best_matches.map { |reading| reading.expand + " with " + matches[reading].describe } * "\n\t"
238
+ end
239
+ end
195
240
 
196
- if equal_best.size > 1 and equal_best.detect{|k,m| !m.fact_type.is_a?(Metamodel::TypeInheritance)}
197
- # Complain if there's more than one equivalent cost match (unless all are TypeInheritance):
198
- raise "#{@phrases.inspect} could match any of the following:\n\t"+
199
- best_matches.map { |reading| reading.expand + " with " + matches[reading].describe } * "\n\t"
241
+ if matches.size >= 1
242
+ @reading = best_matches[0]
243
+ @side_effects = matches[@reading]
244
+ @fact_type = @side_effects.fact_type
245
+ debug :matching, "Matched '#{@fact_type.default_reading}'"
246
+ @phrases.unshift(contracted_role) if contracted_role
247
+ apply_side_effects(context, @side_effects)
248
+ return @fact_type
200
249
  end
201
- end
202
250
 
203
- if matches.size >= 1
204
- @reading = best_matches[0]
205
- @side_effects = matches[@reading]
206
- @fact_type = @side_effects.fact_type
207
- debug :matching, "Matched '#{@fact_type.default_reading}'"
208
- apply_side_effects(context, @side_effects)
209
- return @fact_type
210
251
  end
211
-
252
+ debug :matching, "No fact type matched, candidates were '#{candidate_fact_types.map{|ft| ft.default_reading}*"', '"}'"
253
+ end
254
+ if left_contract_this_onto && !contracted_role
255
+ contracted_from = left_contract_this_onto.var_refs[0]
256
+ contraction_player = contracted_from.player
257
+ contracted_role = VarRef.new(contraction_player.name)
258
+ contracted_role.player = contracted_from.player
259
+ contracted_role.role_name = contracted_from.role_name
260
+ contracted_role.bind(context)
261
+ vrs.unshift contracted_role
262
+
263
+ debug :matching, "Failed to match #{inspect}. Trying again using left contraction onto #{contraction_player.name}"
264
+ redo
212
265
  end
213
- debug :matching, "No fact type matched, candidates were '#{candidate_fact_types.map{|ft| ft.default_reading}*"', '"}'"
266
+ end until true # Once through, unless we hit a redo
267
+ if contracted_role
268
+ contracted_role.unbind context
214
269
  end
215
270
  @fact_type = nil
216
271
  end
217
272
 
218
- # The ActiveFacts::Metamodel::Reading passed has the same players as this Compiler::Reading. Does it match?
273
+ # The Reading passed has the same players as this Clause. Does it match?
219
274
  # Twisty curves. This is a complex bit of code!
220
- # Find whether the phrases of this reading match the fact type reading,
275
+ # Find whether the phrases of this clause match the fact type reading,
221
276
  # which may require absorbing unmarked adjectives.
222
277
  #
223
- # If it does match, make the required changes and set @role_ref to the matching role.
278
+ # If it does match, make the required changes and set @var_ref to the matching role ref.
224
279
  # Adjectives that were used to match are removed (and leaving any additional adjectives intact).
225
280
  #
226
281
  # Approach:
@@ -233,20 +288,19 @@ module ActiveFacts
233
288
  # trailing adjectives, both marked and unmarked, are absorbed too.
234
289
  # a word that matches the reading's
235
290
  #
236
- def reading_matches(fact_type, reading)
291
+ def clause_matches(fact_type, reading, contracted_role = nil)
237
292
  side_effects = [] # An array of items for each role, describing any side-effects of the match.
238
293
  intervening_words = nil
239
294
  residual_adjectives = false
240
- debug :matching_fails, "Does '#{@phrases.inspect}' match '#{reading.expand}'" do
295
+ phrases = [contracted_role].compact+@phrases
296
+ debug :matching_fails, "Does '#{phrases.inspect}' match '#{reading.expand}'" do
241
297
  phrase_num = 0
242
298
  reading_parts = reading.text.split(/\s+/)
243
- # Check that the number of roles matches (skipped, the caller should have done it):
244
- # return nil unless reading_parts.select{|p| p =~ /\{(\d+)\}/}.size == role_refs.size
245
299
  reading_parts.each do |element|
246
300
  if element !~ /\{(\d+)\}/
247
301
  # Just a word; it must match
248
- unless @phrases[phrase_num] == element
249
- debug :matching_fails, "Mismatched ordinary word #{@phrases[phrase_num].inspect} (wanted #{element})"
302
+ unless phrases[phrase_num] == element
303
+ debug :matching_fails, "Mismatched ordinary word #{phrases[phrase_num].inspect} (wanted #{element})"
250
304
  return nil
251
305
  end
252
306
  phrase_num += 1
@@ -255,12 +309,14 @@ module ActiveFacts
255
309
  role_ref = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[$1.to_i]
256
310
  end
257
311
 
312
+ player = role_ref.role.object_type
313
+
258
314
  # Figure out what's next in this phrase (the next player and the words leading up to it)
259
315
  next_player_phrase = nil
260
316
  intervening_words = []
261
- while (phrase = @phrases[phrase_num])
317
+ while (phrase = phrases[phrase_num])
262
318
  phrase_num += 1
263
- if phrase.is_a?(RoleRef)
319
+ if phrase.respond_to?(:player)
264
320
  next_player_phrase = phrase
265
321
  next_player_phrase_num = phrase_num-1
266
322
  break
@@ -268,21 +324,24 @@ module ActiveFacts
268
324
  intervening_words << phrase
269
325
  end
270
326
  end
271
-
272
- player = role_ref.role.concept
273
327
  return nil unless next_player_phrase # reading has more players than we do.
328
+ next_player = next_player_phrase.player
274
329
 
275
330
  # The next player must match:
276
331
  common_supertype = nil
277
- if next_player_phrase.player != player
332
+ if next_player != player
278
333
  # This relies on the supertypes being in breadth-first order:
279
- common_supertype = (next_player_phrase.player.supertypes_transitive & player.supertypes_transitive)[0]
334
+ common_supertype = (next_player.supertypes_transitive & player.supertypes_transitive)[0]
280
335
  if !common_supertype
281
- debug :matching_fails, "Reading discounted because next player #{player.name} doesn't match #{next_player_phrase.player.name}"
336
+ debug :matching_fails, "Reading discounted because next player #{player.name} doesn't match #{next_player.name}"
282
337
  return nil
283
338
  end
284
339
 
285
340
  debug :matching_fails, "Subtype join is required between #{player.name} and #{next_player_phrase.player.name} via common supertype #{common_supertype.name}"
341
+ else
342
+ if !next_player_phrase
343
+ next # Contraction succeeded so far
344
+ end
286
345
  end
287
346
 
288
347
  # It's the right player. Do the adjectives match? This must include the intervening_words, if any.
@@ -316,12 +375,14 @@ module ActiveFacts
316
375
  phrase_ta = (next_player_phrase.trailing_adjective||'').split(/\s+/)
317
376
  i = 0 # Pad the phrases up to the size of the trailing_adjectives
318
377
  while phrase_ta.size < ta.size
319
- break unless (word = @phrases[phrase_num+i]).is_a?(String)
378
+ break unless (word = phrases[phrase_num+i]).is_a?(String)
320
379
  phrase_ta << word
321
380
  i += 1
322
381
  end
382
+ # ta is the adjectives in the fact type being matched
383
+ # phrase_ta is the explicit adjectives augmented with implicit ones to the same size
323
384
  return nil if ta != phrase_ta[0,ta.size]
324
- role_has_residual_adjectives = true if phrase_ta.size > ta.size || i < ta.size
385
+ role_has_residual_adjectives = true if phrase_ta.size > ta.size
325
386
  absorbed_followers = i
326
387
  phrase_num += i # Skip following words that were consumed as trailing adjectives
327
388
  elsif next_player_phrase.trailing_adjective
@@ -330,7 +391,7 @@ module ActiveFacts
330
391
 
331
392
  # REVISIT: I'm not even sure I should be caring about role names here.
332
393
  # Role names are on roles, and are only useful within the fact type definition.
333
- # At some point, we need to worry about role names on readings within fact type derivations,
394
+ # At some point, we need to worry about role names on clauses within fact type derivations,
334
395
  # which means they'll move to the Role Ref class; but even then they only match within the
335
396
  # definition that creates that Role Ref.
336
397
  =begin
@@ -343,24 +404,25 @@ module ActiveFacts
343
404
  =end
344
405
 
345
406
  residual_adjectives ||= role_has_residual_adjectives
346
- if residual_adjectives && next_player_phrase.binding.refs.size == 1
407
+ if residual_adjectives && next_player_phrase.variable.refs.size == 1
408
+ # This makes matching order-dependent, because there may be no "other purpose"
409
+ # until another reading has been matched and the roles rebound.
347
410
  debug :matching_fails, "Residual adjectives have no other purpose, so this match fails"
348
411
  return nil
349
412
  end
350
413
 
351
414
  # The phrases matched this reading's next role_ref, save data to apply the side-effects:
352
- debug :matching_fails, "Saving side effects for #{next_player_phrase.term}, absorbs #{absorbed_precursors}/#{absorbed_followers}#{common_supertype ? ', join over supertype '+ common_supertype.name : ''}" if absorbed_precursors+absorbed_followers+(common_supertype ? 1 : 0) > 0
353
- side_effects << ReadingMatchSideEffect.new(next_player_phrase, role_ref, next_player_phrase_num, absorbed_precursors, absorbed_followers, common_supertype, role_has_residual_adjectives)
415
+ side_effects << ClauseMatchSideEffect.new(next_player_phrase, role_ref, next_player_phrase_num, absorbed_precursors, absorbed_followers, common_supertype, role_has_residual_adjectives)
354
416
  end
355
417
 
356
- if phrase_num != @phrases.size || !intervening_words.empty?
357
- debug :matching_fails, "Extra words #{(intervening_words + @phrases[phrase_num..-1]).inspect}"
418
+ if phrase_num != phrases.size || !intervening_words.empty?
419
+ debug :matching_fails, "Extra words #{(intervening_words + phrases[phrase_num..-1]).inspect}"
358
420
  return nil
359
421
  end
360
422
 
361
423
  if fact_type.is_a?(Metamodel::TypeInheritance)
362
424
  # There may be only one subtyping join on a TypeInheritance fact type.
363
- ti_joins = side_effects.select{|se| se.common_supertype}
425
+ ti_joins = side_effects.select{|side_effect| side_effect.common_supertype}
364
426
  if ti_joins.size > 1 # Not allowed
365
427
  debug :matching_fails, "Can't have more than one subtyping join on a TypeInheritance fact type"
366
428
  return nil
@@ -368,7 +430,7 @@ module ActiveFacts
368
430
 
369
431
  if ti = ti_joins[0]
370
432
  # The Type Inheritance join must continue in the same direction as this reading.
371
- allowed = fact_type.supertype == ti.role_ref.role.concept ?
433
+ allowed = fact_type.supertype == ti.role_ref.role.object_type ?
372
434
  fact_type.subtype.supertypes_transitive :
373
435
  fact_type.supertype.subtypes_transitive
374
436
  if !allowed.include?(ti.common_supertype)
@@ -378,11 +440,14 @@ module ActiveFacts
378
440
  end
379
441
  end
380
442
 
381
- debug :matching, "Matched reading '#{reading.expand}' with #{side_effects.map{|se| se.absorbed_precursors+se.absorbed_followers + (se.common_supertype ? 1 : 0)
382
- }.inspect} side effects#{residual_adjectives ? ' and residual adjectives' : ''}"
443
+ debug :matching, "Matched reading '#{reading.expand}' (with #{
444
+ side_effects.map{|side_effect|
445
+ side_effect.absorbed_precursors+side_effect.absorbed_followers + (side_effect.common_supertype ? 1 : 0)
446
+ }.inspect
447
+ } side effects)#{residual_adjectives ? ' and residual adjectives' : ''}"
383
448
  end
384
449
  # There will be one side_effects for each role player
385
- ReadingMatchSideEffects.new(fact_type, self, residual_adjectives, side_effects)
450
+ ClauseMatchSideEffects.new(fact_type, self, residual_adjectives, side_effects)
386
451
  end
387
452
 
388
453
  def apply_side_effects(context, side_effects)
@@ -390,68 +455,71 @@ module ActiveFacts
390
455
  # Enact the side-effects of this match (delete the consumed adjectives):
391
456
  # Since this deletes words from the phrases, we do it in reverse order.
392
457
  debug :matching, "Apply side-effects" do
393
- side_effects.apply_all do |se|
458
+ side_effects.apply_all do |side_effect|
459
+ phrase = side_effect.phrase
394
460
 
395
461
  # We re-use the role_ref if possible (no extra adjectives were used, no rolename or join, etc).
396
- debug :matching, "side-effect means binding #{se.phrase.inspect} matches role ref #{se.role_ref.role.concept.name}"
397
- se.phrase.role_ref = se.role_ref
462
+ debug :matching, "side-effect means variable #{phrase.inspect} matches role ref #{side_effect.role_ref.role.object_type.name}"
463
+ phrase.role_ref = side_effect.role_ref
398
464
 
399
465
  changed = false
400
466
 
401
467
  # Where this phrase has leading or trailing adjectives that are in excess of those of
402
468
  # the role_ref, those must be local, and we'll need to extract them.
403
469
 
404
- if rra = se.role_ref.trailing_adjective
405
- debug :matching, "Deleting matched trailing adjective '#{rra}'#{se.absorbed_followers>0 ? " in #{se.absorbed_followers} followers" : ""}"
470
+ if rra = side_effect.role_ref.trailing_adjective
471
+ debug :matching, "Deleting matched trailing adjective '#{rra}'#{side_effect.absorbed_followers>0 ? " in #{side_effect.absorbed_followers} followers" : ""}"
406
472
 
407
473
  # These adjective(s) matched either an adjective here, or a follower word, or both.
408
- if a = se.phrase.trailing_adjective
474
+ if a = phrase.trailing_adjective
409
475
  if a.size >= rra.size
410
- a.slice!(0, rra.size+1) # Remove the matched adjectives and the space (if any)
411
- se.phrase.wipe_trailing_adjective if a.empty?
476
+ a = a[rra.size+1..-1]
477
+ phrase.trailing_adjective = a == '' ? nil : a
412
478
  changed = true
413
479
  end
414
- elsif se.absorbed_followers > 0
415
- se.phrase.wipe_trailing_adjective
416
- # This phrase is absorbing non-hyphenated adjective(s), which changes its binding
417
- se.phrase.trailing_adjective = @phrases.slice!(se.num+1, se.absorbed_followers)*' '
418
- se.phrase.rebind context
480
+ elsif side_effect.absorbed_followers > 0
481
+ # The following statement is incorrect. The absorbed adjective is what caused the match.
482
+ # This phrase is absorbing non-hyphenated adjective(s), which changes its variable
483
+ # phrase.trailing_adjective =
484
+ @phrases.slice!(side_effect.num+1, side_effect.absorbed_followers)*' '
419
485
  changed = true
420
486
  end
421
487
  end
422
488
 
423
- if rra = se.role_ref.leading_adjective
424
- debug :matching, "Deleting matched leading adjective '#{rra}'#{se.absorbed_precursors>0 ? " in #{se.absorbed_precursors} precursors" : ""}}"
489
+ if rra = side_effect.role_ref.leading_adjective
490
+ debug :matching, "Deleting matched leading adjective '#{rra}'#{side_effect.absorbed_precursors>0 ? " in #{side_effect.absorbed_precursors} precursors" : ""}}"
425
491
 
426
492
  # These adjective(s) matched either an adjective here, or a precursor word, or both.
427
- if a = se.phrase.leading_adjective
493
+ if a = phrase.leading_adjective
428
494
  if a.size >= rra.size
429
- a.slice!(-rra.size, 1000) # Remove the matched adjectives and the space
430
- a.chop!
431
- se.phrase.wipe_leading_adjective if a.empty?
495
+ a = a[0...-rra.size]
496
+ phrase.leading_adjective = a == '' ? nil : a
432
497
  changed = true
433
498
  end
434
- elsif se.absorbed_precursors > 0
435
- se.phrase.wipe_leading_adjective
436
- # This phrase is absorbing non-hyphenated adjective(s), which changes its binding
437
- se.phrase.leading_adjective = @phrases.slice!(se.num-se.absorbed_precursors, se.absorbed_precursors)*' '
438
- se.phrase.rebind context
499
+ elsif side_effect.absorbed_precursors > 0
500
+ # The following statement is incorrect. The absorbed adjective is what caused the match.
501
+ # This phrase is absorbing non-hyphenated adjective(s), which changes its variable
502
+ #phrase.leading_adjective =
503
+ @phrases.slice!(side_effect.num-side_effect.absorbed_precursors, side_effect.absorbed_precursors)*' '
439
504
  changed = true
440
505
  end
441
506
  end
507
+ if changed
508
+ phrase.rebind context
509
+ end
442
510
 
443
511
  end
444
512
  end
445
513
  end
446
514
 
447
- # Make a new fact type with roles for this reading.
515
+ # Make a new fact type with roles for this clause.
448
516
  # Don't assign @fact_type; that will happen when the reading is added
449
517
  def make_fact_type vocabulary
450
518
  fact_type = vocabulary.constellation.FactType(:new)
451
519
  debug :matching, "Making new fact type for #{@phrases.inspect}" do
452
520
  @phrases.each do |phrase|
453
- next unless phrase.is_a?(RoleRef)
454
- phrase.role = vocabulary.constellation.Role(fact_type, fact_type.all_role.size, :concept => phrase.player)
521
+ next unless phrase.respond_to?(:player)
522
+ phrase.role = vocabulary.constellation.Role(fact_type, fact_type.all_role.size, :object_type => phrase.player)
455
523
  phrase.role.role_name = phrase.role_name if phrase.role_name && phrase.role_name.is_a?(String)
456
524
  end
457
525
  end
@@ -466,12 +534,12 @@ module ActiveFacts
466
534
  index = 0
467
535
  debug :matching, "Making new reading for #{@phrases.inspect}" do
468
536
  reading_words.map! do |phrase|
469
- if phrase.is_a?(RoleRef)
537
+ if phrase.respond_to?(:player)
470
538
  # phrase.role will be set if this reading was used to make_fact_type.
471
- # Otherwise we have to find the existing role via the Binding. This is pretty ugly.
539
+ # Otherwise we have to find the existing role via the Variable. This is pretty ugly.
472
540
  unless phrase.role
473
- # Find another binding for this phrase which already has a role_ref to the same fact type:
474
- ref = phrase.binding.refs.detect{|ref| ref.role_ref && ref.role_ref.role.fact_type == fact_type}
541
+ # Find another variable for this phrase which already has a role_ref to the same fact type:
542
+ ref = phrase.variable.refs.detect{|ref| ref.role_ref && ref.role_ref.role.fact_type == fact_type}
475
543
  role_ref = ref.role_ref
476
544
  phrase.role = role_ref.role
477
545
  end
@@ -500,10 +568,11 @@ module ActiveFacts
500
568
  end
501
569
  if existing = @fact_type.all_reading.detect{|r|
502
570
  r.text == reading_words*' ' and
503
- r.role_sequence.all_role_ref_in_order.map{|rr| rr.role.concept} ==
504
- role_sequence.all_role_ref_in_order.map{|rr| rr.role.concept}
571
+ r.role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type} ==
572
+ role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type}
505
573
  }
506
- raise "Reading '#{existing.expand}' already exists, so why are we creating a duplicate?"
574
+ existing
575
+ #raise "Reading '#{existing.expand}' already exists, so why are we creating a duplicate?"
507
576
  end
508
577
  constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_words*" ")
509
578
  end
@@ -520,13 +589,13 @@ module ActiveFacts
520
589
  reading_words = []
521
590
  new_role_sequence_needed = false
522
591
  @phrases.each do |phrase|
523
- if phrase.is_a?(RoleRef)
592
+ if phrase.respond_to?(:player)
524
593
  role_phrases << phrase
525
594
  reading_words << "{#{phrase.role_ref.ordinal}}"
526
- if phrase.role_name # ||
527
- # phrase.leading_adjective ||
528
- # phrase.trailing_adjective
529
- debug :matching, "phrase in matched reading has residual adjectives or role name, so needs a new role_sequence" if @fact_type.all_reading.size > 0
595
+ if phrase.role_name != phrase.role_ref.role.role_name ||
596
+ phrase.leading_adjective ||
597
+ phrase.trailing_adjective
598
+ 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
530
599
  new_role_sequence_needed = true
531
600
  end
532
601
  else
@@ -535,7 +604,7 @@ module ActiveFacts
535
604
  end
536
605
  end
537
606
 
538
- debug :matching, "Reading '#{reading_words*' '}' #{new_role_sequence_needed ? 'requires' : 'does not require'} a new Role Sequence"
607
+ debug :matching, "Clause '#{reading_words*' '}' #{new_role_sequence_needed ? 'requires' : 'does not require'} a new Role Sequence"
539
608
 
540
609
  constellation = @fact_type.constellation
541
610
  reading_text = reading_words*" "
@@ -568,37 +637,53 @@ module ActiveFacts
568
637
  debug :matching, "Using existing role sequence for new reading '#{reading_text}'"
569
638
  end
570
639
  end
571
- if @fact_type.all_reading.detect{|r| r.text == reading_text}
572
- raise "Reading '#{@reading.expand}' already exists, so why are we creating a duplicate (with #{extra_adjectives.inspect})?"
640
+ if @fact_type.all_reading.
641
+ detect do |r|
642
+ r.text == reading_text and
643
+ r.role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type} ==
644
+ @role_sequence.all_role_ref_in_order.map{|rr| rr.role.object_type}
645
+ end
646
+ # raise "Reading '#{@reading.expand}' already exists, so why are we creating a duplicate (with #{extra_adjectives.inspect})?"
647
+ else
648
+ constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_text)
573
649
  end
574
- constellation.Reading(@fact_type, @fact_type.all_reading.size, :role_sequence => @role_sequence, :text => reading_text)
575
650
  @role_sequence
576
651
  end
577
652
 
578
653
  def make_embedded_constraints vocabulary
579
- role_refs.each do |role_ref|
580
- next unless role_ref.quantifier
581
- # puts "Quantifier #{role_ref.inspect} not implemented as a presence constraint"
582
- role_ref.make_embedded_presence_constraint vocabulary
654
+ var_refs.each do |var_ref|
655
+ next unless var_ref.quantifier
656
+ # puts "Quantifier #{var_ref.inspect} not implemented as a presence constraint"
657
+ var_ref.make_embedded_presence_constraint vocabulary
583
658
  end
584
659
 
585
660
  if @qualifiers && @qualifiers.size > 0
586
-
587
- rc = RingConstraint.new(@role_sequence, @qualifiers)
588
- rc.vocabulary = vocabulary
589
- rc.constellation = vocabulary.constellation
590
- rc.compile
661
+ # We shouldn't make a new ring constraint if there's already one over this ring.
662
+ existing_rcs =
663
+ @role_sequence.all_role_ref.map{|rr| rr.role.all_ring_constraint.to_a }.flatten.uniq
664
+ unless existing_rcs[0]
665
+ rc = RingConstraint.new(@role_sequence, @qualifiers)
666
+ rc.vocabulary = vocabulary
667
+ rc.constellation = vocabulary.constellation
668
+ rc.compile
669
+ else
670
+ # Ignore the fact that the ring might be of a different type.
671
+ end
591
672
 
592
673
  # REVISIT: Check maybe and other qualifiers:
593
674
  debug :constraint, "Need to make constraints for #{@qualifiers*', '}" if @qualifiers.size > 0
594
675
  end
595
676
  end
596
677
 
678
+ def is_naked_object_type
679
+ @phrases.size == 1 && var_refs.size == 1
680
+ end
681
+
597
682
  end
598
683
 
599
- # An instance of ReadingMatchSideEffects is created when the compiler matches an existing fact type.
684
+ # An instance of ClauseMatchSideEffects is created when the compiler matches an existing fact type.
600
685
  # It captures the details that have to be adjusted for the match to be regarded a success.
601
- class ReadingMatchSideEffect
686
+ class ClauseMatchSideEffect
602
687
  attr_reader :phrase, :role_ref, :num, :absorbed_precursors, :absorbed_followers, :common_supertype, :residual_adjectives
603
688
 
604
689
  def initialize phrase, role_ref, num, absorbed_precursors, absorbed_followers, common_supertype, residual_adjectives
@@ -609,62 +694,73 @@ module ActiveFacts
609
694
  @absorbed_followers = absorbed_followers
610
695
  @common_supertype = common_supertype
611
696
  @residual_adjectives = residual_adjectives
697
+ debug :matching_fails, "Saving side effects for #{@phrase.term}, absorbs #{@absorbed_precursors}/#{@absorbed_followers}#{@common_supertype ? ', join over supertype '+ @common_supertype.name : ''}" if @absorbed_precursors+@absorbed_followers+(@common_supertype ? 1 : 0) > 0
612
698
  end
613
699
 
614
700
  def cost
615
701
  absorbed_precursors + absorbed_followers + (common_supertype ? 1 : 0)
616
702
  end
703
+
704
+ def to_s
705
+ "#{@phrase.inspect} absorbs #{@absorbed_precursors||0}/#{@absorbed_followers||0} at #{@num}#{@common_supertype && ' super '+@common_supertype.name}#{@residual_adjectives ? ' with residual adjectives' : ''}"
706
+ end
617
707
  end
618
708
 
619
- class ReadingMatchSideEffects
709
+ class ClauseMatchSideEffects
620
710
  attr_reader :residual_adjectives
621
711
  attr_reader :fact_type
622
- attr_reader :role_side_effects # One array of values per RoleRef matched, in order
712
+ attr_reader :role_side_effects # One array of values per VarRef matched, in order
623
713
 
624
- def initialize fact_type, reading, residual_adjectives, role_side_effects
714
+ def initialize fact_type, clause, residual_adjectives, role_side_effects
625
715
  @fact_type = fact_type
626
- @reading = reading
716
+ @clause = clause
627
717
  @residual_adjectives = residual_adjectives
628
718
  @role_side_effects = role_side_effects
629
719
  end
630
720
 
721
+ def to_s
722
+ 'side-effects are [' +
723
+ @role_side_effects.map{|r| r.to_s}*', ' +
724
+ ']' +
725
+ "#{@residual_adjectives ? ' with residual adjectives' : ''}"
726
+ end
727
+
631
728
  def apply_all &b
632
729
  @role_side_effects.reverse.each{ |role_side_effect| b.call(*role_side_effect) }
633
730
  end
634
731
 
635
732
  def cost
636
733
  c = 0
637
- @role_side_effects.each do |se|
638
- c += se.cost
734
+ @role_side_effects.each do |side_effect|
735
+ c += side_effect.cost
639
736
  end
640
737
  c + (@residual_adjectives ? 1 : 0)
641
738
  end
642
739
 
643
740
  def describe
644
741
  actual_effects =
645
- @role_side_effects.map do |se|
646
- ( [se.common_supertype ? "supertype join over #{se.common_supertype.name}" : nil] +
647
- [se.absorbed_precursors > 0 ? "absorbs #{se.absorbed_precursors} preceding words" : nil] +
648
- [se.absorbed_followers > 0 ? "absorbs #{se.absorbed_followers} following words" : nil]
742
+ @role_side_effects.map do |side_effect|
743
+ ( [side_effect.common_supertype ? "supertype join over #{side_effect.common_supertype.name}" : nil] +
744
+ [side_effect.absorbed_precursors > 0 ? "absorbs #{side_effect.absorbed_precursors} preceding words" : nil] +
745
+ [side_effect.absorbed_followers > 0 ? "absorbs #{side_effect.absorbed_followers} following words" : nil]
649
746
  )
650
747
  end.flatten.compact*','
651
748
  actual_effects.empty? ? "no side effects" : actual_effects
652
749
  end
653
750
  end
654
751
 
655
- class RoleRef
656
- attr_reader :term, :leading_adjective, :trailing_adjective, :quantifier, :function_call, :role_name, :value_constraint, :literal, :objectification_join
657
- attr_reader :player
658
- attr_accessor :binding
659
- attr_accessor :reading # The reading that this RoleRef is part of
660
- attr_accessor :role # This refers to the ActiveFacts::Metamodel::Role
661
- attr_accessor :role_ref # This refers to the ActiveFacts::Metamodel::RoleRef
662
- attr_accessor :objectification_of # If objectification_join is set, this is the fact type it objectifies
752
+ class VarRef
753
+ attr_reader :term, :quantifier, :function_call, :value_constraint, :literal, :nested_clauses
754
+ attr_accessor :leading_adjective, :trailing_adjective, :role_name
755
+ attr_accessor :player # What ObjectType does the Variable denote
756
+ attr_accessor :variable # What Variable for that ObjectType
757
+ attr_accessor :role # Which Role of this ObjectType
758
+ attr_accessor :role_ref # Which RoleRef to that Role
759
+ attr_accessor :clause # The clause that this VarRef is part of
760
+ attr_accessor :objectification_of # If nested_clauses is set, this is the fact type it objectifies
663
761
  attr_reader :embedded_presence_constraint # This refers to the ActiveFacts::Metamodel::PresenceConstraint
664
- attr_writer :leading_adjective
665
- attr_writer :role_name # For assigning subscript when found in identifying roles list
666
762
 
667
- def initialize term, leading_adjective = nil, trailing_adjective = nil, quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, objectification_join = nil
763
+ def initialize term, leading_adjective = nil, trailing_adjective = nil, quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, nested_clauses = nil
668
764
  @term = term
669
765
  @leading_adjective = leading_adjective
670
766
  @trailing_adjective = trailing_adjective
@@ -673,31 +769,29 @@ module ActiveFacts
673
769
  @role_name = role_name
674
770
  @value_constraint = value_constraint
675
771
  @literal = literal
676
- @objectification_join = objectification_join
772
+ @nested_clauses = nested_clauses
677
773
  end
678
774
 
679
775
  def inspect
680
- "RoleRef<#{
681
- @quantifier && @quantifier.inspect+' ' }#{
682
- @leading_adjective && @leading_adjective.sub(/ |$/,'- ').sub(/ *$/,' ') }#{
683
- @term }#{
684
- @trailing_adjective && ' '+@trailing_adjective.sub(/(.* |^)/, '\1-') }#{
685
- @role_name and @role_name.is_a?(Integer) ? "(#{@role_name})" : " (as #{@role_name})" }#{
686
- @literal && ' '+@literal.inspect }#{
687
- @value_constraint && ' '+@value_constraint.inspect
688
- }#{
689
- @objectification_join ? "(where #{@objectification_join.inspect})" : ""
690
- }>"
776
+ to_s
691
777
  end
692
778
 
693
779
  def to_s
694
- "#{
695
- @quantifier && @quantifier.inspect+' ' }#{
696
- @leading_adjective && @leading_adjective.sub(/ |$/,'- ').sub(/ *$/,' ') }#{
697
- @role_name || @term }#{
698
- @trailing_adjective && ' '+@trailing_adjective.sub(/(.* |^)/, '\1-') }#{
699
- @literal && ' '+@literal.inspect }#{
700
- @value_constraint && ' '+@value_constraint.inspect }"
780
+ "{#{
781
+ @quantifier && @quantifier.inspect+' '
782
+ }#{
783
+ @leading_adjective && @leading_adjective.sub(/ |$/,'- ').sub(/ *$/,' ')
784
+ }#{
785
+ @term
786
+ }#{
787
+ @trailing_adjective && ' '+@trailing_adjective.sub(/(.* |^)/, '\1-')
788
+ }#{
789
+ @role_name and @role_name.is_a?(Integer) ? "(#{@role_name})" : " (as #{@role_name})"
790
+ }#{
791
+ @literal && ' '+@literal.inspect
792
+ }#{
793
+ @value_constraint && ' '+@value_constraint.to_s
794
+ }}"
701
795
  end
702
796
 
703
797
  def <=>(other)
@@ -707,11 +801,32 @@ module ActiveFacts
707
801
  ) <=> 0
708
802
  end
709
803
 
804
+ def includes_literals
805
+ @nested_clauses && @nested_clauses.detect{|oj| oj.includes_literals}
806
+ end
807
+
808
+ # We create value types for the results of arithmetic expressions, and they get assigned here:
809
+ def player=(player)
810
+ @player = player
811
+ end
812
+
813
+ def identify_players_with_role_name(context)
814
+ identify_player(context) if role_name
815
+ # Include players in nested clauses, if any
816
+ nested_clauses.each{|clause| clause.identify_players_with_role_name(context)} if nested_clauses
817
+ end
818
+
819
+ def identify_other_players context
820
+ identify_player context
821
+ end
822
+
710
823
  def identify_player context
711
- @player = context.concept @term
712
- raise "Concept #{@term} unrecognised" unless @player
713
- context.player_by_role_name[@role_name] = player if @role_name
714
- @player
824
+ @player || begin
825
+ @player = context.object_type @term
826
+ raise "ObjectType #{@term} unrecognised" unless @player
827
+ context.player_by_role_name[@role_name] = player if @role_name
828
+ @player
829
+ end
715
830
  end
716
831
 
717
832
  def uses_role_name?
@@ -732,6 +847,7 @@ module ActiveFacts
732
847
  end
733
848
 
734
849
  def bind context
850
+ @nested_clauses.each{|c| c.bind context} if @nested_clauses
735
851
  if role_name = @role_name
736
852
  # Omit these tests to see if anything evil eventuates:
737
853
  #if @leading_adjective || @trailing_adjective
@@ -745,19 +861,19 @@ module ActiveFacts
745
861
  role_name = @term
746
862
  end
747
863
  end
748
- @binding = (context.bindings[key] ||= Binding.new(@player, role_name))
749
- @binding.refs << self
750
- @binding
864
+ @variable = (context.variables[key] ||= Variable.new(@player, role_name))
865
+ @variable.refs << self
866
+ @variable
751
867
  end
752
868
 
753
869
  def unbind context
754
870
  # The key has changed.
755
- @binding.refs.delete(self)
756
- if @binding.refs.empty?
757
- # Remove the binding from the context if this was the last reference
758
- context.bindings.delete_if {|k,v| v == @binding }
871
+ @variable.refs.delete(self)
872
+ if @variable.refs.empty?
873
+ # Remove the variable from the context if this was the last reference
874
+ context.variables.delete_if {|k,v| v == @variable }
759
875
  end
760
- @binding = nil
876
+ @variable = nil
761
877
  end
762
878
 
763
879
  def rebind(context)
@@ -765,18 +881,18 @@ module ActiveFacts
765
881
  bind context
766
882
  end
767
883
 
768
- def rebind_to(context, other_role_ref)
769
- debug :binding, "Rebinding #{inspect} to #{other_role_ref.inspect}"
884
+ def rebind_to(context, other_var_ref)
885
+ debug :binding, "Rebinding #{inspect} to #{other_var_ref.inspect}"
770
886
 
771
- old_binding = binding # Remember to move all refs across
887
+ old_variable = variable # Remember to move all refs across
772
888
  unbind(context)
773
889
 
774
- new_binding = other_role_ref.binding
775
- [self, *old_binding.refs].each do |ref|
776
- ref.binding = new_binding
777
- new_binding.refs << ref
890
+ new_variable = other_var_ref.variable
891
+ [self, *old_variable.refs].each do |ref|
892
+ ref.variable = new_variable
893
+ new_variable.refs << ref
778
894
  end
779
- old_binding.rebound_to = new_binding
895
+ old_variable.rebound_to = new_variable
780
896
  end
781
897
 
782
898
  # These are called when we successfully match a fact type reading that has relevant adjectives:
@@ -804,9 +920,9 @@ module ActiveFacts
804
920
  fact_type = @role_ref.role.fact_type
805
921
  constellation = vocabulary.constellation
806
922
 
807
- debug :constraint, "Processing embedded constraint #{@quantifier.inspect} on #{@role_ref.role.concept.name} in #{fact_type.describe}" do
808
- # Preserve the role order of the reading, excluding this role:
809
- constrained_roles = (@reading.role_refs-[self]).map{|rr| rr.role_ref.role}
923
+ debug :constraint, "Processing embedded constraint #{@quantifier.inspect} on #{@role_ref.role.object_type.name} in #{fact_type.describe}" do
924
+ # Preserve the role order of the clause, excluding this role:
925
+ constrained_roles = (@clause.var_refs-[self]).map{|vr| vr.role_ref.role}
810
926
  if constrained_roles.empty?
811
927
  debug :constraint, "Quantifier over unary role has no effect"
812
928
  return
@@ -816,6 +932,8 @@ module ActiveFacts
816
932
  debug :constraint, "Setting max frequency to #{@quantifier.max} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}"
817
933
  raise "Conflicting maximum frequency for constraint" if constraint.max_frequency && constraint.max_frequency != @quantifier.max
818
934
  constraint.max_frequency = @quantifier.max
935
+ raise "Conflicting minimum frequency for constraint" if constraint.min_frequency && constraint.min_frequency != @quantifier.min
936
+ constraint.min_frequency = @quantifier.min
819
937
  else
820
938
  role_sequence = constellation.RoleSequence(:new)
821
939
  constrained_roles.each_with_index do |constrained_role, i|
@@ -837,6 +955,10 @@ module ActiveFacts
837
955
  end
838
956
 
839
957
  end
958
+
959
+ def result(context = nil)
960
+ self
961
+ end
840
962
  end
841
963
 
842
964
  class Quantifier