activefacts 0.8.6 → 0.8.8
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +33 -2
- data/README.rdoc +30 -36
- data/Rakefile +16 -20
- data/bin/afgen +17 -11
- data/bin/cql +313 -36
- data/download.html +43 -19
- data/examples/CQL/Address.cql +15 -15
- data/examples/CQL/Blog.cql +8 -8
- data/examples/CQL/CompanyDirectorEmployee.cql +6 -5
- data/examples/CQL/Death.cql +3 -3
- data/examples/CQL/Diplomacy.cql +48 -0
- data/examples/CQL/Genealogy.cql +41 -41
- data/examples/CQL/Insurance.cql +311 -0
- data/examples/CQL/JoinEquality.cql +35 -0
- data/examples/CQL/Marriage.cql +1 -1
- data/examples/CQL/Metamodel.cql +290 -185
- data/examples/CQL/MetamodelNext.cql +420 -0
- data/examples/CQL/Monogamy.cql +24 -0
- data/examples/CQL/MonthInSeason.cql +27 -0
- data/examples/CQL/Moon.cql +23 -0
- data/examples/CQL/MultiInheritance.cql +4 -4
- data/examples/CQL/NonRoleId.cql +14 -0
- data/examples/CQL/OddIdentifier.cql +18 -0
- data/examples/CQL/OilSupply.cql +24 -24
- data/examples/CQL/OneToOnes.cql +17 -0
- data/examples/CQL/Orienteering.cql +55 -55
- data/examples/CQL/OrienteeringER.cql +58 -0
- data/examples/CQL/PersonPlaysGame.cql +2 -2
- data/examples/CQL/RedundantDependency.cql +34 -0
- data/examples/CQL/SchoolActivities.cql +5 -5
- data/examples/CQL/SeparateSubtype.cql +28 -0
- data/examples/CQL/ServiceDirector.cql +283 -0
- data/examples/CQL/SimplestUnary.cql +2 -2
- data/examples/CQL/SubtypePI.cql +11 -11
- data/examples/CQL/Supervision.cql +38 -0
- data/examples/CQL/Tests.Test5.Load.cql +38 -0
- data/examples/CQL/WaiterTips.cql +33 -0
- data/examples/CQL/Warehousing.cql +55 -53
- data/examples/CQL/WindowInRoomInBldg.cql +9 -9
- data/examples/CQL/unit.cql +433 -544
- data/examples/index.html +314 -170
- data/examples/intro.html +6 -176
- data/examples/local.css +8 -4
- data/index.html +40 -25
- data/lib/activefacts/api/concept.rb +2 -2
- data/lib/activefacts/api/constellation.rb +4 -4
- data/lib/activefacts/api/instance.rb +2 -2
- data/lib/activefacts/api/instance_index.rb +4 -0
- data/lib/activefacts/api/numeric.rb +3 -1
- data/lib/activefacts/api/role.rb +1 -1
- data/lib/activefacts/api/standard_types.rb +23 -16
- data/lib/activefacts/api/support.rb +3 -1
- data/lib/activefacts/api/vocabulary.rb +4 -0
- data/lib/activefacts/cql/CQLParser.treetop +87 -39
- data/lib/activefacts/cql/Concepts.treetop +95 -69
- data/lib/activefacts/cql/Context.treetop +11 -2
- data/lib/activefacts/cql/Expressions.treetop +23 -59
- data/lib/activefacts/cql/FactTypes.treetop +141 -95
- data/lib/activefacts/cql/Language/English.treetop +33 -21
- data/lib/activefacts/cql/LexicalRules.treetop +6 -1
- data/lib/activefacts/cql/Terms.treetop +75 -26
- data/lib/activefacts/cql/ValueTypes.treetop +52 -54
- data/lib/activefacts/cql/compiler.rb +46 -1691
- data/lib/activefacts/cql/compiler/constraint.rb +602 -0
- data/lib/activefacts/cql/compiler/entity_type.rb +425 -0
- data/lib/activefacts/cql/compiler/fact.rb +300 -0
- data/lib/activefacts/cql/compiler/fact_type.rb +230 -0
- data/lib/activefacts/cql/compiler/reading.rb +832 -0
- data/lib/activefacts/cql/compiler/shared.rb +109 -0
- data/lib/activefacts/cql/compiler/value_type.rb +104 -0
- data/lib/activefacts/cql/parser.rb +132 -81
- data/lib/activefacts/generate/cql.rb +397 -274
- data/lib/activefacts/generate/oo.rb +13 -12
- data/lib/activefacts/generate/ordered.rb +107 -117
- data/lib/activefacts/generate/ruby.rb +34 -38
- data/lib/activefacts/generate/sql/mysql.rb +62 -45
- data/lib/activefacts/generate/sql/server.rb +59 -42
- data/lib/activefacts/input/cql.rb +6 -3
- data/lib/activefacts/input/orm.rb +991 -557
- data/lib/activefacts/persistence/columns.rb +16 -12
- data/lib/activefacts/persistence/foreignkey.rb +7 -4
- data/lib/activefacts/persistence/index.rb +3 -4
- data/lib/activefacts/persistence/reference.rb +5 -2
- data/lib/activefacts/support.rb +20 -14
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary.rb +1 -0
- data/lib/activefacts/vocabulary/extensions.rb +328 -44
- data/lib/activefacts/vocabulary/metamodel.rb +145 -20
- data/lib/activefacts/vocabulary/verbaliser.rb +621 -0
- data/spec/absorption_spec.rb +4 -4
- data/spec/api/value_type.rb +1 -1
- data/spec/cql/context_spec.rb +45 -22
- data/spec/cql/deontic_spec.rb +88 -0
- data/spec/cql/matching_spec.rb +517 -0
- data/spec/cql/samples_spec.rb +88 -31
- data/spec/cql/unit_spec.rb +58 -37
- data/spec/cql_cql_spec.rb +12 -7
- data/spec/cql_mysql_spec.rb +3 -7
- data/spec/cql_parse_spec.rb +0 -4
- data/spec/cql_ruby_spec.rb +1 -4
- data/spec/cql_sql_spec.rb +5 -18
- data/spec/cql_symbol_tables_spec.rb +3 -0
- data/spec/cqldump_spec.rb +0 -2
- data/spec/helpers/array_matcher.rb +35 -0
- data/spec/helpers/ctrl_c_support.rb +52 -0
- data/spec/helpers/diff_matcher.rb +38 -0
- data/spec/helpers/file_matcher.rb +5 -3
- data/spec/helpers/string_matcher.rb +39 -0
- data/spec/helpers/test_parser.rb +13 -0
- data/spec/norma_cql_spec.rb +13 -5
- data/spec/norma_ruby_spec.rb +11 -3
- data/spec/{absorption_ruby_spec.rb → norma_ruby_sql_spec.rb} +37 -32
- data/spec/norma_sql_spec.rb +11 -5
- data/spec/norma_tables_spec.rb +33 -29
- data/spec/spec_helper.rb +4 -1
- data/status.html +92 -23
- metadata +102 -36
- data/lib/activefacts/generate/cql/html.rb +0 -403
@@ -54,6 +54,11 @@ module ActiveFacts
|
|
54
54
|
}
|
55
55
|
end
|
56
56
|
|
57
|
+
rule url
|
58
|
+
# url_scheme ':' (user ( ':' !(port '/') password )? '@' )? hostname ( ':' port )? '/' path query? fragment?
|
59
|
+
( !(white / ';') .)+
|
60
|
+
end
|
61
|
+
|
57
62
|
rule literal
|
58
63
|
( boolean_literal
|
59
64
|
/ string
|
@@ -164,7 +169,7 @@ module ActiveFacts
|
|
164
169
|
end
|
165
170
|
|
166
171
|
rule comment_to_eol
|
167
|
-
'//' (!"\n" .)
|
172
|
+
'//' (!"\n" .)*
|
168
173
|
end
|
169
174
|
|
170
175
|
rule comment_c_style
|
@@ -10,7 +10,8 @@ module ActiveFacts
|
|
10
10
|
rule term_definition_name
|
11
11
|
id s
|
12
12
|
t:(!non_term_def id s)*
|
13
|
-
{
|
13
|
+
{
|
14
|
+
def value
|
14
15
|
t.elements.inject([
|
15
16
|
id.value
|
16
17
|
]){|a, e| a << e.id.value}*' '
|
@@ -23,42 +24,50 @@ module ActiveFacts
|
|
23
24
|
/ written_as # Value type
|
24
25
|
/ is s mapping_pragmas where # Objectified type
|
25
26
|
/ non_role_word
|
27
|
+
/ identified_by # as in: "a kind of X identified by..."
|
28
|
+
/ unit
|
26
29
|
end
|
27
30
|
|
28
31
|
rule entity_prefix
|
29
|
-
is s identified_by
|
32
|
+
is s identified_by
|
33
|
+
/
|
34
|
+
subtype_prefix term_definition_name
|
35
|
+
&{|e| input.context.object_type(e[1].value, "subtype"); true }
|
30
36
|
end
|
31
37
|
|
32
38
|
rule prescan
|
33
39
|
s (
|
34
40
|
term_definition_name s entity_prefix
|
35
|
-
|
41
|
+
&{|e| input.context.object_type(e[0].value, "entity type"); true }
|
36
42
|
/
|
37
|
-
term_definition_name written_as
|
38
|
-
&{|e| input.context.
|
43
|
+
t1:term_definition_name written_as t2:term_definition_name
|
44
|
+
&{|e| input.context.object_type(e[0].value, "value type")
|
45
|
+
input.context.object_type(e[2].value, "value type")
|
46
|
+
true
|
47
|
+
}
|
39
48
|
/
|
40
49
|
term_definition_name s is s mapping_pragmas where
|
41
|
-
&{|e| input.context.
|
50
|
+
&{|e| input.context.object_type(e[0].value, "objectified_fact_type"); true }
|
42
51
|
)?
|
43
52
|
prescan_rest
|
44
53
|
&{|s|
|
54
|
+
# Wipe any terminal failures that were added:
|
45
55
|
@terminal_failures = []
|
46
56
|
@max_terminal_failure_index = start_index
|
47
57
|
|
48
58
|
# puts "========== prescan is complete on #{(s.map{|e|e.text_value}*" ").inspect} =========="
|
49
|
-
false
|
59
|
+
false
|
50
60
|
}
|
51
61
|
end
|
52
62
|
|
53
|
-
# Do a first-pass mainly lexical analysis, looking
|
54
|
-
#
|
55
|
-
# REVISIT: and adjectives
|
63
|
+
# Do a first-pass mainly lexical analysis, looking for role name definitions and adjectives,
|
64
|
+
# for use in detecting terms later.
|
56
65
|
rule prescan_rest
|
57
66
|
&{ input.context.reset_role_names }
|
58
67
|
(
|
59
|
-
|
60
|
-
/
|
61
|
-
&{|s| input.context.role_name(s[
|
68
|
+
context_note # Context notes have different lexical conventions
|
69
|
+
/ '(' as S term_definition_name s ')' s # Prescan for a Role Name
|
70
|
+
&{|s| input.context.role_name(s[3].value) }
|
62
71
|
# Adjective definitions
|
63
72
|
/ new_adjective_term
|
64
73
|
/ global_term # If we see A B - C D, don't recognise B as a new adjective for C D.
|
@@ -71,29 +80,68 @@ module ActiveFacts
|
|
71
80
|
end
|
72
81
|
|
73
82
|
rule new_adjective_term
|
74
|
-
!global_term adj:id '-' s global_term
|
75
|
-
&{|s| input.context.new_leading_adjective_term(s[1].value, s[
|
83
|
+
!global_term adj:id '-' lead_intervening s global_term # Definitely a new leading adjective for this term
|
84
|
+
&{|s| input.context.new_leading_adjective_term([s[1].text_value, s[3].value].compact*" ", s[5].text_value) }
|
76
85
|
/
|
77
|
-
global_term s '-' !global_term adj:id
|
78
|
-
&{|s| input.context.new_trailing_adjective_term(s[
|
86
|
+
global_term s trail_intervening '-' !global_term adj:id # Definitely a new trailing adjective for this term
|
87
|
+
&{|s| input.context.new_trailing_adjective_term([s[2].value, s[5].text_value].compact*" ", s[0].text_value) }
|
88
|
+
end
|
89
|
+
|
90
|
+
rule lead_intervening # Words intervening between a new adjective and the term
|
91
|
+
(S !global_term id)*
|
92
|
+
{
|
93
|
+
def value
|
94
|
+
elements.size == 0 ? nil : elements.map{|e| e.id.text_value}*" "
|
95
|
+
end
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
rule trail_intervening # Words intervening between a new adjective and the term
|
100
|
+
(!global_term id S)*
|
101
|
+
{
|
102
|
+
def value
|
103
|
+
elements.size == 0 ? nil : elements.map{|e| e.id.text_value}*" "
|
104
|
+
end
|
105
|
+
}
|
79
106
|
end
|
80
107
|
|
81
108
|
# This is the rule to use after the prescan; it only succeeds on a complete term or role reference
|
82
109
|
rule term
|
83
|
-
s head:id &{|s| w = s[1].text_value; input.context.term_starts(w) }
|
84
|
-
tail:(s '-'? s w:id
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
110
|
+
s head:id x &{|s| w = s[1].text_value; input.context.term_starts?(w, s[2]) }
|
111
|
+
tail:(s '-'? s w:id &{|s| w = s[3].text_value; input.context.term_continues?(w) })*
|
112
|
+
&{|s| input.context.term_complete? }
|
113
|
+
{
|
114
|
+
def ast quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, objectification_join = nil
|
115
|
+
t = x.context[:term]
|
116
|
+
gt = x.context[:global_term]
|
117
|
+
leading_adjective = t[0...-gt.size-1] if t.size > gt.size and t[-gt.size..-1] == gt
|
118
|
+
trailing_adjective = t[gt.size+1..-1] if t.size > gt.size and t[0...gt.size] == gt
|
119
|
+
Compiler::RoleRef.new(gt, leading_adjective, trailing_adjective, quantifier, function_call, role_name, value_constraint, literal, objectification_join)
|
120
|
+
end
|
121
|
+
|
122
|
+
def value # Sometimes we just want the full term name
|
123
|
+
x.context[:term]
|
89
124
|
end
|
90
125
|
}
|
126
|
+
/
|
127
|
+
s head:id '-' s term &{|s| s[4].ast.leading_adjective == nil }
|
128
|
+
{
|
129
|
+
def ast quantifier = nil, function_call = nil, role_name = nil, value_constraint = nil, literal = nil, objectification_join = nil
|
130
|
+
ast = term.ast(quantifier, function_call, role_name, value_constraint, literal, objectification_join)
|
131
|
+
ast.leading_adjective = head.text_value
|
132
|
+
ast
|
133
|
+
end
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
rule x
|
138
|
+
'' <SavedContext>
|
91
139
|
end
|
92
140
|
|
93
141
|
rule global_term
|
94
142
|
# This rule shouldn't be used outside the prescan, it will memoize the wrong things.
|
95
|
-
head:id &{|s| input.context.term_starts(s[0].text_value) }
|
96
|
-
tail:(s w:id &{|s| input.context.term_continues(s[1].text_value) } )*
|
143
|
+
head:id x &{|s| input.context.term_starts?(s[0].text_value, s[1]) }
|
144
|
+
tail:(s w:id &{|s| input.context.term_continues?(s[1].text_value) } )*
|
97
145
|
{ def value
|
98
146
|
tail.elements.inject(head.value) { |t, e| "#{t} #{e.w.value}" }
|
99
147
|
end
|
@@ -108,8 +156,9 @@ module ActiveFacts
|
|
108
156
|
/ only
|
109
157
|
/ or
|
110
158
|
/ quantifier
|
111
|
-
/
|
159
|
+
/ value_constraint
|
112
160
|
/ but
|
161
|
+
/ 'occurs' s quantifier s 'time'
|
113
162
|
end
|
114
163
|
|
115
164
|
end
|
@@ -9,32 +9,22 @@ module ActiveFacts
|
|
9
9
|
grammar ValueTypes
|
10
10
|
rule value_type
|
11
11
|
s term_definition_name
|
12
|
+
# REVISIT: ORM2 would allow (subtype_prefix term)?
|
12
13
|
written_as
|
13
|
-
|
14
|
-
base:id s
|
14
|
+
base:(term/id) s
|
15
15
|
value_type_parameters
|
16
|
-
units
|
17
|
-
r:(
|
16
|
+
u:units?
|
17
|
+
r:(value_constraint enforcement)?
|
18
18
|
mapping_pragmas
|
19
19
|
s ';' s
|
20
20
|
{
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
mapping_pragmas.value,
|
29
|
-
(!r.empty? ? r.enforcement.value : nil)
|
30
|
-
]
|
31
|
-
end
|
32
|
-
|
33
|
-
def value
|
34
|
-
[ term_definition_name.value,
|
35
|
-
defined_type
|
36
|
-
]
|
37
|
-
end
|
21
|
+
def ast
|
22
|
+
name = term_definition_name.value
|
23
|
+
params = value_type_parameters.values
|
24
|
+
value_constraint = r.empty? ? nil : Compiler::ValueConstraint.new(r.value_constraint.ranges, r.enforcement.ast)
|
25
|
+
units = u.empty? ? [] : u.value
|
26
|
+
Compiler::ValueType.new name, base.value, params, units, value_constraint, mapping_pragmas.value
|
27
|
+
end
|
38
28
|
}
|
39
29
|
end
|
40
30
|
|
@@ -49,44 +39,50 @@ module ActiveFacts
|
|
49
39
|
head:number s tail:( ',' s number s )*
|
50
40
|
{
|
51
41
|
def values
|
52
|
-
[head.value
|
42
|
+
[head.value, *tail.elements.map{|i| i.number.value}]
|
53
43
|
end
|
54
44
|
}
|
55
45
|
end
|
56
46
|
|
57
47
|
rule unit_definition
|
58
48
|
u:(
|
59
|
-
unit_coefficient units s o:unit_offset?
|
49
|
+
coeff:unit_coefficient? base:units? s o:unit_offset?
|
60
50
|
converts s a:(approximately s)? to s
|
61
51
|
singular:id s plural:('/' s p:id s)?
|
62
52
|
/
|
63
53
|
singular:id s plural:('/' s p:id s)?
|
64
54
|
converts s a:(approximately s)? to s
|
65
|
-
unit_coefficient units s o:unit_offset?
|
55
|
+
coeff:unit_coefficient? base:units? s o:unit_offset?
|
66
56
|
)
|
67
|
-
q:(approximately /
|
68
|
-
';'
|
69
|
-
{
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
57
|
+
q:(approximately '' / ephemera s url )? s
|
58
|
+
';'
|
59
|
+
{
|
60
|
+
def ast
|
61
|
+
singular = u.singular.text_value
|
62
|
+
plural = u.plural.text_value.empty? ? nil : u.plural.p.text_value
|
63
|
+
if u.coeff.empty?
|
64
|
+
raise "Unit definition requires either a coefficient or an ephemera URL" unless q.respond_to?(:ephemera)
|
65
|
+
numerator,denominator = 1, 1
|
66
|
+
else
|
67
|
+
numerator, denominator = *u.coeff.ast
|
68
|
+
end
|
69
|
+
offset = u.o.text_value.empty? ? 0 : u.o.value
|
70
|
+
bases = u.base.empty? ? [] : u.base.value
|
71
|
+
approximately = q.respond_to? :approximately
|
72
|
+
ephemera = q.respond_to?(:ephemera) ? q.url.text_value : nil
|
73
|
+
Compiler::Unit.new singular, plural, numerator, denominator, offset, bases, approximately, ephemera
|
74
|
+
end
|
80
75
|
}
|
81
76
|
end
|
82
77
|
|
83
78
|
rule unit_coefficient
|
84
79
|
numerator:number denominator:(s '/' s number)? s
|
85
|
-
{
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
80
|
+
{
|
81
|
+
def ast
|
82
|
+
[ numerator.text_value,
|
83
|
+
(denominator.text_value.empty? ? "1" : denominator.number.text_value)
|
84
|
+
]
|
85
|
+
end
|
90
86
|
}
|
91
87
|
end
|
92
88
|
|
@@ -98,24 +94,25 @@ module ActiveFacts
|
|
98
94
|
}
|
99
95
|
end
|
100
96
|
|
97
|
+
# In a unit definition, we may use undefined base units; this is the only way to get fundamental units
|
101
98
|
rule units
|
102
|
-
|
99
|
+
!non_unit maybe_unit s tail:(!non_unit maybe_unit s)* div:('/' s maybe_unit s tail:(!non_unit maybe_unit s)*)?
|
103
100
|
{ def value
|
104
|
-
|
105
|
-
|
106
|
-
else
|
107
|
-
u.tail.elements.inject([u.unit.value]) { |a, e| a << e.unit.value } +
|
108
|
-
(u.div.text_value.empty? ? [] : u.div.tail.elements.inject([u.div.unit.inverse]) { |a, e| a << e.unit.inverse })
|
109
|
-
end
|
101
|
+
tail.elements.inject([maybe_unit.value]) { |a, e| a << e.maybe_unit.value } +
|
102
|
+
(div.text_value.empty? ? [] : div.tail.elements.inject([div.maybe_unit.inverse]) { |a, e| a << e.maybe_unit.inverse })
|
110
103
|
end
|
111
104
|
}
|
112
105
|
end
|
113
106
|
|
114
107
|
rule non_unit
|
115
|
-
restricted / converts / approximately /
|
108
|
+
restricted / converts / approximately / ephemera
|
116
109
|
end
|
117
110
|
|
118
111
|
rule unit
|
112
|
+
maybe_unit &{|s| input.context.unit?(s[0].unit_name.text_value) }
|
113
|
+
end
|
114
|
+
|
115
|
+
rule maybe_unit
|
119
116
|
unit_name:id pow:('^' '-'? [0-9])?
|
120
117
|
{ def value
|
121
118
|
[unit_name.text_value, pow.text_value.empty? ? 1 : Integer(pow.text_value[1..-1])]
|
@@ -128,11 +125,12 @@ module ActiveFacts
|
|
128
125
|
}
|
129
126
|
end
|
130
127
|
|
131
|
-
rule
|
132
|
-
|
128
|
+
rule value_constraint
|
129
|
+
restricted s to s range_list s u:units?
|
133
130
|
{
|
134
131
|
def ranges
|
135
|
-
|
132
|
+
raise "units on value constraints are not yet processed" unless u.empty? # REVISIT: Use the units here
|
133
|
+
range_list.ranges
|
136
134
|
end
|
137
135
|
}
|
138
136
|
end
|
@@ -143,7 +141,7 @@ module ActiveFacts
|
|
143
141
|
'}' s
|
144
142
|
{
|
145
143
|
def ranges
|
146
|
-
[head.value
|
144
|
+
[head.value, *tail.elements.map{|e| e.range.value }]
|
147
145
|
end
|
148
146
|
}
|
149
147
|
end
|
@@ -5,1719 +5,74 @@
|
|
5
5
|
require 'activefacts/vocabulary'
|
6
6
|
require 'activefacts/cql/parser'
|
7
7
|
|
8
|
+
require 'activefacts/cql/compiler/shared'
|
9
|
+
require 'activefacts/cql/compiler/value_type'
|
10
|
+
require 'activefacts/cql/compiler/entity_type'
|
11
|
+
require 'activefacts/cql/compiler/reading'
|
12
|
+
require 'activefacts/cql/compiler/fact_type'
|
13
|
+
require 'activefacts/cql/compiler/fact'
|
14
|
+
require 'activefacts/cql/compiler/constraint'
|
15
|
+
|
8
16
|
module ActiveFacts
|
9
17
|
module CQL
|
10
18
|
class Compiler < ActiveFacts::CQL::Parser
|
11
19
|
attr_reader :vocabulary
|
12
20
|
|
13
|
-
|
14
|
-
|
15
|
-
RingTypes = %w{acyclic intransitive symmetric asymmetric transitive antisymmetric irreflexive reflexive}
|
16
|
-
RingPairs = {
|
17
|
-
:intransitive => [:acyclic, :asymmetric, :symmetric],
|
18
|
-
:irreflexive => [:symmetric]
|
19
|
-
}
|
20
|
-
|
21
|
-
def initialize(string, filename = "stdin")
|
22
|
-
@string = string # The contents of the input string
|
21
|
+
def initialize(filename = "stdin")
|
23
22
|
@filename = filename
|
24
|
-
|
25
23
|
@constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
|
24
|
+
debug :file, "Parsing '#{filename}'"
|
25
|
+
end
|
26
26
|
|
27
|
+
def compile_file filename
|
28
|
+
old_filename = @filename
|
29
|
+
@filename = filename
|
30
|
+
File.open(filename) do |f|
|
31
|
+
compile(f.read)
|
32
|
+
end
|
33
|
+
@filename = old_filename
|
34
|
+
end
|
35
|
+
|
36
|
+
def compile input
|
37
|
+
@string = input
|
27
38
|
# The syntax tree created from each parsed CQL statement gets passed to the block.
|
28
39
|
# parse_all returns an array of the block's non-nil return values.
|
29
|
-
|
40
|
+
ok = parse_all(@string, :definition) do |node|
|
41
|
+
debug :parse, "Parsed '#{node.text_value.gsub(/\s+/,' ').strip}'" do
|
30
42
|
begin
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
when :entity_type
|
41
|
-
entity_type *value
|
42
|
-
when :fact_type
|
43
|
-
fact_type *value
|
44
|
-
when :constraint
|
45
|
-
constraint *value
|
46
|
-
when :fact
|
47
|
-
fact *value
|
48
|
-
when :unit
|
49
|
-
unit *value
|
50
|
-
else
|
51
|
-
print "="*20+" unhandled declaration type: "; p kind, value
|
52
|
-
end
|
43
|
+
ast = node.ast
|
44
|
+
next unless ast
|
45
|
+
debug :ast, ast.inspect
|
46
|
+
ast.source = node.body
|
47
|
+
ast.constellation = @constellation
|
48
|
+
ast.vocabulary = @vocabulary
|
49
|
+
value = compile_definition ast
|
50
|
+
debug :definition, "Compiled to #{value.is_a?(Array) ? value.map{|v| v.verbalise}*', ' : value.verbalise}" if value
|
51
|
+
@vocabulary = value if ast.is_a?(Compiler::Vocabulary)
|
53
52
|
rescue => e
|
54
|
-
|
53
|
+
# Augment the exception message, but preserve the backtrace
|
55
54
|
start_line = @string.line_of(node.interval.first)
|
56
55
|
end_line = @string.line_of(node.interval.last-1)
|
57
56
|
lines = start_line != end_line ? "s #{start_line}-#{end_line}" : " #{start_line.to_s}"
|
58
|
-
|
57
|
+
ne = StandardError.new("at line#{lines} #{e.message.strip}")
|
58
|
+
ne.set_backtrace(e.backtrace)
|
59
|
+
raise ne
|
59
60
|
end
|
60
|
-
|
61
|
-
nil
|
62
|
-
end
|
63
|
-
raise failure_reason unless result
|
64
|
-
@vocabulary
|
65
|
-
end
|
66
|
-
|
67
|
-
def context
|
68
|
-
@context ||= Context.new(self)
|
69
|
-
end
|
70
|
-
|
71
|
-
private
|
72
|
-
def value_type(name, base_type_name, parameters, unit, ranges, mapping_pragmas, enforcement)
|
73
|
-
length, scale = *parameters
|
74
|
-
|
75
|
-
# Create the base type:
|
76
|
-
base_type = nil
|
77
|
-
if (base_type_name != name)
|
78
|
-
unless base_type = @constellation.ValueType[[@vocabulary, @constellation.Name(base_type_name)]]
|
79
|
-
#puts "REVISIT: Creating base ValueType #{base_type_name} in #{@vocabulary.inspect}"
|
80
|
-
base_type = @constellation.ValueType(@vocabulary, base_type_name)
|
81
|
-
return if base_type_name == name
|
82
61
|
end
|
83
62
|
end
|
84
|
-
|
85
|
-
|
86
|
-
vt = @constellation.ValueType(@vocabulary, name)
|
87
|
-
vt.supertype = base_type if base_type
|
88
|
-
vt.length = length if length
|
89
|
-
vt.scale = scale if scale
|
90
|
-
|
91
|
-
# REVISIT: Find and apply the units
|
92
|
-
|
93
|
-
if ranges.size != 0
|
94
|
-
vt.value_restriction = value_restriction(ranges, enforcement)
|
95
|
-
end
|
63
|
+
raise failure_reason unless ok
|
64
|
+
vocabulary
|
96
65
|
end
|
97
66
|
|
98
|
-
def
|
99
|
-
|
100
|
-
ranges.each do |range|
|
101
|
-
min, max = Array === range ? range : [range, range]
|
102
|
-
v_range = @constellation.ValueRange(
|
103
|
-
min ? [[String === min ? eval(min) : min.to_s, String === min, nil], true] : nil,
|
104
|
-
max ? [[String === max ? eval(max) : max.to_s, String === max, nil], true] : nil
|
105
|
-
)
|
106
|
-
ar = @constellation.AllowedRange(vr, v_range)
|
107
|
-
end
|
108
|
-
apply_enforcement(vr, enforcement) if enforcement
|
109
|
-
vr
|
110
|
-
end
|
111
|
-
|
112
|
-
def apply_enforcement(constraint, enforcement)
|
113
|
-
constraint.enforcement = enforcement[0]
|
114
|
-
constraint.enforcement.agent = enforcement[1] if enforcement[1]
|
115
|
-
end
|
116
|
-
|
117
|
-
def entity_type(name, supertypes, identification, mapping_pragmas, clauses)
|
118
|
-
#puts "Entity Type #{name}, supertypes #{supertypes.inspect}, id #{identification.inspect}, clauses = #{clauses.inspect}"
|
119
|
-
debug :entity, "Defining Entity Type #{name}" do
|
120
|
-
# Assert the entity:
|
121
|
-
# If this entity was forward referenced, this won't be a new object, and will subsume its roles
|
122
|
-
entity_type = @constellation.EntityType(@vocabulary, name)
|
123
|
-
entity_type.is_independent = true if (mapping_pragmas.include? 'independent')
|
124
|
-
|
125
|
-
# Set up its supertypes:
|
126
|
-
supertypes.each do |supertype_name|
|
127
|
-
add_supertype(entity_type, supertype_name, !identification && supertype_name == supertypes[0], mapping_pragmas)
|
128
|
-
end
|
129
|
-
|
130
|
-
# If we're using a common identification mode, find or create the necessary ValueTypes first:
|
131
|
-
vt_name = vt = nil
|
132
|
-
if identification && identification[:mode]
|
133
|
-
mode = identification[:mode] # An identification mode
|
134
|
-
|
135
|
-
# Find or Create an appropriate ValueType called "#{name}#{mode}", of the supertype "#{mode}"
|
136
|
-
vt_name = "#{name}#{mode}"
|
137
|
-
unless vt = @constellation.ValueType[[@vocabulary, vt_name]]
|
138
|
-
base_vt = @constellation.ValueType(@vocabulary, mode)
|
139
|
-
vt = @constellation.ValueType(@vocabulary, vt_name, :supertype => base_vt)
|
140
|
-
if parameters = identification[:parameters]
|
141
|
-
length, scale = *parameters
|
142
|
-
vt.length = length if length
|
143
|
-
vt.scale = scale if scale
|
144
|
-
end
|
145
|
-
end
|
146
|
-
# REVISIT: If we do this, it gets emitted twice when we generate CQL. The generator should detect that the restriction is the same and not emit it.
|
147
|
-
#if (ranges = identification[:restriction])
|
148
|
-
# vt.value_restriction = value_restriction(ranges, identification[:enforcement])
|
149
|
-
#end
|
150
|
-
end
|
151
|
-
|
152
|
-
# Use a two-pass algorithm for entity fact types...
|
153
|
-
# The first step is to find all role references and definitions in the clauses
|
154
|
-
# After bind_roles, each phrase in each clause is either:
|
155
|
-
# * a string, which is a linking word, or
|
156
|
-
# * the phrase hash augmented with a :binding=>Binding
|
157
|
-
@symbols = SymbolTable.new(@constellation, @vocabulary)
|
158
|
-
@symbols.bind_roles_in_clauses(clauses, identification ? identification[:roles] : nil)
|
159
|
-
|
160
|
-
# Next arrange the clauses according to what fact they belong to,
|
161
|
-
# then process each fact type using normal fact type processing.
|
162
|
-
# That way if we find a fact type here having none of the players being the
|
163
|
-
# entity type, we know it's an objectified fact type. The CQL syntax might make
|
164
|
-
# us come here with such a case when the fact type is a subtype of some entity type,
|
165
|
-
# such as occurs in the Metamodel with TypeInheritance.
|
166
|
-
|
167
|
-
# N.B. This doesn't allow forward identification by roles with adjectives (see the i[0]):
|
168
|
-
@symbols.allowed_forward = (ir = identification && identification[:roles]) ? ir.inject({}){|h, i| h[i[0]] = true; h} : {}
|
169
|
-
|
170
|
-
identifying_fact_types = {}
|
171
|
-
clauses_by_fact_type(clauses).each do |clauses_for_fact_type|
|
172
|
-
fact_type = nil
|
173
|
-
@symbols.embedded_presence_constraints = [] # Clear embedded_presence_constraints for each fact type
|
174
|
-
debug :entity, "New Fact Type for entity #{name}" do
|
175
|
-
clauses_for_fact_type.each do |clause|
|
176
|
-
type, qualifiers, phrases, context = *clause
|
177
|
-
debug :reading, "Clause: #{clause.inspect}" do
|
178
|
-
f, r = *bind_fact_reading(fact_type, qualifiers, phrases)
|
179
|
-
identifying_fact_types[f] = true
|
180
|
-
fact_type ||= f
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# Find the role that this entity type plays in the fact type, if any:
|
186
|
-
debug :reading, "Roles are: #{fact_type.all_role.map{|role| (role.concept == entity_type ? "*" : "") + role.concept.name }*", "}"
|
187
|
-
player_roles = fact_type.all_role.select{|role| role.concept == entity_type }
|
188
|
-
raise "#{role.concept.name} may only play one role in each identifying fact type" if player_roles.size > 1
|
189
|
-
if player_role = player_roles[0]
|
190
|
-
non_player_roles = fact_type.all_role-[player_role]
|
191
|
-
|
192
|
-
raise "#{name} cannot be identified by a role in a non-binary fact type" if non_player_roles.size > 1
|
193
|
-
elsif identification
|
194
|
-
# This situation occurs when an objectified fact type has an entity identifier
|
195
|
-
#raise "Entity type #{name} cannot objectify fact type #{fact_type.describe}, it already objectifies #{entity_type.fact_type.describe}" if entity_type.fact_type
|
196
|
-
raise "Entity type #{name} cannot objectify fact type #{identification.inspect}, it already objectifies #{entity_type.fact_type.describe}" if entity_type.fact_type
|
197
|
-
debug :entity, "Entity type #{name} objectifies fact type #{fact_type.describe} with distinct identifier"
|
198
|
-
|
199
|
-
entity_type.fact_type = fact_type
|
200
|
-
fact_type_identification(fact_type, name, false)
|
201
|
-
else
|
202
|
-
debug :entity, "Entity type #{name} objectifies fact type #{fact_type.describe}"
|
203
|
-
# it's an objectified fact type, such as a subtype
|
204
|
-
entity_type.fact_type = fact_type
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
# Finally, create the identifying uniqueness constraint, or mark it as preferred
|
209
|
-
# if it's already been created. The identifying roles have been defined already.
|
210
|
-
|
211
|
-
if identification
|
212
|
-
debug :identification, "Handling identification" do
|
213
|
-
if id_role_names = identification[:roles] # A list of identifying roles
|
214
|
-
debug "Identifying roles: #{id_role_names.inspect}"
|
215
|
-
|
216
|
-
# Pick out the identifying_roles in the order they were declared,
|
217
|
-
# not the order the fact types were defined:
|
218
|
-
identifying_roles = id_role_names.map do |names|
|
219
|
-
unless (role = bind_unary_fact_type(entity_type, names))
|
220
|
-
player, binding = @symbols.bind(names)
|
221
|
-
role = @symbols.roles_by_binding[binding]
|
222
|
-
raise "identifying role #{names*"-"} not found in fact types for #{name}" unless role
|
223
|
-
end
|
224
|
-
role
|
225
|
-
end
|
226
|
-
|
227
|
-
# Find a uniqueness constraint as PI, or make one
|
228
|
-
pc = find_pc_over_roles(identifying_roles)
|
229
|
-
if (pc)
|
230
|
-
debug "Existing PC #{pc.verbalise} is now PK for #{name} #{pc.class.roles.keys.map{|k|"#{k} => "+pc.send(k).verbalise}*", "}"
|
231
|
-
pc.is_preferred_identifier = true
|
232
|
-
pc.name = "#{name}PK" unless pc.name
|
233
|
-
else
|
234
|
-
debug "Adding PK for #{name} using #{identifying_roles.map{|r| r.concept.name}.inspect}"
|
235
|
-
|
236
|
-
role_sequence = @constellation.RoleSequence(:new)
|
237
|
-
# REVISIT: Need to sort the identifying_roles to match the identification parameter array
|
238
|
-
identifying_roles.each_with_index do |identifying_role, index|
|
239
|
-
@constellation.RoleRef(role_sequence, index, :role => identifying_role)
|
240
|
-
end
|
241
|
-
|
242
|
-
# Add a unique constraint over all identifying roles
|
243
|
-
pc = @constellation.PresenceConstraint(
|
244
|
-
:new,
|
245
|
-
:vocabulary => @vocabulary,
|
246
|
-
:name => "#{name}PK", # Is this a useful name?
|
247
|
-
:role_sequence => role_sequence,
|
248
|
-
:is_preferred_identifier => true,
|
249
|
-
:max_frequency => 1 # Unique
|
250
|
-
#:is_mandatory => true,
|
251
|
-
#:min_frequency => 1,
|
252
|
-
)
|
253
|
-
end
|
254
|
-
|
255
|
-
elsif identification[:mode]
|
256
|
-
mode = identification[:mode] # An identification mode
|
257
|
-
|
258
|
-
raise "Entity definition using reference mode may only have one identifying fact type" if identifying_fact_types.size > 1
|
259
|
-
mode_fact_type = identifying_fact_types.keys[0]
|
260
|
-
|
261
|
-
# If the entity type is an objectified fact type, don't use the objectified fact type!
|
262
|
-
mode_fact_type = nil if mode_fact_type && mode_fact_type.entity_type == entity_type
|
263
|
-
|
264
|
-
debug :mode, "Processing Reference Mode for #{name}#{mode_fact_type ? " with existing '#{mode_fact_type.default_reading}'" : ""}"
|
265
|
-
|
266
|
-
# Fact Type:
|
267
|
-
if (ft = mode_fact_type)
|
268
|
-
entity_role, value_role = ft.all_role.partition{|role| role.concept == entity_type}.flatten
|
269
|
-
else
|
270
|
-
ft = @constellation.FactType(:new)
|
271
|
-
entity_role = @constellation.Role(ft, 0, :concept => entity_type)
|
272
|
-
value_role = @constellation.Role(ft, 1, :concept => vt)
|
273
|
-
debug :mode, "Creating new fact type to identify #{name}"
|
274
|
-
end
|
275
|
-
|
276
|
-
# REVISIT: The restriction applies only to the value role. There is good reason to apply it above to the value type as well.
|
277
|
-
if (ranges = identification[:restriction])
|
278
|
-
value_role.role_value_restriction = value_restriction(ranges, identification[:enforcement])
|
279
|
-
end
|
280
|
-
|
281
|
-
# Forward reading, if it doesn't already exist:
|
282
|
-
rss = entity_role.all_role_ref.map{|rr| rr.role_sequence.all_role_ref.size == 2 ? rr.role_sequence : nil }.compact
|
283
|
-
# Find or create RoleSequences for the forward and reverse readings:
|
284
|
-
rs01 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [entity_role, value_role] }[0]
|
285
|
-
if !rs01
|
286
|
-
rs01 = @constellation.RoleSequence(:new)
|
287
|
-
@constellation.RoleRef(rs01, 0, :role => entity_role)
|
288
|
-
@constellation.RoleRef(rs01, 1, :role => value_role)
|
289
|
-
end
|
290
|
-
if rs01.all_reading.empty?
|
291
|
-
@constellation.Reading(ft, ft.all_reading.size, :role_sequence => rs01, :text => "{0} has {1}")
|
292
|
-
debug :mode, "Creating new forward reading '#{name} has #{vt.name}'"
|
293
|
-
else
|
294
|
-
debug :mode, "Using existing forward reading"
|
295
|
-
end
|
296
|
-
|
297
|
-
# Reverse reading:
|
298
|
-
rs10 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [value_role, entity_role] }[0]
|
299
|
-
if !rs10
|
300
|
-
rs10 = @constellation.RoleSequence(:new)
|
301
|
-
@constellation.RoleRef(rs10, 0, :role => value_role)
|
302
|
-
@constellation.RoleRef(rs10, 1, :role => entity_role)
|
303
|
-
end
|
304
|
-
if rs10.all_reading.empty?
|
305
|
-
@constellation.Reading(ft, ft.all_reading.size, :role_sequence => rs10, :text => "{0} is of {1}")
|
306
|
-
debug :mode, "Creating new reverse reading '#{vt.name} is of #{name}'"
|
307
|
-
else
|
308
|
-
debug :mode, "Using existing reverse reading"
|
309
|
-
end
|
310
|
-
|
311
|
-
# Entity Type must have a value type. Find or create the role sequence, then create a PC if necessary
|
312
|
-
debug :mode, "entity_role has #{entity_role.all_role_ref.size} attached sequences"
|
313
|
-
debug :mode, "entity_role has #{entity_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}.size} unary sequences"
|
314
|
-
rs0 = entity_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1 ? rr.role_sequence : nil }.compact[0]
|
315
|
-
if !rs0
|
316
|
-
rs0 = @constellation.RoleSequence(:new)
|
317
|
-
@constellation.RoleRef(rs0, 0, :role => entity_role)
|
318
|
-
debug :mode, "Creating new EntityType role sequence"
|
319
|
-
else
|
320
|
-
rs0 = rs0.role_sequence
|
321
|
-
debug :mode, "Using existing EntityType role sequence"
|
322
|
-
end
|
323
|
-
if (rs0.all_presence_constraint.size == 0)
|
324
|
-
constraint = @constellation.PresenceConstraint(
|
325
|
-
:new,
|
326
|
-
:name => '',
|
327
|
-
:vocabulary => @vocabulary,
|
328
|
-
:role_sequence => rs0,
|
329
|
-
:min_frequency => 1,
|
330
|
-
:max_frequency => 1,
|
331
|
-
:is_preferred_identifier => false,
|
332
|
-
:is_mandatory => true
|
333
|
-
)
|
334
|
-
debug :mode, "Creating new EntityType PresenceConstraint"
|
335
|
-
else
|
336
|
-
debug :mode, "Using existing EntityType PresenceConstraint"
|
337
|
-
end
|
338
|
-
|
339
|
-
# Value Type must have a value type. Find or create the role sequence, then create a PC if necessary
|
340
|
-
debug :mode, "value_role has #{value_role.all_role_ref.size} attached sequences"
|
341
|
-
debug :mode, "value_role has #{value_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}.size} unary sequences"
|
342
|
-
rs1 = value_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1 ? rr.role_sequence : nil }.compact[0]
|
343
|
-
if (!rs1)
|
344
|
-
rs1 = @constellation.RoleSequence(:new)
|
345
|
-
@constellation.RoleRef(rs1, 0, :role => value_role)
|
346
|
-
debug :mode, "Creating new ValueType role sequence"
|
347
|
-
else
|
348
|
-
rs1 = rs1.role_sequence
|
349
|
-
debug :mode, "Using existing ValueType role sequence"
|
350
|
-
end
|
351
|
-
if (rs1.all_presence_constraint.size == 0)
|
352
|
-
constraint = @constellation.PresenceConstraint(
|
353
|
-
:new,
|
354
|
-
:name => '',
|
355
|
-
:vocabulary => @vocabulary,
|
356
|
-
:role_sequence => rs1,
|
357
|
-
:min_frequency => 0,
|
358
|
-
:max_frequency => 1,
|
359
|
-
:is_preferred_identifier => true,
|
360
|
-
:is_mandatory => false
|
361
|
-
)
|
362
|
-
debug :mode, "Creating new ValueType PresenceConstraint"
|
363
|
-
else
|
364
|
-
debug :mode, "Marking existing ValueType PresenceConstraint as preferred"
|
365
|
-
rs1.all_presence_constraint[0].is_preferred_identifier = true
|
366
|
-
end
|
367
|
-
end
|
368
|
-
end
|
369
|
-
else
|
370
|
-
# identification must be inherited.
|
371
|
-
debug "Identification is inherited"
|
372
|
-
end
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|
376
|
-
def add_supertype(entity_type, supertype_name, identifying_supertype, mapping_pragmas)
|
377
|
-
debug :supertype, "Supertype #{supertype_name}"
|
378
|
-
supertype = @constellation.EntityType(@vocabulary, supertype_name)
|
379
|
-
inheritance_fact = @constellation.TypeInheritance(entity_type, supertype, :fact_type_id => :new)
|
380
|
-
|
381
|
-
assimilations = mapping_pragmas.select { |p| ['absorbed', 'separate', 'partitioned'].include? p}
|
382
|
-
raise "Conflicting assimilation pragmas #{assimilations*", "}" if assimilations.size > 1
|
383
|
-
inheritance_fact.assimilation = assimilations[0]
|
384
|
-
|
385
|
-
# Create a reading:
|
386
|
-
sub_role = @constellation.Role(inheritance_fact, 0, :concept => entity_type)
|
387
|
-
super_role = @constellation.Role(inheritance_fact, 1, :concept => supertype)
|
388
|
-
|
389
|
-
rs = @constellation.RoleSequence(:new)
|
390
|
-
@constellation.RoleRef(rs, 0, :role => sub_role)
|
391
|
-
@constellation.RoleRef(rs, 1, :role => super_role)
|
392
|
-
@constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :text => "{0} is a kind of {1}")
|
393
|
-
@constellation.Reading(inheritance_fact, 1, :role_sequence => rs, :text => "{0} is a subtype of {1}")
|
394
|
-
|
395
|
-
rs2 = @constellation.RoleSequence(:new)
|
396
|
-
@constellation.RoleRef(rs2, 0, :role => super_role)
|
397
|
-
@constellation.RoleRef(rs2, 1, :role => sub_role)
|
398
|
-
n = 'aeiouh'.include?(sub_role.concept.name.downcase[0]) ? 1 : 0
|
399
|
-
@constellation.Reading(inheritance_fact, 2+n, :role_sequence => rs2, :text => "{0} is a {1}")
|
400
|
-
@constellation.Reading(inheritance_fact, 3-n, :role_sequence => rs2, :text => "{0} is an {1}")
|
401
|
-
|
402
|
-
if identifying_supertype
|
403
|
-
inheritance_fact.provides_identification = true
|
404
|
-
end
|
405
|
-
|
406
|
-
# Create uniqueness constraints over the subtyping fact type
|
407
|
-
p1rs = @constellation.RoleSequence(:new)
|
408
|
-
@constellation.RoleRef(p1rs, 0).role = sub_role
|
409
|
-
pc1 = @constellation.PresenceConstraint(:new)
|
410
|
-
pc1.name = "#{entity_type.name}MustHaveSupertype#{supertype.name}"
|
411
|
-
pc1.vocabulary = @vocabulary
|
412
|
-
pc1.role_sequence = p1rs
|
413
|
-
pc1.is_mandatory = true # A subtype instance must have a supertype instance
|
414
|
-
pc1.min_frequency = 1
|
415
|
-
pc1.max_frequency = 1
|
416
|
-
pc1.is_preferred_identifier = false
|
417
|
-
|
418
|
-
# The supertype role often identifies the subtype:
|
419
|
-
p2rs = @constellation.RoleSequence(:new)
|
420
|
-
@constellation.RoleRef(p2rs, 0).role = super_role
|
421
|
-
pc2 = @constellation.PresenceConstraint(:new)
|
422
|
-
pc2.name = "#{supertype.name}MayBeA#{entity_type.name}"
|
423
|
-
pc2.vocabulary = @vocabulary
|
424
|
-
pc2.role_sequence = p2rs
|
425
|
-
pc2.is_mandatory = false
|
426
|
-
pc2.min_frequency = 0
|
427
|
-
pc2.max_frequency = 1
|
428
|
-
pc2.is_preferred_identifier = inheritance_fact.provides_identification
|
429
|
-
end
|
430
|
-
|
431
|
-
def unit params
|
432
|
-
singular = params[:singular]
|
433
|
-
plural = params[:plural]
|
434
|
-
base_units = params[:base]
|
435
|
-
denominator = params[:coefficient][:denominator]
|
436
|
-
numerator = params[:coefficient][:numerator]
|
437
|
-
offset = params[:offset]
|
438
|
-
approximately = params[:approximately]
|
439
|
-
ephemeral = params[:ephemeral]
|
440
|
-
|
441
|
-
if (numerator.to_f / denominator.to_i != 1.0)
|
442
|
-
coefficient = @constellation.Coefficient(
|
443
|
-
:numerator => numerator.to_f,
|
444
|
-
:denominator => denominator.to_i,
|
445
|
-
:is_precise => !approximately
|
446
|
-
)
|
447
|
-
else
|
448
|
-
coefficient = nil
|
449
|
-
end
|
450
|
-
offset = offset.to_f
|
451
|
-
offset = nil if offset == 0
|
452
|
-
debug :units, "Defining new unit #{singular}#{plural ? "/"+plural : ""}" do
|
453
|
-
debug :units, "Coefficient is #{coefficient.numerator}#{coefficient.denominator != 1 ? "/#{coefficient.denominator}" : ""} #{coefficient.is_precise ? "exactly" : "approximately"}" if coefficient
|
454
|
-
debug :units, "Offset is #{offset}" if offset
|
455
|
-
raise "Redefinition of unit #{singular}" if @constellation.Unit.values.detect{|u| u.name == singular}
|
456
|
-
raise "Redefinition of unit #{plural}" if @constellation.Unit.values.detect{|u| u.name == plural}
|
457
|
-
unit = @constellation.Unit(:new,
|
458
|
-
:name => singular,
|
459
|
-
# :plural => plural,
|
460
|
-
:coefficient => coefficient,
|
461
|
-
:offset => offset,
|
462
|
-
:is_fundamental => base_units.empty?,
|
463
|
-
:is_ephemeral => ephemeral,
|
464
|
-
:vocabulary => @vocabulary
|
465
|
-
)
|
466
|
-
base_units.each do |base_unit, exponent|
|
467
|
-
base = @constellation.Unit.values.detect{|u| u.name == base_unit}
|
468
|
-
debug :units, "Base unit #{base_unit}^#{exponent} #{base ? "" : "(implicitly fundamental)"}"
|
469
|
-
base ||= @constellation.Unit(:new, :name => base_unit, :is_fundamental => true, :vocabulary => @vocabulary)
|
470
|
-
@constellation.Derivation(:derived_unit => unit, :base_unit => base, :exponent => exponent)
|
471
|
-
end
|
472
|
-
if plural
|
473
|
-
plural_unit = @constellation.Unit(:new,
|
474
|
-
:name => plural,
|
475
|
-
:is_fundamental => false,
|
476
|
-
:vocabulary => @vocabulary
|
477
|
-
)
|
478
|
-
@constellation.Derivation(:derived_unit => plural_unit, :base_unit => unit, :exponent => 1)
|
479
|
-
end
|
480
|
-
end
|
481
|
-
end
|
482
|
-
|
483
|
-
# If one of the words is the name of the entity type, and the other
|
484
|
-
# words consist of a unary fact type reading, return the role it plays.
|
485
|
-
def bind_unary_fact_type(entity_type, words)
|
486
|
-
return nil unless i = words.index(entity_type.name)
|
487
|
-
|
488
|
-
to_match = words.clone
|
489
|
-
to_match[i] = '{0}'
|
490
|
-
to_match = to_match*' '
|
491
|
-
|
492
|
-
# See if any unary fact type of this or any supertype matches these words:
|
493
|
-
entity_type.supertypes_transitive.each do |supertype|
|
494
|
-
supertype.all_role.each do |role|
|
495
|
-
role.fact_type.all_role.size == 1 &&
|
496
|
-
role.fact_type.all_reading.each do |reading|
|
497
|
-
if reading.text == to_match
|
498
|
-
debug :identification, "Bound identification to unary role '#{to_match.sub(/\{0\}/, entity_type.name)}'"
|
499
|
-
return role
|
500
|
-
end
|
501
|
-
end
|
502
|
-
end
|
503
|
-
end
|
504
|
-
nil
|
505
|
-
end
|
506
|
-
|
507
|
-
def fact_type(name, clauses, conditions)
|
508
|
-
debug "Processing clauses for fact type" do
|
509
|
-
fact_type = nil
|
510
|
-
|
511
|
-
#
|
512
|
-
# The first step is to find all role references and definitions in the phrases
|
513
|
-
# This also:
|
514
|
-
# * deletes any adjectives that were used but not hyphenated
|
515
|
-
# * changes each linking word phrase into a simple String
|
516
|
-
# * adds a :binding key to each bound role
|
517
|
-
#
|
518
|
-
@symbols = SymbolTable.new(@constellation, @vocabulary)
|
519
|
-
@symbols.bind_roles_in_clauses(clauses)
|
520
|
-
|
521
|
-
clauses.each do |clause|
|
522
|
-
kind, qualifiers, phrases, context = *clause
|
523
|
-
|
524
|
-
fact_type, r = *bind_fact_reading(fact_type, qualifiers, phrases)
|
525
|
-
end
|
526
|
-
|
527
|
-
# The fact type has a name iff it's objectified as an entity type
|
528
|
-
#puts "============= Creating entity type #{name} to nominalize fact type #{fact_type.default_reading} ======================" if name
|
529
|
-
fact_type.entity_type = @constellation.EntityType(@vocabulary, name) if name
|
530
|
-
|
531
|
-
# Add the identifying PresenceConstraint for this fact type:
|
532
|
-
if fact_type.all_role.size == 1 && !fact_type.entity_type
|
533
|
-
# All is well, unaries don't need an identifying PC unless objectified
|
534
|
-
else
|
535
|
-
fact_type_identification(fact_type, name, true)
|
536
|
-
end
|
537
|
-
|
538
|
-
# REVISIT: Process the fact derivation conditions, if any
|
539
|
-
end
|
540
|
-
end
|
541
|
-
|
542
|
-
def fact(population_name, clauses)
|
543
|
-
debug "Processing clauses for fact" do
|
544
|
-
population_name ||= ''
|
545
|
-
population = @constellation.Population(@vocabulary, population_name)
|
546
|
-
@symbols = SymbolTable.new(@constellation, @vocabulary)
|
547
|
-
@symbols.bind_roles_in_clauses(clauses)
|
548
|
-
|
549
|
-
bound_instances = {} # Instances indexed by binding
|
550
|
-
facts =
|
551
|
-
clauses.map do |clause|
|
552
|
-
kind, qualifiers, phrases, context = *clause
|
553
|
-
# Every bound word (term) in the phrases must have a literal
|
554
|
-
# OR be bound to an entity type identified by the phrases
|
555
|
-
# Any clause that has one binding and no other word is a value instance or simply-identified entity type
|
556
|
-
phrases.map! do |phrase|
|
557
|
-
next phrase unless l = phrase[:literal]
|
558
|
-
binding = phrase[:binding]
|
559
|
-
debug :instance, "Making #{binding.concept.class.basename} #{binding.concept.name} using #{l.inspect}" do
|
560
|
-
bound_instances[binding] =
|
561
|
-
instance_identified_by_literal(population, binding.concept, l)
|
562
|
-
end
|
563
|
-
phrase
|
564
|
-
end
|
565
|
-
|
566
|
-
if phrases.size == 1 && Hash === (phrase = phrases[0])
|
567
|
-
binding = phrase[:binding]
|
568
|
-
l = phrase[:literal]
|
569
|
-
debug :instance, "Making(2) #{binding.concept.class.basename} #{binding.concept.name} using #{l.inspect}" do
|
570
|
-
bound_instances[binding] =
|
571
|
-
instance_identified_by_literal(population, binding.concept, l)
|
572
|
-
end
|
573
|
-
else
|
574
|
-
[phrases, *bind_fact_reading(nil, qualifiers, phrases)]
|
575
|
-
end
|
576
|
-
end
|
577
|
-
|
578
|
-
# Because the fact types may include forward references, we must process the list repeatedly
|
579
|
-
# until we make no further progress. Any remaining
|
580
|
-
progress = true
|
581
|
-
pass = 0
|
582
|
-
while progress
|
583
|
-
progress = false
|
584
|
-
pass += 1
|
585
|
-
debug :instance, "Pass #{pass}" do
|
586
|
-
facts.map! do |fact|
|
587
|
-
next fact unless fact.is_a?(Array)
|
588
|
-
phrases, fact_type, reading = *fact
|
589
|
-
|
590
|
-
# This is a fact type we bound; see if we can create the fact instance yet
|
591
|
-
|
592
|
-
bare_roles = phrases.select{|w| w.is_a?(Hash) && !w[:literal] && !bound_instances[w[:binding]]}
|
593
|
-
# REVISIT: Bare bindings might be bound to instances we created
|
594
|
-
|
595
|
-
debug :instance, "Considering '#{fact_type.preferred_reading.expand}' with bare roles: #{bare_roles.map{|role| role[:binding].concept.name}*", "} "
|
596
|
-
|
597
|
-
case
|
598
|
-
when bare_roles.size == 0
|
599
|
-
debug :instance, "All bindings in '#{fact_type.preferred_reading.expand}' contain instances; create the fact type"
|
600
|
-
instances = phrases.select{|p| p.is_a?(Hash)}.map{|p| bound_instances[p[:binding]]}
|
601
|
-
debug :instance, "Instances are #{instances.map{|i| "#{i.concept.name} #{i.value.inspect}"}*", "}"
|
602
|
-
|
603
|
-
# Check that this fact doesn't already exist
|
604
|
-
fact = fact_type.all_fact.detect{|f|
|
605
|
-
# Get the role values of this fact in the order of the reading we just bound
|
606
|
-
role_values_in_reading_order = f.all_role_value.sort_by do |rv|
|
607
|
-
reading.role_sequence.all_role_ref.detect{|rr| rr.role == rv.role}.ordinal
|
608
|
-
end
|
609
|
-
# If all this fact's role values are played by the bound instances, it's the same fact
|
610
|
-
!role_values_in_reading_order.zip(instances).detect{|rv, i| rv.instance != i }
|
611
|
-
}
|
612
|
-
unless fact
|
613
|
-
fact = @constellation.Fact(:new, :fact_type => fact_type, :population => population)
|
614
|
-
@constellation.Instance(:new, :concept => fact_type.entity_type, :fact => fact, :population => population)
|
615
|
-
reading.role_sequence.all_role_ref.zip(instances).each do |rr, instance|
|
616
|
-
debug :instance, "New fact has #{instance.concept.name} role #{instance.value.inspect}"
|
617
|
-
@constellation.RoleValue(:fact => fact, :instance => instance, :role => rr.role, :population => population)
|
618
|
-
end
|
619
|
-
else
|
620
|
-
debug :instance, "Found existing fact type instance"
|
621
|
-
end
|
622
|
-
progress = true
|
623
|
-
next fact
|
624
|
-
|
625
|
-
# If we have one bare role (no literal or instance) played by an entity type,
|
626
|
-
# and the bound fact type participates in the identifier, we might now be able
|
627
|
-
# to create the entity instance.
|
628
|
-
when bare_roles.size == 1 &&
|
629
|
-
(binding = bare_roles[0][:binding]) &&
|
630
|
-
(e = binding.concept).is_a?(ActiveFacts::Metamodel::EntityType) &&
|
631
|
-
e.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type == fact_type}
|
632
|
-
|
633
|
-
# Check this instance doesn't already exist already:
|
634
|
-
identifying_binding = (phrases.select{|p| Hash === p}.map{|p|p[:binding]}-[binding])[0]
|
635
|
-
identifying_instance = bound_instances[identifying_binding]
|
636
|
-
|
637
|
-
debug :instance, "This clause associates a new #{binding.concept.name} with a #{identifying_binding.concept.name}#{identifying_instance ? " which exists" : ""}"
|
638
|
-
|
639
|
-
identifying_role_ref = e.preferred_identifier.role_sequence.all_role_ref.detect { |rr|
|
640
|
-
rr.role.fact_type == fact_type && rr.role.concept == identifying_binding.concept
|
641
|
-
}
|
642
|
-
unless identifying_role_ref
|
643
|
-
debug :instance, "Failed to find a #{identifying_instance.concept.name}"
|
644
|
-
next fact # We can't do this yet
|
645
|
-
end
|
646
|
-
role_value = identifying_instance.all_role_value.detect do |rv|
|
647
|
-
rv.fact.fact_type == identifying_role_ref.role.fact_type
|
648
|
-
end
|
649
|
-
if role_value
|
650
|
-
instance = (role_value.fact.all_role_value.to_a-[role_value])[0].instance
|
651
|
-
debug :instance, "Found existing instance (of #{instance.concept.name}) from a previous definition"
|
652
|
-
bound_instances[binding] = instance
|
653
|
-
progress = true
|
654
|
-
next role_value.instance
|
655
|
-
end
|
656
|
-
|
657
|
-
pi_role_refs = e.preferred_identifier.role_sequence.all_role_ref
|
658
|
-
# For each pi role, we have to find the fact clause, which contains the binding we need.
|
659
|
-
# Then we have to create an instance of each fact
|
660
|
-
identifiers =
|
661
|
-
pi_role_refs.map do |rr|
|
662
|
-
fact_a = facts.detect{|f| f.is_a?(Array) && f[1] == rr.role.fact_type}
|
663
|
-
identifying_binding = fact_a[0].detect{|phrase| phrase.is_a?(Hash) && phrase[:binding] != binding}[:binding]
|
664
|
-
identifying_instance = bound_instances[identifying_binding]
|
665
|
-
|
666
|
-
[rr, fact_a, identifying_binding, identifying_instance]
|
667
|
-
end
|
668
|
-
if identifiers.detect{ |i| !i[3] } # Not all required facts are bound yet
|
669
|
-
debug :instance, "Can't go through with creating #{binding.concept.name}; not all the facts are in"
|
670
|
-
next fact
|
671
|
-
end
|
672
|
-
|
673
|
-
debug :instance, "Going ahead with creating #{binding.concept.name} using #{identifiers.size} roles"
|
674
|
-
instance = @constellation.Instance(:new, :concept => e, :population => population)
|
675
|
-
bound_instances[binding] = instance
|
676
|
-
identifiers.each do |rr, fact_a, identifying_binding, identifying_instance|
|
677
|
-
# This reading provides the identifying literal for the EntityType e
|
678
|
-
id_fact = @constellation.Fact(:new, :fact_type => rr.role.fact_type, :population => population)
|
679
|
-
role = (rr.role.fact_type.all_role.to_a-[rr.role])[0]
|
680
|
-
@constellation.RoleValue(:instance => instance, :fact => id_fact, :population => population, :role => role)
|
681
|
-
@constellation.RoleValue(:instance => identifying_instance, :fact => id_fact, :role => rr.role, :population => population)
|
682
|
-
true
|
683
|
-
end
|
684
|
-
|
685
|
-
progress = true
|
686
|
-
end
|
687
|
-
fact
|
688
|
-
end
|
689
|
-
end
|
690
|
-
end
|
691
|
-
incomplete = facts.select{|ft| !ft.is_a?(ActiveFacts::Metamodel::Instance) && !ft.is_a?(ActiveFacts::Metamodel::Fact)}
|
692
|
-
if incomplete.size > 0
|
693
|
-
# Provide a readable description of the problem here, by showing each binding with no instance
|
694
|
-
missing_bindings = incomplete.map do |f|
|
695
|
-
phrases = f[0]
|
696
|
-
phrases.select{|p|
|
697
|
-
p.is_a?(Hash) and binding = p[:binding] and !bound_instances[binding]
|
698
|
-
}.map{|phrase| phrase[:binding]}
|
699
|
-
end.flatten.uniq
|
700
|
-
raise "Not enough facts are given to identify #{
|
701
|
-
missing_bindings.map do |b|
|
702
|
-
[ b.leading_adjective, b.concept.name, b.trailing_adjective ].compact*" " +
|
703
|
-
" (need #{b.concept.preferred_identifier.role_sequence.all_role_ref.map do |rr|
|
704
|
-
[ rr.leading_adjective, rr.role.role_name || rr.role.concept.name, rr.trailing_adjective ].compact*" "
|
705
|
-
end*", "
|
706
|
-
})"
|
707
|
-
end*", "
|
708
|
-
}"
|
709
|
-
end
|
710
|
-
end
|
711
|
-
end
|
712
|
-
|
713
|
-
def entity_identified_by_literal(population, concept, literal)
|
714
|
-
# A literal that identifies an entity type means the entity type has only one identifying role
|
715
|
-
# That role is played either by a value type, or by another similarly single-identified entity type
|
716
|
-
debug "Making EntityType #{concept.name} identified by '#{literal}' #{population.name.size>0 ? " in "+population.name.inspect : ''}" do
|
717
|
-
identifying_role_refs = concept.preferred_identifier.role_sequence.all_role_ref
|
718
|
-
raise "Single literal cannot satisfy multiple identifying roles for #{concept.name}" if identifying_role_refs.size > 1
|
719
|
-
role = identifying_role_refs.single.role
|
720
|
-
identifying_instance = instance_identified_by_literal(population, role.concept, literal)
|
721
|
-
existing_instance = nil
|
722
|
-
instance_rv = identifying_instance.all_role_value.detect { |rv|
|
723
|
-
next false unless rv.population == population # Not this population
|
724
|
-
next false unless rv.fact.fact_type == role.fact_type # Not this fact type
|
725
|
-
other_role_value = (rv.fact.all_role_value-[rv])[0]
|
726
|
-
existing_instance = other_role_value.instance
|
727
|
-
other_role_value.instance.concept == concept # Is it this concept?
|
728
|
-
}
|
729
|
-
if instance_rv
|
730
|
-
instance = existing_instance
|
731
|
-
debug :instance, "This #{concept.name} entity already exists"
|
732
|
-
else
|
733
|
-
fact = @constellation.Fact(:new, :fact_type => role.fact_type, :population => population)
|
734
|
-
instance = @constellation.Instance(:new, :concept => concept, :population => population)
|
735
|
-
# The identifying fact type has two roles; create both role instances:
|
736
|
-
@constellation.RoleValue(:instance => identifying_instance, :fact => fact, :population => population, :role => role)
|
737
|
-
@constellation.RoleValue(:instance => instance, :fact => fact, :population => population, :role => (role.fact_type.all_role-[role])[0])
|
738
|
-
end
|
739
|
-
instance
|
740
|
-
end
|
741
|
-
end
|
742
|
-
|
743
|
-
def instance_identified_by_literal(population, concept, literal)
|
744
|
-
if concept.is_a?(ActiveFacts::Metamodel::EntityType)
|
745
|
-
entity_identified_by_literal(population, concept, literal)
|
746
|
-
else
|
747
|
-
debug :instance, "Making ValueType #{concept.name} #{literal.inspect} #{population.name.size>0 ? " in "+population.name.inspect : ''}" do
|
748
|
-
|
749
|
-
is_a_string = String === literal
|
750
|
-
instance = @constellation.Instance.detect do |key, i|
|
751
|
-
# REVISIT: And same unit
|
752
|
-
i.population == population &&
|
753
|
-
i.value &&
|
754
|
-
i.value.literal == literal &&
|
755
|
-
i.value.is_a_string == is_a_string
|
756
|
-
end
|
757
|
-
#instance = concept.all_instance.detect { |instance|
|
758
|
-
# instance.population == population && instance.value == literal
|
759
|
-
#}
|
760
|
-
debug :instance, "This #{concept.name} value already exists" if instance
|
761
|
-
unless instance
|
762
|
-
instance = @constellation.Instance(
|
763
|
-
:new,
|
764
|
-
:concept => concept,
|
765
|
-
:population => population,
|
766
|
-
:value => [literal.to_s, is_a_string, nil]
|
767
|
-
)
|
768
|
-
end
|
769
|
-
instance
|
770
|
-
end
|
771
|
-
end
|
772
|
-
end
|
773
|
-
|
774
|
-
def constraint *value
|
775
|
-
case type = value.shift
|
776
|
-
when :presence
|
777
|
-
presence_constraint *value
|
778
|
-
when :set
|
779
|
-
set_constraint *value
|
780
|
-
when :subset
|
781
|
-
subset_constraint *value
|
782
|
-
when :equality
|
783
|
-
equality_constraint *value
|
784
|
-
else
|
785
|
-
$stderr.puts "REVISIT: external #{type} constraints aren't yet handled:\n\t"+value.map{|a| a.inspect }*"\n\t"
|
786
|
-
end
|
787
|
-
end
|
788
|
-
|
789
|
-
# The joins list is an array of an array of fact types.
|
790
|
-
# The fact types contain roles played by concepts, where each
|
791
|
-
# concept plays more than one role. In fact, a concept may
|
792
|
-
# occur in more than one binding, and each binding plays more
|
793
|
-
# than one role. The bindings that are common to all fact types
|
794
|
-
# in each array in the joins list form the constrained role
|
795
|
-
# sequences. Each binding that isn't common at this top level
|
796
|
-
# must occur more than once in each group of fact types where
|
797
|
-
# it appears, and it forms a join between those fact types.
|
798
|
-
def bind_joins_as_role_sequences(joins_list)
|
799
|
-
@symbols = SymbolTable.new(@constellation, @vocabulary)
|
800
|
-
fact_roles_list = []
|
801
|
-
bindings_list = []
|
802
|
-
joins_list.each_with_index do |joins, index|
|
803
|
-
# joins is an array of phrase arrays, each for one reading
|
804
|
-
@symbols.bind_roles_in_phrases_list(joins)
|
805
|
-
|
806
|
-
fact_roles_list << joins.map do |phrases|
|
807
|
-
ifr = invoked_fact_roles(phrases)
|
808
|
-
raise "Fact type reading not found for #{phrases.inspect}" unless ifr
|
809
|
-
ifr
|
810
|
-
end
|
811
|
-
bindings_list << joins.map do |phrases|
|
812
|
-
phrases.map{ |phrase| Hash === phrase ? phrase[:binding] : nil}.compact
|
813
|
-
end
|
814
|
-
end
|
815
|
-
|
816
|
-
# Each set of binding arrays in the list must share at least one common binding
|
817
|
-
bindings_by_join = bindings_list.map{|join| join.flatten}
|
818
|
-
common_bindings = bindings_by_join[1..-1].inject(bindings_by_join[0]) { |c, b| c & b }
|
819
|
-
# Was:
|
820
|
-
# common_bindings = bindings_list.inject(bindings_list[0]) { |common, bindings| common & bindings }
|
821
|
-
raise "Set constraints must have at least one common role between the sets" unless common_bindings.size > 0
|
822
|
-
|
823
|
-
# REVISIT: Do we need to constrain things such that each join path only includes *one* instance of each common binding?
|
824
|
-
|
825
|
-
# For each set of binding arrays, if there's more than one binding array in the set,
|
826
|
-
# it represents a join path. Here we check that each join path is complete, i.e. linked up.
|
827
|
-
# Each element of a join path is the array of bindings for a fact type invocation.
|
828
|
-
# Each invocation must share a binding (not one of the globally common ones) with
|
829
|
-
# another invocation in that join path.
|
830
|
-
bindings_list.each_with_index do |join, jpnum|
|
831
|
-
# Check that this bindings array creates a complete join path:
|
832
|
-
join.each_with_index do |bindings, i|
|
833
|
-
fact_type_roles = fact_roles_list[jpnum][i]
|
834
|
-
fact_type = fact_type_roles[0].fact_type
|
835
|
-
|
836
|
-
# The bindings are for one fact type invocation.
|
837
|
-
# These bindings must be joined to some later fact type by a common binding that isn't a globally-common one:
|
838
|
-
local_bindings = bindings-common_bindings
|
839
|
-
next if local_bindings.size == 0 # No join path is required, as only one fact type is invoked.
|
840
|
-
next if i == join.size-1 # We already checked that the last fact type invocation is joined
|
841
|
-
ok = local_bindings.detect do |local_binding|
|
842
|
-
j = i+1
|
843
|
-
join[j..-1].detect do |other_bindings|
|
844
|
-
other_fact_type_roles = fact_roles_list[jpnum][j]
|
845
|
-
other_fact_type = other_fact_type_roles[0].fact_type
|
846
|
-
j += 1
|
847
|
-
# These next two lines allow joining from/to an objectified fact type:
|
848
|
-
fact_type_roles.detect{|r| r.concept == other_fact_type.entity_type } ||
|
849
|
-
other_fact_type_roles.detect{|r| r.concept == fact_type.entity_type } ||
|
850
|
-
other_bindings.include?(local_binding)
|
851
|
-
end
|
852
|
-
end
|
853
|
-
raise "Incomplete join path; one of the bindings #{local_bindings.inspect} must re-occur to establish a join" unless ok
|
854
|
-
end
|
855
|
-
end
|
856
|
-
|
857
|
-
# Create the role sequences and their role references.
|
858
|
-
# Each role sequence contain one RoleRef for each common binding
|
859
|
-
# REVISIT: This results in ordering all RoleRefs according to the order of the common_bindings.
|
860
|
-
# This for example means that a set constraint having joins might have the join order changed so they all match.
|
861
|
-
# When you create e.g. a subset constraint in NORMA, make sure that the subset roles are created in the order of the preferred readings.
|
862
|
-
role_sequences = joins_list.map{|r| @constellation.RoleSequence(:new) }
|
863
|
-
common_bindings.each_with_index do |binding, index|
|
864
|
-
role_sequences.each_with_index do |rs, rsi|
|
865
|
-
join = bindings_list[rsi]
|
866
|
-
fact_pos = nil
|
867
|
-
join_pos = (0...join.size).detect do |i|
|
868
|
-
fact_pos = join[i].index(binding)
|
869
|
-
end
|
870
|
-
@constellation.RoleRef(rs, index).role = fact_roles_list[rsi][join_pos][fact_pos]
|
871
|
-
end
|
872
|
-
end
|
873
|
-
|
874
|
-
role_sequences
|
875
|
-
end
|
876
|
-
|
877
|
-
def presence_constraint(constrained_role_names, quantifier, phrases_list, context, enforcement)
|
878
|
-
raise "REVISIT: Join presence constraints not supported yet" if phrases_list[0].size > 1
|
879
|
-
phrases_list = phrases_list.map{|r| r[0] }
|
880
|
-
#p phrases_list
|
881
|
-
|
882
|
-
@symbols = SymbolTable.new(@constellation, @vocabulary)
|
883
|
-
|
884
|
-
# Find players for all constrained_role_names. These may use leading or trailing adjective forms...
|
885
|
-
constrained_players = []
|
886
|
-
constrained_bindings = []
|
887
|
-
constrained_role_names.each do |role_name|
|
888
|
-
player, binding = @symbols.bind(role_name)
|
889
|
-
constrained_players << player
|
890
|
-
constrained_bindings << binding
|
891
|
-
end
|
892
|
-
#puts "Constrained bindings are #{constrained_bindings.inspect}"
|
893
|
-
#puts "Constrained bindings object_id's are #{constrained_bindings.map{|b|b.object_id.to_s}*","}"
|
894
|
-
|
895
|
-
# Find players for all the concepts in all phrases_list:
|
896
|
-
@symbols.bind_roles_in_phrases_list(phrases_list)
|
897
|
-
|
898
|
-
constrained_roles = []
|
899
|
-
unmatched_roles = constrained_role_names.clone
|
900
|
-
phrases_list.each do |phrases|
|
901
|
-
# puts phrases.inspect
|
902
|
-
|
903
|
-
# If this succeeds, the phrases found matches the roles in our phrases
|
904
|
-
fact_roles = invoked_fact_roles(phrases)
|
905
|
-
raise "Fact type reading not found for #{phrases.inspect}" unless fact_roles
|
906
|
-
|
907
|
-
# Look for the constrained role(s); the bindings will be the same
|
908
|
-
matched_bindings = phrases.select{|p| Hash === p}.map{|p| p[:binding]}
|
909
|
-
#puts "matched_bindings = #{matched_bindings.inspect}"
|
910
|
-
#puts "matched_bindings object_id's are #{matched_bindings.map{|b|b.object_id.to_s}*","}}"
|
911
|
-
matched_bindings.each_with_index{|b, pos|
|
912
|
-
i = constrained_bindings.index(b)
|
913
|
-
next unless i
|
914
|
-
unmatched_roles[i] = nil
|
915
|
-
#puts "found #{constrained_bindings[i].inspect} found as #{b.inspect} in position #{i.inspect}"
|
916
|
-
role = fact_roles[pos]
|
917
|
-
constrained_roles << role unless constrained_roles.include?(role)
|
918
|
-
}
|
919
|
-
end
|
920
|
-
|
921
|
-
# Check that all constrained roles were matched at least once:
|
922
|
-
unmatched_roles.compact!
|
923
|
-
raise "Constrained roles #{unmatched_roles.map{|ur| ur*"-"}*", "} not found in fact types" if unmatched_roles.size != 0
|
924
|
-
|
925
|
-
rs = @constellation.RoleSequence(:new)
|
926
|
-
#puts "constrained_roles: #{constrained_roles.map{|r| r.concept.name}.inspect}"
|
927
|
-
constrained_roles.each_with_index do |role, index|
|
928
|
-
raise "Constrained role #{constrained_role_names[index]} not found" unless role
|
929
|
-
rr = @constellation.RoleRef(rs, index)
|
930
|
-
rr.role = role
|
931
|
-
end
|
932
|
-
#puts "New external PresenceConstraint with quantifier = #{quantifier.inspect} over #{rs.describe}"
|
933
|
-
|
934
|
-
# REVISIT: Check that no existing PC spans the same roles (nor a superset nor subset?)
|
935
|
-
|
936
|
-
constraint = @constellation.PresenceConstraint(
|
937
|
-
:new,
|
938
|
-
:name => '',
|
939
|
-
:vocabulary => @vocabulary,
|
940
|
-
:role_sequence => rs,
|
941
|
-
:min_frequency => quantifier[0],
|
942
|
-
:max_frequency => quantifier[1],
|
943
|
-
:is_preferred_identifier => false,
|
944
|
-
:is_mandatory => quantifier[0] && quantifier[0] > 0
|
945
|
-
)
|
946
|
-
apply_enforcement(constraint, enforcement) if enforcement
|
947
|
-
end
|
948
|
-
|
949
|
-
def set_constraint(constrained_roles, quantifier, joins_list, context, enforcement)
|
950
|
-
role_sequences = bind_joins_as_role_sequences(joins_list)
|
951
|
-
|
952
|
-
if quantifier[1] == nil
|
953
|
-
# create a presence constraint instead if we get quantifier = [N,nil] (at least N)
|
954
|
-
# We massage the bound role sequences to make this work.
|
955
|
-
raise "either/or constraint must have one common role" if role_sequences.size != 2 || role_sequences[0].all_role_ref.size != 1
|
956
|
-
second_role = role_sequences[1].all_role_ref.single.role
|
957
|
-
second_role_ref = @constellation.RoleRef(:role_sequence => role_sequences[0], :ordinal => 1, :role => second_role)
|
958
|
-
@constellation.deny(role_sequences[1].all_role_ref.single)
|
959
|
-
@constellation.deny(role_sequences[1])
|
960
|
-
constraint = @constellation.PresenceConstraint(
|
961
|
-
:new,
|
962
|
-
:name => '',
|
963
|
-
:vocabulary => @vocabulary,
|
964
|
-
:role_sequence => role_sequences[0],
|
965
|
-
:min_frequency => quantifier[0],
|
966
|
-
:max_frequency => nil,
|
967
|
-
:is_preferred_identifier => false,
|
968
|
-
:is_mandatory => true
|
969
|
-
)
|
970
|
-
apply_enforcement(constraint, enforcement) if enforcement
|
971
|
-
else
|
972
|
-
# Create a normal (mandatory) exclusion constraint:
|
973
|
-
constraint = @constellation.SetExclusionConstraint(:new)
|
974
|
-
constraint.vocabulary = @vocabulary
|
975
|
-
role_sequences.each_with_index do |rs, i|
|
976
|
-
@constellation.SetComparisonRoles(constraint, i, :role_sequence => rs)
|
977
|
-
end
|
978
|
-
apply_enforcement(constraint, enforcement) if enforcement
|
979
|
-
constraint.is_mandatory = quantifier[0] == 1
|
980
|
-
end
|
67
|
+
def compile_definition ast
|
68
|
+
ast.compile
|
981
69
|
end
|
982
70
|
|
983
|
-
def
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
#puts "subset_role_sequence = #{role_sequences[0].describe}"
|
989
|
-
#puts "superset_role_sequence = #{role_sequences[1].describe}"
|
990
|
-
|
991
|
-
# create the constraint:
|
992
|
-
constraint = @constellation.SubsetConstraint(:new)
|
993
|
-
constraint.vocabulary = @vocabulary
|
994
|
-
#constraint.name = nil
|
995
|
-
#constraint.enforcement =
|
996
|
-
constraint.subset_role_sequence = role_sequences[0]
|
997
|
-
constraint.superset_role_sequence = role_sequences[1]
|
998
|
-
apply_enforcement(constraint, enforcement) if enforcement
|
999
|
-
end
|
1000
|
-
|
1001
|
-
def equality_constraint(joins_list, context, enforcement)
|
1002
|
-
#puts "equality\n\t#{joins_list.map{|rl| rl.inspect}*"\n\tif and only if\n\t"}"
|
1003
|
-
|
1004
|
-
role_sequences = bind_joins_as_role_sequences(joins_list)
|
1005
|
-
|
1006
|
-
# Create the constraint:
|
1007
|
-
constraint = @constellation.SetEqualityConstraint(:new)
|
1008
|
-
constraint.vocabulary = @vocabulary
|
1009
|
-
role_sequences.each_with_index do |rs, i|
|
1010
|
-
@constellation.SetComparisonRoles(constraint, i, :role_sequence => rs)
|
1011
|
-
end
|
1012
|
-
apply_enforcement(constraint, enforcement) if enforcement
|
1013
|
-
end
|
1014
|
-
|
1015
|
-
# Search the supertypes of 'subtype' looking for an inheritance path to 'supertype',
|
1016
|
-
# and returning the array of TypeInheritance fact types from supertype to subtype.
|
1017
|
-
def inheritance_path(subtype, supertype)
|
1018
|
-
direct_inheritance = subtype.all_supertype_inheritance.select{|ti| ti.supertype == supertype}
|
1019
|
-
return direct_inheritance if (direct_inheritance[0])
|
1020
|
-
subtype.all_supertype_inheritance.each{|ti|
|
1021
|
-
ip = inheritance_path(ti.supertype, supertype)
|
1022
|
-
return ip+[ti] if (ip)
|
1023
|
-
}
|
1024
|
-
return nil
|
1025
|
-
end
|
1026
|
-
|
1027
|
-
# For a given phrase array from the parser, find the matching declared reading, and return
|
1028
|
-
# the array of Role object in the same order as they occur in the reading.
|
1029
|
-
def invoked_fact_roles(phrases)
|
1030
|
-
# REVISIT: Possibly this special reading from the parser can be removed now?
|
1031
|
-
if (phrases[0] == "!SUBTYPE!")
|
1032
|
-
subtype = phrases[1][:binding].concept
|
1033
|
-
supertype = phrases[2][:binding].concept
|
1034
|
-
raise "#{subtype.name} is not a subtype of #{supertype.name}" unless subtype.supertypes_transitive.include?(supertype)
|
1035
|
-
ip = inheritance_path(subtype, supertype)
|
1036
|
-
return [
|
1037
|
-
ip[-1].all_role.detect{|r| r.concept == subtype},
|
1038
|
-
ip[0].all_role.detect{|r| r.concept == supertype}
|
1039
|
-
]
|
1040
|
-
end
|
1041
|
-
|
1042
|
-
bindings = phrases.select{|p| Hash === p}
|
1043
|
-
players = bindings.map{|p| p[:binding].concept }
|
1044
|
-
invoked_fact_roles_by_players(phrases, players)
|
1045
|
-
end
|
1046
|
-
|
1047
|
-
def invoked_fact_roles_by_players(phrases, players)
|
1048
|
-
players[0].all_role.each do |role|
|
1049
|
-
# Does this fact type have the right number of roles?
|
1050
|
-
next if role.fact_type.all_role.size != players.size
|
1051
|
-
|
1052
|
-
# Does this fact type include the correct other players?
|
1053
|
-
# REVISIT: Might need subtype/supertype matching here, with an implied subtyping join invocation
|
1054
|
-
next if role.fact_type.all_role.detect{|r| !players.include?(r.concept)}
|
1055
|
-
|
1056
|
-
# Oooh, a real candidate. Check the reading words.
|
1057
|
-
debug "Considering "+role.fact_type.describe do
|
1058
|
-
next unless role.fact_type.all_reading.detect do |candidate_reading|
|
1059
|
-
debug "Considering reading"+candidate_reading.text do
|
1060
|
-
to_match = phrases.clone
|
1061
|
-
players_to_match = players.clone
|
1062
|
-
candidate_reading.words_and_role_refs.each do |wrr|
|
1063
|
-
if (wrr.is_a?(ActiveFacts::Metamodel::RoleRef))
|
1064
|
-
break unless Hash === to_match.first
|
1065
|
-
break unless binding = to_match[0][:binding]
|
1066
|
-
# REVISIT: May need to match super- or sub-types here too!
|
1067
|
-
break unless players_to_match[0] == wrr.role.concept
|
1068
|
-
break if wrr.leading_adjective && binding.leading_adjective != wrr.leading_adjective
|
1069
|
-
break if wrr.trailing_adjective && binding.trailing_adjective != wrr.trailing_adjective
|
1070
|
-
|
1071
|
-
# All matched.
|
1072
|
-
to_match.shift
|
1073
|
-
players_to_match.shift
|
1074
|
-
# elsif # REVISIT: Match "not" and "none" here as negating the fact type invocation
|
1075
|
-
else
|
1076
|
-
break unless String === to_match[0]
|
1077
|
-
break unless to_match[0] == wrr
|
1078
|
-
to_match.shift
|
1079
|
-
end
|
1080
|
-
end
|
1081
|
-
|
1082
|
-
# This is the first matching candidate.
|
1083
|
-
# REVISIT: Since we do sub/supertype matching (and will do more!),
|
1084
|
-
# we need to accumulate all possible matches to be sure
|
1085
|
-
# there's only one, or the match is exact, or risk ambiguity.
|
1086
|
-
debug "Reading match was #{to_match.size == 0 ? "ok" : "bad"}"
|
1087
|
-
return candidate_reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role} if to_match.size == 0
|
1088
|
-
end
|
1089
|
-
end
|
1090
|
-
end
|
1091
|
-
end
|
1092
|
-
|
1093
|
-
# Hmm, that didn't work, try the subtypes of the first player.
|
1094
|
-
# When a fact type matches like this, there is an implied join to the subtype.
|
1095
|
-
players[0].subtypes.each do |subtype|
|
1096
|
-
players[0] = subtype
|
1097
|
-
fr = invoked_fact_roles_by_players(phrases, players)
|
1098
|
-
return fr if fr
|
1099
|
-
end
|
1100
|
-
|
1101
|
-
# REVISIT: Do we need to do this again for the supertypes of the first player?
|
1102
|
-
|
1103
|
-
nil
|
1104
|
-
end
|
1105
|
-
|
1106
|
-
def bind_fact_reading(fact_type, qualifiers, phrases)
|
1107
|
-
reading = debug :reading, "Processing reading #{phrases.inspect}" do
|
1108
|
-
role_phrases = phrases.select do |phrase|
|
1109
|
-
Hash === phrase && phrase[:binding]
|
1110
|
-
end
|
1111
|
-
|
1112
|
-
# All readings for a fact type must have the same number of roles.
|
1113
|
-
# This might be relaxed later for fact clauses, where readings might
|
1114
|
-
# be concatenated if the adjacent items are the same concept.
|
1115
|
-
if (fact_type && fact_type.all_reading.size > 0 && role_phrases.size != fact_type.all_role.size)
|
1116
|
-
raise "#{
|
1117
|
-
role_phrases.size > fact_type.all_role.size ? "Too many" : "Not all"
|
1118
|
-
} roles found for non-initial reading of #{fact_type.describe}"
|
1119
|
-
end
|
1120
|
-
|
1121
|
-
# If the reading is the first and is an invocation of an existing fact type,
|
1122
|
-
# find and return the existing fact type and reading.
|
1123
|
-
if !fact_type
|
1124
|
-
bindings = role_phrases.map{|phrase| phrase[:binding]}
|
1125
|
-
bindings_by_name = bindings.sort_by{|b| [b.concept.name, b.leading_adjective||'', b.trailing_adjective||'']}
|
1126
|
-
bound_concepts_by_name = bindings_by_name.map{|b| b.concept}
|
1127
|
-
reading = nil
|
1128
|
-
first_role = nil
|
1129
|
-
debug :reading, "Looking for existing fact type to match #{phrases.inspect}" do
|
1130
|
-
first_role =
|
1131
|
-
bindings[0].concept.all_role.detect do |role|
|
1132
|
-
next if role.fact_type.all_role.size != bindings.size # Wrong arity
|
1133
|
-
concepts = role.fact_type.all_role.map{|r| r.concept }
|
1134
|
-
next unless bound_concepts_by_name == concepts.sort_by{|c| c.name} # Wrong players
|
1135
|
-
matching_reading =
|
1136
|
-
role.fact_type.all_reading.detect do |reading|
|
1137
|
-
debug :reading, "Considering #{reading.expand}"
|
1138
|
-
reading_role_refs = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}
|
1139
|
-
reading_concepts = reading_role_refs.map{|rr| rr.role.concept}
|
1140
|
-
elements = reading.text.scan(/\{[0-9]+\}|\w+/)
|
1141
|
-
next false if elements.zip(phrases).detect do |element, phrase|
|
1142
|
-
if element =~ /\A\{([0-9]+)\}\Z/ # Must be a role player; need a matching binding
|
1143
|
-
!phrase.is_a?(Hash) or
|
1144
|
-
!(binding = phrase[:binding]) or
|
1145
|
-
!(role_ref = reading_role_refs[$1.to_i]) or # If we fail here, it's an error!
|
1146
|
-
role_ref.role.concept != binding.concept or
|
1147
|
-
=begin
|
1148
|
-
# REVISIT: This loose matching fails on the Metamodel with RingConstraints.
|
1149
|
-
# Need "best match" semantics, or some way to know that these adjectives are "extra" to the readings.
|
1150
|
-
(la = role_ref.leading_adjective) && binding[:leading_adjective] != la or
|
1151
|
-
(ta = role_ref.trailing_adjective) && binding[:trailing_adjective] != ta
|
1152
|
-
=end
|
1153
|
-
role_ref.leading_adjective != binding[:leading_adjective] or
|
1154
|
-
role_ref.trailing_adjective != binding[:trailing_adjective]
|
1155
|
-
else
|
1156
|
-
element != phrase
|
1157
|
-
end
|
1158
|
-
end
|
1159
|
-
debug :reading, "'#{reading.expand}' matches!"
|
1160
|
-
true # There was no mismatch
|
1161
|
-
end
|
1162
|
-
matching_reading # This role was in a matching fact type!
|
1163
|
-
end
|
1164
|
-
end
|
1165
|
-
|
1166
|
-
if first_role
|
1167
|
-
fact_type = first_role.fact_type
|
1168
|
-
|
1169
|
-
# Remember the roles for each binding, for subsequent readings:
|
1170
|
-
reading.role_sequence.all_role_ref.each_with_index do |rr, index|
|
1171
|
-
@symbols.roles_by_binding[bindings[index]] = rr.role
|
1172
|
-
end
|
1173
|
-
|
1174
|
-
return [fact_type, reading]
|
1175
|
-
end
|
1176
|
-
end
|
1177
|
-
|
1178
|
-
fact_type ||= @constellation.FactType(:new)
|
1179
|
-
|
1180
|
-
# Create the roles on the first reading, or look them up on subsequent readings.
|
1181
|
-
# If the player occurs twice, we must find one with matching adjectives.
|
1182
|
-
|
1183
|
-
role_sequence = @constellation.RoleSequence(:new) # RoleSequence for RoleRefs of this reading
|
1184
|
-
roles = []
|
1185
|
-
role_phrases.each_with_index do |role_phrase, index|
|
1186
|
-
binding = role_phrase[:binding]
|
1187
|
-
role_name = role_phrase[:role_name]
|
1188
|
-
player = binding.concept
|
1189
|
-
role = nil
|
1190
|
-
if (fact_type.all_reading.size == 0) # First reading
|
1191
|
-
# Assert this role of the fact type:
|
1192
|
-
role = @constellation.Role(fact_type, fact_type.all_role.size, :concept => player)
|
1193
|
-
role.role_name = role_name if role_name
|
1194
|
-
debug "Concept #{player.name} found, created role #{role.describe} by binding #{binding.inspect}"
|
1195
|
-
@symbols.roles_by_binding[binding] = role
|
1196
|
-
else # Subsequent readings
|
1197
|
-
#debug "Looking for role #{binding.inspect} in bindings #{@symbols.roles_by_binding.inspect}"
|
1198
|
-
role = @symbols.roles_by_binding[binding]
|
1199
|
-
raise "Role #{binding.inspect} not found in prior readings" if !role
|
1200
|
-
player = role.concept
|
1201
|
-
end
|
1202
|
-
|
1203
|
-
# Save a role value restriction
|
1204
|
-
if (ranges = role_phrase[:restriction])
|
1205
|
-
role.role_value_restriction = value_restriction(ranges, role_phrase[:restriction_enforcement])
|
1206
|
-
end
|
1207
|
-
|
1208
|
-
roles << role
|
1209
|
-
|
1210
|
-
# Create the RoleRefs for the RoleSequence
|
1211
|
-
|
1212
|
-
role_ref = @constellation.RoleRef(role_sequence, index, :role => roles[index])
|
1213
|
-
leading_adjective = role_phrase[:leading_adjective]
|
1214
|
-
role_ref.leading_adjective = leading_adjective if leading_adjective
|
1215
|
-
trailing_adjective = role_phrase[:trailing_adjective]
|
1216
|
-
role_ref.trailing_adjective = trailing_adjective if trailing_adjective
|
1217
|
-
end
|
1218
|
-
|
1219
|
-
# Create any embedded constraints:
|
1220
|
-
debug "Creating embedded presence constraints for #{fact_type.describe}" do
|
1221
|
-
create_embedded_presence_constraints(fact_type, role_phrases, roles)
|
1222
|
-
end
|
1223
|
-
|
1224
|
-
process_qualifiers(role_sequence, qualifiers)
|
1225
|
-
|
1226
|
-
# Save the first role sequence to be used for a default PresenceConstraint
|
1227
|
-
add_reading(fact_type, role_sequence, phrases)
|
1228
|
-
end
|
1229
|
-
[fact_type, reading]
|
1230
|
-
end
|
1231
|
-
|
1232
|
-
def fact_type_identification(fact_type, name, prefer)
|
1233
|
-
if !@symbols.embedded_presence_constraints.detect{|pc| pc.max_frequency == 1}
|
1234
|
-
# Provide a default identifier for a fact type that's lacking one (over all roles):
|
1235
|
-
first_role_sequence = fact_type.preferred_reading.role_sequence
|
1236
|
-
#puts "Creating PC for #{name}: #{fact_type.describe}"
|
1237
|
-
identifier = @constellation.PresenceConstraint(
|
1238
|
-
:new,
|
1239
|
-
:vocabulary => @vocabulary,
|
1240
|
-
:name => "#{name}PK", # Is this a useful name?
|
1241
|
-
:role_sequence => first_role_sequence,
|
1242
|
-
:is_preferred_identifier => prefer,
|
1243
|
-
:max_frequency => 1 # Unique
|
1244
|
-
)
|
1245
|
-
# REVISIT: The UC might be provided later as an external constraint, relax this rule:
|
1246
|
-
#raise "'#{fact_type.default_reading}': non-unary fact types having no uniqueness constraints must be objectified (named)" unless fact_type.entity_type
|
1247
|
-
debug "Made default fact type identifier #{identifier.object_id} over #{first_role_sequence.describe} in #{fact_type.describe}"
|
1248
|
-
elsif prefer
|
1249
|
-
#debug "Made fact type identifier #{identifier.object_id} preferred over #{@symbols.embedded_presence_constraints[0].role_sequence.describe} in #{fact_type.describe}"
|
1250
|
-
@symbols.embedded_presence_constraints[0].is_preferred_identifier = true
|
1251
|
-
end
|
1252
|
-
end
|
1253
|
-
|
1254
|
-
# Categorise the fact type clauses according to the set of role player names
|
1255
|
-
# Return an array where each element is an array of clauses, the clauses having
|
1256
|
-
# matching players, and otherwise preserving the order of definition.
|
1257
|
-
def clauses_by_fact_type(clauses)
|
1258
|
-
clause_group_by_role_players = {}
|
1259
|
-
clauses.inject([]) do |clause_groups, clause|
|
1260
|
-
type, qualifiers, phrases, context = *clause
|
1261
|
-
|
1262
|
-
debug "Clause: #{clause.inspect}"
|
1263
|
-
roles = phrases.map do |phrase|
|
1264
|
-
Hash === phrase ? phrase[:binding] : nil
|
1265
|
-
end.compact
|
1266
|
-
|
1267
|
-
# Look for an existing clause group involving these players, or make one:
|
1268
|
-
clause_group = clause_group_by_role_players[key = roles.sort]
|
1269
|
-
if clause_group # Another clause for an existing clause group
|
1270
|
-
clause_group << clause
|
1271
|
-
else # A new clause group
|
1272
|
-
clause_groups << (clause_group_by_role_players[key] = [clause])
|
1273
|
-
end
|
1274
|
-
clause_groups
|
1275
|
-
end
|
1276
|
-
end
|
1277
|
-
|
1278
|
-
# For each fact reading there may be embedded mandatory, uniqueness or frequency constraints:
|
1279
|
-
def create_embedded_presence_constraints(fact_type, role_phrases, roles)
|
1280
|
-
embedded_presence_constraints = []
|
1281
|
-
role_phrases.zip(roles).each_with_index do |role_pair, index|
|
1282
|
-
role_phrase, role = *role_pair
|
1283
|
-
|
1284
|
-
next unless quantifier = role_phrase[:quantifier]
|
1285
|
-
|
1286
|
-
debug "Processing embedded constraint #{quantifier.inspect} on #{role.concept.name} in #{fact_type.describe}" do
|
1287
|
-
constrained_roles = roles.clone
|
1288
|
-
constrained_roles.delete_at(index)
|
1289
|
-
constraint = find_pc_over_roles(constrained_roles)
|
1290
|
-
if constraint
|
1291
|
-
debug "Setting max frequency to #{quantifier[1]} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}"
|
1292
|
-
raise "Conflicting maximum frequency for constraint" if constraint.max_frequency && constraint.max_frequency != quantifier[1]
|
1293
|
-
constraint.max_frequency = quantifier[1]
|
1294
|
-
else
|
1295
|
-
role_sequence = @constellation.RoleSequence(:new)
|
1296
|
-
constrained_roles.each_with_index do |constrained_role, i|
|
1297
|
-
role_ref = @constellation.RoleRef(role_sequence, i, :role => constrained_role)
|
1298
|
-
end
|
1299
|
-
constraint = @constellation.PresenceConstraint(
|
1300
|
-
:new,
|
1301
|
-
:vocabulary => @vocabulary,
|
1302
|
-
:role_sequence => role_sequence,
|
1303
|
-
:is_mandatory => quantifier[0] && quantifier[0] > 0, # REVISIT: Check "maybe" qualifier?
|
1304
|
-
:max_frequency => quantifier[1],
|
1305
|
-
:min_frequency => quantifier[0]
|
1306
|
-
)
|
1307
|
-
embedded_presence_constraints << constraint
|
1308
|
-
debug "Made new PC min=#{quantifier[0].inspect} max=#{quantifier[1].inspect} constraint #{constraint.object_id} over #{(e = fact_type.entity_type) ? e.name : role_sequence.describe} in #{fact_type.describe}"
|
1309
|
-
end
|
1310
|
-
end
|
1311
|
-
end
|
1312
|
-
@symbols.embedded_presence_constraints += embedded_presence_constraints
|
1313
|
-
end
|
1314
|
-
|
1315
|
-
def process_qualifiers(role_sequence, qualifiers)
|
1316
|
-
return unless qualifiers.size > 0
|
1317
|
-
qualifiers.sort!
|
1318
|
-
|
1319
|
-
# Process the ring constraints:
|
1320
|
-
ring_constraints, qualifiers = qualifiers.partition{|q| RingTypes.include?(q) }
|
1321
|
-
unless ring_constraints.empty?
|
1322
|
-
# A Ring may be over a supertype/subtype pair, and this won't find that.
|
1323
|
-
role_refs = Array(role_sequence.all_role_ref)
|
1324
|
-
role_pairs = []
|
1325
|
-
player_supertypes_by_role = role_refs.map{|rr|
|
1326
|
-
concept = rr.role.concept
|
1327
|
-
concept.is_a?(ActiveFacts::Metamodel::EntityType) ? supertypes(concept) : [concept]
|
1328
|
-
}
|
1329
|
-
role_refs.each_with_index{|rr1, i|
|
1330
|
-
player1 = rr1.role.concept
|
1331
|
-
(i+1...role_refs.size).each{|j|
|
1332
|
-
rr2 = role_refs[j]
|
1333
|
-
player2 = rr2.role.concept
|
1334
|
-
if player_supertypes_by_role[i] - player_supertypes_by_role[j] != player_supertypes_by_role[i]
|
1335
|
-
role_pairs << [rr1.role, rr2.role]
|
1336
|
-
end
|
1337
|
-
}
|
1338
|
-
}
|
1339
|
-
raise "ring constraint (#{ring_constraints*" "}) role pair not found" if role_pairs.size == 0
|
1340
|
-
raise "ring constraint (#{ring_constraints*" "}) is ambiguous over roles of #{role_pairs.map{|rp| rp.map{|r| r.concept.name}}.inspect}" if role_pairs.size > 1
|
1341
|
-
roles = role_pairs[0]
|
1342
|
-
|
1343
|
-
# Ensure that the keys in RingPairs follow others:
|
1344
|
-
ring_constraints = ring_constraints.partition{|rc| !RingPairs.keys.include?(rc.downcase.to_sym) }.flatten
|
1345
|
-
|
1346
|
-
if ring_constraints.size > 1 and !RingPairs[ring_constraints[-1].to_sym].include?(ring_constraints[0].to_sym)
|
1347
|
-
raise "incompatible ring constraint types (#{ring_constraints*", "})"
|
1348
|
-
end
|
1349
|
-
ring_type = ring_constraints.map{|c| c.capitalize}*""
|
1350
|
-
|
1351
|
-
ring = @constellation.RingConstraint(
|
1352
|
-
:new,
|
1353
|
-
:vocabulary => @vocabulary,
|
1354
|
-
# :name => name, # REVISIT: Create a name for Ring Constraints?
|
1355
|
-
:role => roles[0],
|
1356
|
-
:other_role => roles[1],
|
1357
|
-
:ring_type => ring_type
|
1358
|
-
)
|
1359
|
-
|
1360
|
-
debug "Added #{ring.verbalise} #{ring.class.roles.keys.map{|k|"#{k} => "+ring.send(k).verbalise}*", "}"
|
1361
|
-
end
|
1362
|
-
|
1363
|
-
return unless qualifiers.size > 0
|
1364
|
-
|
1365
|
-
# Process the remaining qualifiers:
|
1366
|
-
puts "REVISIT: Qualifiers #{qualifiers.inspect} over #{role_sequence.describe}"
|
1367
|
-
end
|
1368
|
-
|
1369
|
-
def find_pc_over_roles(roles)
|
1370
|
-
return nil if roles.size == 0 # Safeguard; this would chuck an exception otherwise
|
1371
|
-
roles[0].all_role_ref.each do |role_ref|
|
1372
|
-
next if role_ref.role_sequence.all_role_ref.map(&:role) != roles
|
1373
|
-
pc = role_ref.role_sequence.all_presence_constraint.single # Will return nil if there's more than one.
|
1374
|
-
#puts "Existing PresenceConstraint matches those roles!" if pc
|
1375
|
-
return pc if pc
|
1376
|
-
end
|
1377
|
-
nil
|
1378
|
-
end
|
1379
|
-
|
1380
|
-
def add_reading(fact_type, role_sequence, phrases)
|
1381
|
-
ordinal = (fact_type.all_reading.map(&:ordinal).max||-1) + 1 # Use the next unused ordinal
|
1382
|
-
reading = @constellation.Reading(fact_type, ordinal, :role_sequence => role_sequence)
|
1383
|
-
role_num = -1
|
1384
|
-
reading.text = phrases.map {|phrase|
|
1385
|
-
Hash === phrase ? "{#{role_num += 1}}" : phrase
|
1386
|
-
}*" "
|
1387
|
-
raise "Wrong number of players (#{role_num+1}) found in reading #{reading.text} over #{fact_type.describe}" if role_num+1 != fact_type.all_role.size
|
1388
|
-
debug "Added reading #{reading.text}"
|
1389
|
-
reading
|
1390
|
-
end
|
1391
|
-
|
1392
|
-
# Return an array of this entity type and all its supertypes, transitively:
|
1393
|
-
def supertypes(o)
|
1394
|
-
([o] + o.all_supertype_inheritance.map{|ti| supertypes(ti.supertype)}.flatten).uniq
|
1395
|
-
end
|
1396
|
-
|
1397
|
-
def concept_by_name(name)
|
1398
|
-
player = @constellation.Concept[[@vocabulary.identifying_role_values, name]]
|
1399
|
-
|
1400
|
-
# REVISIT: Hack to allow facts to refer to standard types that will be imported from standard vocabulary:
|
1401
|
-
if !player && %w{Date DateAndTime Time}.include?(name)
|
1402
|
-
player = @constellation.ValueType(@vocabulary.identifying_role_values, name)
|
1403
|
-
end
|
1404
|
-
|
1405
|
-
if (!player && @symbols.allowed_forward[name])
|
1406
|
-
player = @constellation.EntityType(@vocabulary, name)
|
1407
|
-
end
|
1408
|
-
player
|
1409
|
-
end
|
1410
|
-
|
1411
|
-
class SymbolTable #:nodoc:all
|
1412
|
-
# Externally built tables used in this binding context:
|
1413
|
-
attr_reader :roles_by_binding
|
1414
|
-
attr_accessor :embedded_presence_constraints
|
1415
|
-
attr_accessor :allowed_forward
|
1416
|
-
attr_reader :constellation
|
1417
|
-
attr_reader :vocabulary
|
1418
|
-
attr_reader :bindings_by_concept
|
1419
|
-
attr_reader :role_names
|
1420
|
-
|
1421
|
-
# A Binding here is a form of reference to a concept, being a name and optional adjectives, possibly designated by a role name:
|
1422
|
-
Binding = Struct.new("Binding", :concept, :name, :leading_adjective, :trailing_adjective, :role_name)
|
1423
|
-
class Binding
|
1424
|
-
def inspect
|
1425
|
-
"Binding(#{concept.class.basename} #{concept.name}, #{[leading_adjective, name, trailing_adjective].compact*"-"}#{role_name ? " (as #{role_name})" : ""})"
|
1426
|
-
end
|
1427
|
-
|
1428
|
-
# Any ordering works to allow a hash to be keyed by a set (unordered array) of Bindings:
|
1429
|
-
def <=>(other)
|
1430
|
-
object_id <=> other.object_id
|
1431
|
-
end
|
1432
|
-
end
|
1433
|
-
|
1434
|
-
def initialize(constellation, vocabulary)
|
1435
|
-
@constellation = constellation
|
1436
|
-
@vocabulary = vocabulary
|
1437
|
-
@bindings_by_concept = Hash.new {|h, k| h[k] = [] } # Indexed by Binding#name, maybe multiple entries for each name
|
1438
|
-
@role_names = {}
|
1439
|
-
|
1440
|
-
@embedded_presence_constraints = []
|
1441
|
-
@roles_by_binding = {} # Build a hash of allowed bindings on first reading (check against it on subsequent ones)
|
1442
|
-
@allowed_forward = {} # No roles may be forward-referenced
|
1443
|
-
end
|
1444
|
-
|
1445
|
-
#
|
1446
|
-
# This method is the guts of role matching.
|
1447
|
-
# "words" may be a single word (and then the adjectives may also be used) or two words.
|
1448
|
-
# In either case a word is expected to be a defined concept or role name.
|
1449
|
-
# If a role_name is provided here, that's a *definition* and will only be accepted if legal
|
1450
|
-
# If allowed_forward is true, words is a single word and is not defined, create a forward Entity
|
1451
|
-
# If leading_speculative or trailing_speculative is true, the adjectives may not apply. If they do apply, use them.
|
1452
|
-
# If loose_binding_except is true, it's a hash containing names that may *not* be loose-bound... else none may.
|
1453
|
-
#
|
1454
|
-
# Loose binding is when a word without an adjective matches a role with, or vice verse.
|
1455
|
-
#
|
1456
|
-
def bind(words, leading_adjective = nil, trailing_adjective = nil, role_name = nil, allowed_forward = false, leading_speculative = false, trailing_speculative = false, loose_binding_except = nil)
|
1457
|
-
words = Array(words)
|
1458
|
-
if (words.size > 2 or words.size == 2 && (leading_adjective or trailing_adjective or allowed_forward))
|
1459
|
-
raise "role has too many adjectives '#{[leading_adjective, words, trailing_adjective].flatten.compact*" "}'"
|
1460
|
-
end
|
1461
|
-
|
1462
|
-
# Check for use of a role name, valid if they haven't used any adjectives or tried to define a role_name:
|
1463
|
-
binding = @role_names[words[0]]
|
1464
|
-
if binding && words.size == 1 # If ok, this is it.
|
1465
|
-
raise "May not use existing role name '#{words[0]}' to define a new role name" if role_name
|
1466
|
-
if (leading_adjective && !leading_speculative) || (trailing_adjective && !trailing_speculative)
|
1467
|
-
raise "May not use existing role name '#{words[0]}' with adjectives"
|
1468
|
-
end
|
1469
|
-
return binding.concept, binding
|
1470
|
-
end
|
1471
|
-
|
1472
|
-
# Look for an existing definition
|
1473
|
-
# If we have more than one word that might be the concept name, find which it is:
|
1474
|
-
words.each do |w|
|
1475
|
-
# Find the existing defined binding that matches this one:
|
1476
|
-
bindings = @bindings_by_concept[w]
|
1477
|
-
best_match = nil
|
1478
|
-
matched_adjectives = 0
|
1479
|
-
bindings.each do |binding|
|
1480
|
-
# Adjectives defined on the binding must be matched unless loose binding is allowed.
|
1481
|
-
loose_ok = loose_binding_except and !loose_binding_except[binding.concept.name]
|
1482
|
-
|
1483
|
-
# Don't allow binding a new role name to an existing one:
|
1484
|
-
next if role_name and role_name != binding.role_name
|
1485
|
-
|
1486
|
-
quality = 0
|
1487
|
-
if binding.leading_adjective != leading_adjective
|
1488
|
-
next if binding.leading_adjective && leading_adjective # Both set, but different
|
1489
|
-
next if !loose_ok && (!leading_speculative || !leading_adjective)
|
1490
|
-
quality += 1
|
1491
|
-
end
|
1492
|
-
|
1493
|
-
if binding.trailing_adjective != trailing_adjective
|
1494
|
-
next if binding.trailing_adjective && trailing_adjective # Both set, but different
|
1495
|
-
next if !loose_ok && (!trailing_speculative || !trailing_adjective)
|
1496
|
-
quality += 1
|
1497
|
-
end
|
1498
|
-
|
1499
|
-
quality += 1 unless binding.role_name # A role name that was not matched... better if there wasn't one
|
1500
|
-
|
1501
|
-
if (quality > matched_adjectives || !best_match)
|
1502
|
-
best_match = binding # A better match than we had before
|
1503
|
-
matched_adjectives = quality
|
1504
|
-
break unless loose_ok || leading_speculative || trailing_speculative
|
1505
|
-
end
|
1506
|
-
end
|
1507
|
-
|
1508
|
-
if best_match
|
1509
|
-
# We've found the best existing definition
|
1510
|
-
|
1511
|
-
# Indicate which speculative adjectives were used so the clauses can be deleted:
|
1512
|
-
leading_adjective.replace("") if best_match.leading_adjective and leading_adjective and leading_speculative
|
1513
|
-
trailing_adjective.replace("") if best_match.trailing_adjective and trailing_adjective and trailing_speculative
|
1514
|
-
|
1515
|
-
return best_match.concept, best_match
|
1516
|
-
end
|
1517
|
-
|
1518
|
-
# No existing defined binding. Look up an existing concept of this name:
|
1519
|
-
player = concept(w, allowed_forward)
|
1520
|
-
next unless player
|
1521
|
-
|
1522
|
-
# Found a new binding for this player, save it.
|
1523
|
-
|
1524
|
-
# Check that a trailing adjective isn't an existing role name or concept:
|
1525
|
-
trailing_word = words[1] if w == words[0]
|
1526
|
-
if trailing_word
|
1527
|
-
raise "May not use existing role name '#{trailing_word}' with a new name or with adjectives" if @role_names[trailing_word]
|
1528
|
-
raise "ambiguous concept reference #{words*" '"}'" if concept(trailing_word)
|
1529
|
-
end
|
1530
|
-
leading_word = words[0] if w != words[0]
|
1531
|
-
|
1532
|
-
raise "may not redefine existing concept '#{role_name}' as a role name" if role_name and concept(role_name)
|
1533
|
-
|
1534
|
-
binding = Binding.new(
|
1535
|
-
player,
|
1536
|
-
w,
|
1537
|
-
(!leading_speculative && leading_adjective) || leading_word,
|
1538
|
-
(!trailing_speculative && trailing_adjective) || trailing_word,
|
1539
|
-
role_name
|
1540
|
-
)
|
1541
|
-
@bindings_by_concept[binding.name] << binding
|
1542
|
-
@role_names[binding.role_name] = binding if role_name
|
1543
|
-
return binding.concept, binding
|
1544
|
-
end
|
1545
|
-
|
1546
|
-
# Not found.
|
1547
|
-
return nil
|
1548
|
-
end
|
1549
|
-
|
1550
|
-
# return the EntityType or ValueType this name refers to:
|
1551
|
-
def concept(name, allowed_forward = false)
|
1552
|
-
# See if the name is a defined concept in this vocabulary:
|
1553
|
-
player = @constellation.Concept[[virv = @vocabulary.identifying_role_values, name]]
|
1554
|
-
|
1555
|
-
# REVISIT: Hack to allow facts to refer to standard types that will be imported from standard vocabulary:
|
1556
|
-
if !player && %w{Date DateAndTime Time}.include?(name)
|
1557
|
-
player = @constellation.ValueType(virv, name)
|
1558
|
-
end
|
1559
|
-
|
1560
|
-
if !player && allowed_forward
|
1561
|
-
player = @constellation.EntityType(@vocabulary, name)
|
1562
|
-
end
|
1563
|
-
|
1564
|
-
player
|
1565
|
-
end
|
1566
|
-
|
1567
|
-
def bind_roles_in_clauses(clauses, identification = [])
|
1568
|
-
identification ||= []
|
1569
|
-
bind_roles_in_phrases_list(
|
1570
|
-
clauses.map{|clause| clause[2]}, # Extract the phrases
|
1571
|
-
single_word_identifiers = identification.map{|i| i.size == 1 ? i[0] : nil}.compact.uniq
|
1572
|
-
)
|
1573
|
-
end
|
1574
|
-
|
1575
|
-
#
|
1576
|
-
# Walk through all phrases identifying role players.
|
1577
|
-
# Each role player phrase gets a :binding key added to it.
|
1578
|
-
#
|
1579
|
-
# Any adjectives that the parser didn't recognise are merged with their players here,
|
1580
|
-
# as long as they're indicated as adjectives of that player somewhere in the readings.
|
1581
|
-
#
|
1582
|
-
# Other words are turned from phrases (hashes) into simple strings.
|
1583
|
-
#
|
1584
|
-
def bind_roles_in_phrases_list(phrases_list, allowed_forwards = [])
|
1585
|
-
disallow_loose_binding = allowed_forwards.inject({}) { |h, v| h[v] = true; h }
|
1586
|
-
phrases_list.each do |phrases|
|
1587
|
-
debug :bind, "Binding phrases"
|
1588
|
-
|
1589
|
-
phrase_numbers_used_speculatively = []
|
1590
|
-
disallow_loose_binding_this_reading = disallow_loose_binding.clone
|
1591
|
-
phrases.each_with_index do |phrase, index|
|
1592
|
-
la = phrase[:leading_adjective]
|
1593
|
-
player_name = phrase[:word]
|
1594
|
-
ta = phrase[:trailing_adjective]
|
1595
|
-
role_name = phrase[:role_name]
|
1596
|
-
|
1597
|
-
# We use the preceeding phrase and/or following phrase speculatively if they're simple words:
|
1598
|
-
preceeding_phrase = nil
|
1599
|
-
following_phrase = nil
|
1600
|
-
if !la && index > 0 && (preceeding_phrase = phrases[index-1])
|
1601
|
-
preceeding_phrase = nil unless String === preceeding_phrase || preceeding_phrase.keys == [:word]
|
1602
|
-
la = preceeding_phrase[:word] if Hash === preceeding_phrase
|
1603
|
-
end
|
1604
|
-
if !ta && (following_phrase = phrases[index+1])
|
1605
|
-
following_phrase = nil unless following_phrase.keys == [:word]
|
1606
|
-
ta = following_phrase[:word] if following_phrase
|
1607
|
-
end
|
1608
|
-
|
1609
|
-
# If the identification includes this player name as a single word, it's allowed to be forward referenced:
|
1610
|
-
allowed_forward = allowed_forwards.include?(player_name)
|
1611
|
-
|
1612
|
-
debug :bind, "Binding a role: #{[player_name, la, ta, role_name, allowed_forward, !!preceeding_phrase, !!following_phrase].inspect}"
|
1613
|
-
player, binding = bind(
|
1614
|
-
player_name,
|
1615
|
-
la, ta,
|
1616
|
-
role_name,
|
1617
|
-
allowed_forward,
|
1618
|
-
!!preceeding_phrase, !!following_phrase,
|
1619
|
-
phrases == phrases_list[0] ? nil : disallow_loose_binding_this_reading # Never allow loose binding on the first reading
|
1620
|
-
)
|
1621
|
-
disallow_loose_binding_this_reading[player.name] = true if player
|
1622
|
-
|
1623
|
-
# Arrange to delete the speculative adjectives that were used:
|
1624
|
-
if preceeding_phrase && preceeding_phrase[:word] == ""
|
1625
|
-
debug :bind, "binding consumed a speculative leading_adjective #{la}"
|
1626
|
-
# The numbers are adjusted to allow for prior deletions.
|
1627
|
-
phrase_numbers_used_speculatively << index-1-phrase_numbers_used_speculatively.size
|
1628
|
-
end
|
1629
|
-
if following_phrase && following_phrase[:word] == ""
|
1630
|
-
debug :bind, "binding consumed a speculative trailing_adjective #{ta}"
|
1631
|
-
phrase_numbers_used_speculatively << index+1-phrase_numbers_used_speculatively.size
|
1632
|
-
end
|
1633
|
-
|
1634
|
-
if player
|
1635
|
-
# Replace the words used to identify the role by a reference to the role itself,
|
1636
|
-
# leaving :quantifier, :function, :restriction and :literal intact
|
1637
|
-
phrase[:binding] = binding
|
1638
|
-
binding
|
1639
|
-
else
|
1640
|
-
raise "Internal error; role #{phrase.inspect} not matched" unless phrase.keys == [:word]
|
1641
|
-
# Just a linking word
|
1642
|
-
phrases[index] = phrase[:word]
|
1643
|
-
end
|
1644
|
-
debug :bind, "Bound phrase: #{phrase.inspect}" + " -> " + (player ? player.name+", "+binding.inspect : phrase[:word].inspect)
|
1645
|
-
|
1646
|
-
end
|
1647
|
-
phrase_numbers_used_speculatively.each do |index|
|
1648
|
-
phrases.delete_at(index)
|
1649
|
-
end
|
1650
|
-
debug :bind, "Bound phrases: #{phrases.inspect}"
|
1651
|
-
end
|
1652
|
-
end
|
1653
|
-
end # of SymbolTable class
|
1654
|
-
|
1655
|
-
# The Context manages some key information revealed or needed during parsing
|
1656
|
-
class Context
|
1657
|
-
# REVISIT; This class is "work in progress", supporting semantic predicates from Treetop.
|
1658
|
-
def initialize(compiler)
|
1659
|
-
@compiler = compiler
|
1660
|
-
@vocabularies = {}
|
1661
|
-
end
|
1662
|
-
|
1663
|
-
def vocabulary(v)
|
1664
|
-
# puts "Parser has started work on vocabulary #{v}"
|
1665
|
-
@vocabularies[v] = {}
|
1666
|
-
@terms = {}
|
1667
|
-
true
|
1668
|
-
end
|
1669
|
-
|
1670
|
-
def entity_type(c)
|
1671
|
-
#puts "Parser has started work on entity_type '#{c}'"
|
1672
|
-
@terms[c] = true
|
1673
|
-
true
|
1674
|
-
end
|
1675
|
-
|
1676
|
-
def value_type(c)
|
1677
|
-
#puts "Parser has started work on value_type '#{c}'"
|
1678
|
-
@terms[c] = true
|
1679
|
-
true
|
1680
|
-
end
|
1681
|
-
|
1682
|
-
def objectified_fact_type(c)
|
1683
|
-
#puts "Parser has started work on objectified_fact_type '#{c}'"
|
1684
|
-
@terms[c] = true
|
1685
|
-
true
|
1686
|
-
end
|
1687
|
-
|
1688
|
-
def reset_role_names
|
1689
|
-
# puts "\tresetting role names #{@role_names.keys.sort*", "}" if @role_names && @role_names.size > 0
|
1690
|
-
@role_names = {}
|
1691
|
-
true
|
1692
|
-
end
|
1693
|
-
|
1694
|
-
def role_name(r)
|
1695
|
-
#puts "\tadding role name '#{r}'"
|
1696
|
-
@role_names[r] = true
|
1697
|
-
true
|
1698
|
-
end
|
1699
|
-
|
1700
|
-
def term?(t)
|
1701
|
-
#puts "term?(#{t})"
|
1702
|
-
false
|
1703
|
-
end
|
1704
|
-
|
1705
|
-
def global_term?(t)
|
1706
|
-
#puts "global_term?(#{t})"
|
1707
|
-
false
|
1708
|
-
end
|
1709
|
-
|
1710
|
-
def term_starts(s)
|
1711
|
-
#p s
|
1712
|
-
#debugger
|
1713
|
-
true
|
1714
|
-
end
|
1715
|
-
|
1716
|
-
def term_continues(s)
|
1717
|
-
#p s
|
1718
|
-
#debugger
|
1719
|
-
true
|
1720
|
-
end
|
71
|
+
def unit? s
|
72
|
+
name = @constellation.Name[s]
|
73
|
+
units = !name ? [] : name.all_unit.to_a + name.all_unit_as_plural_name.to_a
|
74
|
+
debug :units, "Looking for unit #{s}, got #{units.map{|u|u.name}.inspect}"
|
75
|
+
units.size > 0
|
1721
76
|
end
|
1722
77
|
|
1723
78
|
end
|