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.
- 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
|