activefacts 0.8.9 → 0.8.10
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/Manifest.txt +28 -33
- data/Rakefile +11 -12
- data/bin/cql +90 -46
- data/examples/CQL/Blog.cql +2 -1
- data/examples/CQL/CompanyDirectorEmployee.cql +2 -2
- data/examples/CQL/Death.cql +1 -1
- data/examples/CQL/Diplomacy.cql +9 -9
- data/examples/CQL/Genealogy.cql +3 -2
- data/examples/CQL/Insurance.cql +10 -7
- data/examples/CQL/JoinEquality.cql +2 -2
- data/examples/CQL/Marriage.cql +1 -1
- data/examples/CQL/Metamodel.cql +73 -53
- data/examples/CQL/MetamodelNext.cql +89 -67
- data/examples/CQL/OneToOnes.cql +2 -2
- data/examples/CQL/ServiceDirector.cql +10 -5
- data/examples/CQL/Supervision.cql +3 -3
- data/examples/CQL/Tests.Test5.Load.cql +1 -1
- data/examples/CQL/Warehousing.cql +4 -2
- data/lib/activefacts/cql/CQLParser.treetop +26 -60
- data/lib/activefacts/cql/Context.treetop +12 -2
- data/lib/activefacts/cql/Expressions.treetop +14 -30
- data/lib/activefacts/cql/FactTypes.treetop +165 -110
- data/lib/activefacts/cql/Language/English.treetop +167 -54
- data/lib/activefacts/cql/LexicalRules.treetop +16 -2
- data/lib/activefacts/cql/{Concepts.treetop → ObjectTypes.treetop} +36 -37
- data/lib/activefacts/cql/Terms.treetop +57 -27
- data/lib/activefacts/cql/ValueTypes.treetop +39 -13
- data/lib/activefacts/cql/compiler.rb +5 -3
- data/lib/activefacts/cql/compiler/{reading.rb → clause.rb} +407 -285
- data/lib/activefacts/cql/compiler/constraint.rb +178 -275
- data/lib/activefacts/cql/compiler/entity_type.rb +73 -64
- data/lib/activefacts/cql/compiler/expression.rb +418 -0
- data/lib/activefacts/cql/compiler/fact.rb +146 -145
- data/lib/activefacts/cql/compiler/fact_type.rb +197 -80
- data/lib/activefacts/cql/compiler/join.rb +159 -0
- data/lib/activefacts/cql/compiler/shared.rb +51 -23
- data/lib/activefacts/cql/compiler/value_type.rb +56 -2
- data/lib/activefacts/cql/parser.rb +15 -4
- data/lib/activefacts/generate/absorption.rb +7 -7
- data/lib/activefacts/generate/cql.rb +100 -37
- data/lib/activefacts/generate/oo.rb +28 -51
- data/lib/activefacts/generate/ordered.rb +60 -36
- data/lib/activefacts/generate/ruby.rb +6 -6
- data/lib/activefacts/generate/sql/server.rb +4 -4
- data/lib/activefacts/input/orm.rb +71 -53
- data/lib/activefacts/persistence.rb +1 -1
- data/lib/activefacts/persistence/columns.rb +27 -23
- data/lib/activefacts/persistence/foreignkey.rb +6 -6
- data/lib/activefacts/persistence/index.rb +17 -17
- data/lib/activefacts/persistence/{concept.rb → object_type.rb} +9 -9
- data/lib/activefacts/persistence/reference.rb +61 -36
- data/lib/activefacts/persistence/tables.rb +61 -59
- data/lib/activefacts/support.rb +54 -29
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +99 -54
- data/lib/activefacts/vocabulary/metamodel.rb +43 -37
- data/lib/activefacts/vocabulary/verbaliser.rb +134 -109
- data/spec/absorption_spec.rb +8 -8
- data/spec/cql/comparison_spec.rb +91 -0
- data/spec/cql/contractions_spec.rb +251 -0
- data/spec/cql/entity_type_spec.rb +319 -0
- data/spec/cql/expressions_spec.rb +63 -0
- data/spec/cql/fact_type_matching_spec.rb +283 -0
- data/spec/cql/french_spec.rb +21 -0
- data/spec/cql/parser/bad_literals_spec.rb +86 -0
- data/spec/cql/parser/constraints_spec.rb +19 -0
- data/spec/cql/parser/entity_types_spec.rb +106 -0
- data/spec/cql/parser/expressions_spec.rb +179 -0
- data/spec/cql/parser/fact_types_spec.rb +41 -0
- data/spec/cql/parser/literals_spec.rb +312 -0
- data/spec/cql/parser/pragmas_spec.rb +89 -0
- data/spec/cql/parser/value_types_spec.rb +42 -0
- data/spec/cql/role_matching_spec.rb +147 -0
- data/spec/cql/samples_spec.rb +9 -9
- data/spec/cql_cql_spec.rb +1 -1
- data/spec/cql_dm_spec.rb +116 -0
- data/spec/cql_mysql_spec.rb +1 -1
- data/spec/cql_ruby_spec.rb +1 -1
- data/spec/cql_sql_spec.rb +3 -3
- data/spec/cql_symbol_tables_spec.rb +30 -30
- data/spec/cqldump_spec.rb +4 -4
- data/spec/helpers/array_matcher.rb +32 -27
- data/spec/helpers/diff_matcher.rb +6 -26
- data/spec/helpers/file_matcher.rb +41 -32
- data/spec/helpers/parse_to_ast_matcher.rb +76 -0
- data/spec/helpers/string_matcher.rb +32 -31
- data/spec/norma_cql_spec.rb +1 -1
- data/spec/norma_ruby_spec.rb +1 -1
- data/spec/norma_ruby_sql_spec.rb +1 -1
- data/spec/norma_sql_spec.rb +3 -1
- data/spec/norma_tables_spec.rb +1 -1
- data/spec/ruby_api_spec.rb +23 -0
- data/spec/spec_helper.rb +5 -4
- metadata +66 -66
- data/examples/CQL/OrienteeringER.cql +0 -58
- data/lib/activefacts/api.rb +0 -44
- data/lib/activefacts/api/concept.rb +0 -410
- data/lib/activefacts/api/constellation.rb +0 -128
- data/lib/activefacts/api/entity.rb +0 -256
- data/lib/activefacts/api/instance.rb +0 -60
- data/lib/activefacts/api/instance_index.rb +0 -80
- data/lib/activefacts/api/numeric.rb +0 -167
- data/lib/activefacts/api/role.rb +0 -80
- data/lib/activefacts/api/role_proxy.rb +0 -70
- data/lib/activefacts/api/role_values.rb +0 -117
- data/lib/activefacts/api/standard_types.rb +0 -87
- data/lib/activefacts/api/support.rb +0 -65
- data/lib/activefacts/api/value.rb +0 -135
- data/lib/activefacts/api/vocabulary.rb +0 -82
- data/spec/api/autocounter.rb +0 -82
- data/spec/api/constellation.rb +0 -130
- data/spec/api/entity_type.rb +0 -103
- data/spec/api/instance.rb +0 -461
- data/spec/api/roles.rb +0 -124
- data/spec/api/value_type.rb +0 -112
- data/spec/api_spec.rb +0 -13
- data/spec/cql/matching_spec.rb +0 -517
- data/spec/cql/unit_spec.rb +0 -394
- 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
|
25
|
-
/
|
26
|
-
/
|
27
|
-
/ 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[
|
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
|
41
|
-
&{|e| input.context.object_type(e[0].value, "entity type")
|
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[
|
46
|
-
true
|
49
|
+
input.context.object_type(e[3].value, "value type")
|
47
50
|
}
|
48
51
|
/
|
49
|
-
term_definition_name s is s
|
50
|
-
&{|e| input.context.object_type(e[0].value, "objectified_fact_type")
|
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
|
-
|
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,
|
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::
|
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,
|
130
|
-
ast = term.ast(quantifier, function_call, role_name, value_constraint, literal,
|
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
|
152
|
-
# These words are illegal in (but maybe ok following) a
|
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
|
-
/
|
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
|
-
/
|
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/
|
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 =
|
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
|
-
|
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
|
-
|
51
|
-
singular:
|
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:
|
54
|
-
|
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?
|
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 /
|
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
|
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/
|
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.
|
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
|
5
|
+
class Clause
|
6
6
|
attr_reader :phrases
|
7
7
|
attr_accessor :qualifiers, :context_note
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
13
|
+
attr_accessor :objectified_as # The VarRef which objectified this fact type
|
13
14
|
|
14
|
-
def initialize
|
15
|
-
@phrases =
|
16
|
-
|
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
|
22
|
-
@phrases.select{|r| r.
|
22
|
+
def var_refs
|
23
|
+
@phrases.select{|r| r.respond_to?(:player)}
|
23
24
|
end
|
24
25
|
|
25
|
-
# A
|
26
|
-
# refers only to the existence of that
|
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?(
|
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
|
-
|
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 && ' '
|
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
|
-
|
49
|
-
|
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
|
-
|
57
|
-
|
58
|
-
# Include players in
|
59
|
-
|
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
|
-
|
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
|
68
|
-
|
84
|
+
def is_equality_comparison
|
85
|
+
false
|
86
|
+
end
|
69
87
|
|
70
|
-
|
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
|
94
|
+
raise "Duplicate role #{rn.is_a?(Integer) ? "subscript" : "name"} '#{rn}' in clause"
|
74
95
|
end
|
75
96
|
|
76
|
-
|
77
|
-
|
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?(
|
86
|
-
if mine.is_a?(
|
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
|
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
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
200
|
+
true
|
201
|
+
end.
|
202
|
+
map{ |role| role.fact_type}
|
203
|
+
end.flatten.uniq
|
166
204
|
|
167
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
-
|
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
|
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
|
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 @
|
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
|
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
|
-
|
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
|
249
|
-
debug :matching_fails, "Mismatched ordinary word #{
|
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 =
|
317
|
+
while (phrase = phrases[phrase_num])
|
262
318
|
phrase_num += 1
|
263
|
-
if phrase.
|
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
|
332
|
+
if next_player != player
|
278
333
|
# This relies on the supertypes being in breadth-first order:
|
279
|
-
common_supertype = (
|
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 #{
|
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 =
|
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
|
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
|
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.
|
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
|
-
|
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 !=
|
357
|
-
debug :matching_fails, "Extra words #{(intervening_words +
|
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{|
|
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.
|
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 #{
|
382
|
-
|
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
|
-
|
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 |
|
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
|
397
|
-
|
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 =
|
405
|
-
debug :matching, "Deleting matched trailing adjective '#{rra}'#{
|
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 =
|
474
|
+
if a = phrase.trailing_adjective
|
409
475
|
if a.size >= rra.size
|
410
|
-
a
|
411
|
-
|
476
|
+
a = a[rra.size+1..-1]
|
477
|
+
phrase.trailing_adjective = a == '' ? nil : a
|
412
478
|
changed = true
|
413
479
|
end
|
414
|
-
elsif
|
415
|
-
|
416
|
-
# This phrase is absorbing non-hyphenated adjective(s), which changes its
|
417
|
-
|
418
|
-
|
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 =
|
424
|
-
debug :matching, "Deleting matched leading adjective '#{rra}'#{
|
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 =
|
493
|
+
if a = phrase.leading_adjective
|
428
494
|
if a.size >= rra.size
|
429
|
-
a
|
430
|
-
a
|
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
|
435
|
-
|
436
|
-
# This phrase is absorbing non-hyphenated adjective(s), which changes its
|
437
|
-
|
438
|
-
|
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
|
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.
|
454
|
-
phrase.role = vocabulary.constellation.Role(fact_type, fact_type.all_role.size, :
|
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.
|
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
|
539
|
+
# Otherwise we have to find the existing role via the Variable. This is pretty ugly.
|
472
540
|
unless phrase.role
|
473
|
-
# Find another
|
474
|
-
ref = phrase.
|
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.
|
504
|
-
role_sequence.all_role_ref_in_order.map{|rr| rr.role.
|
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
|
-
|
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.
|
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
|
-
|
528
|
-
|
529
|
-
debug :matching, "phrase in matched
|
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, "
|
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.
|
572
|
-
|
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
|
-
|
580
|
-
next unless
|
581
|
-
# puts "Quantifier #{
|
582
|
-
|
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
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
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
|
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
|
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
|
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
|
712
|
+
attr_reader :role_side_effects # One array of values per VarRef matched, in order
|
623
713
|
|
624
|
-
def initialize fact_type,
|
714
|
+
def initialize fact_type, clause, residual_adjectives, role_side_effects
|
625
715
|
@fact_type = fact_type
|
626
|
-
@
|
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 |
|
638
|
-
c +=
|
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 |
|
646
|
-
( [
|
647
|
-
[
|
648
|
-
[
|
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
|
656
|
-
attr_reader :term, :
|
657
|
-
|
658
|
-
attr_accessor :
|
659
|
-
attr_accessor :
|
660
|
-
attr_accessor :role #
|
661
|
-
attr_accessor :role_ref #
|
662
|
-
attr_accessor :
|
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,
|
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
|
-
@
|
772
|
+
@nested_clauses = nested_clauses
|
677
773
|
end
|
678
774
|
|
679
775
|
def inspect
|
680
|
-
|
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
|
-
|
697
|
-
@
|
698
|
-
|
699
|
-
@
|
700
|
-
|
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
|
712
|
-
|
713
|
-
|
714
|
-
|
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
|
-
@
|
749
|
-
@
|
750
|
-
@
|
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
|
-
@
|
756
|
-
if @
|
757
|
-
# Remove the
|
758
|
-
context.
|
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
|
-
@
|
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,
|
769
|
-
debug :binding, "Rebinding #{inspect} to #{
|
884
|
+
def rebind_to(context, other_var_ref)
|
885
|
+
debug :binding, "Rebinding #{inspect} to #{other_var_ref.inspect}"
|
770
886
|
|
771
|
-
|
887
|
+
old_variable = variable # Remember to move all refs across
|
772
888
|
unbind(context)
|
773
889
|
|
774
|
-
|
775
|
-
[self, *
|
776
|
-
ref.
|
777
|
-
|
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
|
-
|
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.
|
808
|
-
# Preserve the role order of the
|
809
|
-
constrained_roles = (@
|
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
|