activefacts 0.8.9 → 0.8.10

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