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
@@ -143,13 +143,13 @@ module ActiveFacts
|
|
143
143
|
# What's the underlying SQL data type of this column?
|
144
144
|
def type
|
145
145
|
params = {}
|
146
|
-
|
147
|
-
return ["BIT", params,
|
146
|
+
constraints = []
|
147
|
+
return ["BIT", params, constraints] if references[-1].is_unary # It's a unary
|
148
148
|
|
149
|
-
# Add a role value
|
150
|
-
# REVISIT: Can add join-role-value-
|
151
|
-
if references[-1].to_role && references[-1].to_role.
|
152
|
-
|
149
|
+
# Add a role value constraint
|
150
|
+
# REVISIT: Can add join-role-value-constraints here, if we ever provide a way to define them
|
151
|
+
if references[-1].to_role && references[-1].to_role.role_value_constraint
|
152
|
+
constraints << references[-1].to_role.role_value_constraint
|
153
153
|
end
|
154
154
|
|
155
155
|
vt = references[-1].is_self_value ? references[-1].from : references[-1].to
|
@@ -158,10 +158,10 @@ module ActiveFacts
|
|
158
158
|
while vt.supertype
|
159
159
|
params[:length] ||= vt.length if vt.length.to_i != 0
|
160
160
|
params[:scale] ||= vt.scale if vt.scale.to_i != 0
|
161
|
-
|
161
|
+
constraints << vt.value_constraint if vt.value_constraint
|
162
162
|
vt = vt.supertype
|
163
163
|
end
|
164
|
-
return [vt.name, params,
|
164
|
+
return [vt.name, params, constraints]
|
165
165
|
end
|
166
166
|
|
167
167
|
# The comment is the readings from the References expressed as a join
|
@@ -327,11 +327,15 @@ module ActiveFacts
|
|
327
327
|
debug :columns, "All Columns for #{name}" do
|
328
328
|
columns = []
|
329
329
|
sups = supertypes
|
330
|
+
pi_roles = preferred_identifier.role_sequence.all_role_ref.map{|rr| rr.role}
|
330
331
|
references_from.sort_by do |ref|
|
331
|
-
# Put supertypes first, in order, then non-subtype references, then subtypes
|
332
|
-
sups.index(ref.to)
|
333
|
-
|
334
|
-
|
332
|
+
# Put supertypes first, in order, then PI roles, non-subtype references by name, then subtypes by name:
|
333
|
+
next [0, p] if p = sups.index(ref.to)
|
334
|
+
if !ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
335
|
+
next [1, p] if p = pi_roles.index(ref.to_role)
|
336
|
+
next [2, ref.to_names]
|
337
|
+
end
|
338
|
+
[3, ref.to_names]
|
335
339
|
end.each do |ref|
|
336
340
|
debug :columns, "Columns absorbed via #{ref}" do
|
337
341
|
if (ref.role_type == :supertype)
|
@@ -35,11 +35,14 @@ module ActiveFacts
|
|
35
35
|
# Return an Array of Reference paths for such absorbed FKs
|
36
36
|
def all_absorbed_foreign_key_reference_path
|
37
37
|
references_from.inject([]) do |array, ref|
|
38
|
-
|
39
38
|
if ref.is_simple_reference
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
if TypeInheritance === ref.fact_type
|
40
|
+
# Ignore references to secondary supertypes, when absorption is through primary.
|
41
|
+
next array if absorbed_via && TypeInheritance === absorbed_via.fact_type
|
42
|
+
# Ignore the case where a subtype is absorbed elsewhere:
|
43
|
+
# REVISIT: Disabled, as this should never happen.
|
44
|
+
# next array if ref.to.absorbed_via != ref.fact_type
|
45
|
+
end
|
43
46
|
array << [ref]
|
44
47
|
elsif ref.is_absorbing
|
45
48
|
ref.to.all_absorbed_foreign_key_reference_path.each{|aref|
|
@@ -40,7 +40,7 @@ module ActiveFacts
|
|
40
40
|
|
41
41
|
# The name that was assigned (perhaps implicitly by NORMA)
|
42
42
|
def real_name
|
43
|
-
@uniqueness_constraint.name && @uniqueness_constraint.name != '' ? @uniqueness_constraint.name : nil
|
43
|
+
@uniqueness_constraint.name && @uniqueness_constraint.name != '' ? @uniqueness_constraint.name.gsub(' ','') : nil
|
44
44
|
end
|
45
45
|
|
46
46
|
# This name is either the name explicitly assigned (if any) or is constructed to form a unique index name.
|
@@ -65,7 +65,7 @@ module ActiveFacts
|
|
65
65
|
|
66
66
|
# The name of a view that can be created to enforce uniqueness over non-null key values
|
67
67
|
def view_name
|
68
|
-
"#{over.name}#{on == over ? "" : "In"+on.name}"
|
68
|
+
"#{over.name.gsub(' ','')}#{on == over ? "" : "In"+on.name.gsub(' ','')}"
|
69
69
|
end
|
70
70
|
|
71
71
|
def to_s #:nodoc:
|
@@ -119,7 +119,6 @@ module ActiveFacts
|
|
119
119
|
ref_path.each do |ref|
|
120
120
|
next unless ref.to_role
|
121
121
|
#debug :index2, "Considering #{ref_path.map(&:to_s)*" and "} yielding columns #{all_column_by_ref_path[ref_path].map{|c| c.name(".")}*", "}"
|
122
|
-
#debugger if name == 'VehicleIncident' && ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
|
123
122
|
ref.to_role.all_role_ref.each do |role_ref|
|
124
123
|
all_pcs = role_ref.role_sequence.all_presence_constraint
|
125
124
|
#puts "pcs over #{ref_path.map{|r| r.to_names}.flatten*"."}: #{role_ref.role_sequence.all_presence_constraint.map(&:describe)*"; "}" if all_pcs.size > 0
|
@@ -173,7 +172,7 @@ module ActiveFacts
|
|
173
172
|
compact.
|
174
173
|
sort_by do |index|
|
175
174
|
# Put the indices in a defined order:
|
176
|
-
index.columns.map(&:name)
|
175
|
+
index.columns.map(&:name)+['', index.over.name]
|
177
176
|
end
|
178
177
|
end
|
179
178
|
end
|
@@ -172,7 +172,7 @@ module ActiveFacts
|
|
172
172
|
|
173
173
|
# The reading for the fact type underlying this Reference
|
174
174
|
def reading
|
175
|
-
is_self_value ? "#{from.name} has value" : @fact_type.default_reading
|
175
|
+
is_self_value ? "#{from.name} has value" : @fact_type.default_reading([], true) # Include role name defn's
|
176
176
|
end
|
177
177
|
|
178
178
|
def inspect #:nodoc:
|
@@ -236,7 +236,7 @@ module ActiveFacts
|
|
236
236
|
|
237
237
|
def populate_references #:nodoc:
|
238
238
|
all_role.each do |role|
|
239
|
-
populate_reference role
|
239
|
+
populate_reference role unless role.fact_type.is_a?(ImplicitFactType)
|
240
240
|
end
|
241
241
|
end
|
242
242
|
|
@@ -305,6 +305,9 @@ module ActiveFacts
|
|
305
305
|
if r.to.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == role}
|
306
306
|
debug :references, "EntityType #{name} identifies EntityType #{r.to.name}, so absorbs it"
|
307
307
|
r.to.absorbed_via = r
|
308
|
+
# We can't be absorbed into our supertype!
|
309
|
+
# REVISIT: We might need to flip all one-to-ones as well
|
310
|
+
r.to.references_to.clone.map{|q|q.flip if q.to_role.role_type == :subtype }
|
308
311
|
r.tabulate
|
309
312
|
return
|
310
313
|
end
|
data/lib/activefacts/support.rb
CHANGED
@@ -10,13 +10,26 @@
|
|
10
10
|
$debug_nested = false
|
11
11
|
$debug_keys = nil
|
12
12
|
$debug_available = {}
|
13
|
+
|
14
|
+
def debug_enabled(args)
|
15
|
+
# Figure out whether this trace is enabled and nests:
|
16
|
+
control = (!args.empty? && Symbol === args[0]) ? args.shift : :all
|
17
|
+
key = control.to_s.sub(/_\Z/, '').to_sym
|
18
|
+
$debug_available[key] ||= key
|
19
|
+
enabled = $debug_nested || $debug_keys[key] || $debug_keys[:all]
|
20
|
+
nesting = control.to_s =~ /_\Z/
|
21
|
+
old_nested = $debug_nested
|
22
|
+
$debug_nested = nesting
|
23
|
+
[(enabled ? 1 : 0), $debug_keys[:all] ? " %-15s"%control : nil, old_nested]
|
24
|
+
end
|
25
|
+
|
13
26
|
def debug(*args, &block)
|
14
27
|
unless $debug_indent
|
15
28
|
# First time, initialise the tracing environment
|
16
29
|
$debug_indent = 0
|
17
30
|
$debug_keys = {}
|
18
31
|
if (e = ENV["DEBUG"])
|
19
|
-
e.split(/[^
|
32
|
+
e.split(/[^_a-zA-Z0-9]/).each{|k| $debug_keys[k.to_sym] = true }
|
20
33
|
if $debug_keys[:help]
|
21
34
|
at_exit {
|
22
35
|
$stderr.puts "---\nDebugging keys available: #{$debug_available.keys.map{|s| s.to_s}.sort*", "}"
|
@@ -25,28 +38,21 @@
|
|
25
38
|
end
|
26
39
|
end
|
27
40
|
|
28
|
-
|
29
|
-
control = (!args.empty? && Symbol === args[0]) ? args.shift : :all
|
30
|
-
key = control.to_s.sub(/_\Z/, '').to_sym
|
31
|
-
$debug_available[key] ||= key
|
32
|
-
enabled = $debug_nested || $debug_keys[key]
|
33
|
-
nesting = control.to_s =~ /_\Z/
|
34
|
-
old_nested = $debug_nested
|
35
|
-
$debug_nested = nesting
|
41
|
+
enabled, show_key, old_nested = debug_enabled(args)
|
36
42
|
|
37
43
|
# Emit the message if enabled or a parent is:
|
38
|
-
puts "
|
44
|
+
puts "\##{show_key} "+" "*$debug_indent + args.join(' ') if args.size > 0 && enabled == 1
|
39
45
|
|
40
46
|
if block
|
41
47
|
begin
|
42
|
-
$debug_indent +=
|
43
|
-
|
48
|
+
$debug_indent += enabled
|
49
|
+
return yield # Return the value of the block
|
44
50
|
ensure
|
45
|
-
$debug_indent -=
|
51
|
+
$debug_indent -= enabled
|
46
52
|
$debug_nesting = old_nested
|
47
53
|
end
|
48
54
|
else
|
49
|
-
r = enabled #
|
55
|
+
r = enabled == 1 # If no block, return whether enabled
|
50
56
|
end
|
51
57
|
r
|
52
58
|
end
|
data/lib/activefacts/version.rb
CHANGED
@@ -23,9 +23,36 @@ module ActiveFacts
|
|
23
23
|
'('+all_role.map{|role| role.describe(highlight) }*", "+')'
|
24
24
|
end
|
25
25
|
|
26
|
-
def default_reading(frequency_constraints = [], define_role_names =
|
26
|
+
def default_reading(frequency_constraints = [], define_role_names = nil)
|
27
27
|
preferred_reading.expand(frequency_constraints, define_role_names)
|
28
28
|
end
|
29
|
+
|
30
|
+
def internal_presence_constraints
|
31
|
+
all_role.map do |r|
|
32
|
+
r.all_role_ref.map do |rr|
|
33
|
+
!rr.role_sequence.all_role_ref.detect{|rr1| rr1.role.fact_type != self } ?
|
34
|
+
rr.role_sequence.all_presence_constraint.to_a :
|
35
|
+
[]
|
36
|
+
end
|
37
|
+
end.flatten.compact.uniq
|
38
|
+
end
|
39
|
+
|
40
|
+
# This entity type has just objectified a fact type. Create the necessary ImplicitFactTypes with phantom roles
|
41
|
+
def create_implicit_fact_type_for_unary
|
42
|
+
role = all_role.single
|
43
|
+
next if role.implicit_fact_type # Already exists
|
44
|
+
# NORMA doesn't create an implicit fact type here, rather the fact type has an implicit extra role, so looks like a binary
|
45
|
+
# We only do it when the unary fact type is not objectified
|
46
|
+
implicit_fact_type = @constellation.ImplicitFactType(:new, :role => role)
|
47
|
+
entity_type = @entity_type || @constellation.ImplicitBooleanValueType(role.concept.vocabulary, "_ImplicitBooleanValueType")
|
48
|
+
phantom_role = @constellation.Role(implicit_fact_type, 0, :concept => entity_type)
|
49
|
+
end
|
50
|
+
|
51
|
+
def reading_preferably_starting_with_role role
|
52
|
+
all_reading_by_ordinal.detect do |reading|
|
53
|
+
reading.text =~ /\{\d\}/ and reading.role_sequence.all_role_ref_in_order[$1.to_i].role == role
|
54
|
+
end || preferred_reading
|
55
|
+
end
|
29
56
|
end
|
30
57
|
|
31
58
|
class Role
|
@@ -60,54 +87,36 @@ module ActiveFacts
|
|
60
87
|
end
|
61
88
|
end
|
62
89
|
|
63
|
-
class Join
|
64
|
-
def column_name(joiner = '-')
|
65
|
-
concept == input_role.concept ? input_role.preferred_reference.role_name(joiner) : Array(concept.name)
|
66
|
-
end
|
67
|
-
|
68
|
-
def describe
|
69
|
-
"#{input_role.fact_type.describe(input_role)}->" +
|
70
|
-
concept.name +
|
71
|
-
(output_role ? "->#{output_role.fact_type.describe(output_role)}":"")
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
90
|
class RoleRef
|
76
91
|
def describe
|
77
|
-
|
78
|
-
all_join.sort_by{|jp| jp.join_step}.map{ |jp| jp.describe + "." }*"" + role_name
|
92
|
+
role_name + (join_node ? " JN#{join_node.ordinal}" : '')
|
79
93
|
end
|
80
94
|
|
81
95
|
def role_name(joiner = "-")
|
82
96
|
name_array =
|
83
97
|
if role.fact_type.all_role.size == 1
|
84
|
-
role.fact_type.
|
98
|
+
if role.fact_type.is_a?(ImplicitFactType)
|
99
|
+
"#{role.concept.name} phantom for #{role.fact_type.role.concept.name}"
|
100
|
+
else
|
101
|
+
role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.split(/\s/)
|
102
|
+
end
|
85
103
|
else
|
86
104
|
role.role_name || [leading_adjective, role.concept.name, trailing_adjective].compact.map{|w| w.split(/\s/)}.flatten
|
87
105
|
end
|
88
106
|
return joiner ? Array(name_array)*joiner : Array(name_array)
|
89
107
|
end
|
90
|
-
|
91
|
-
# Two RoleRefs are equal if they have the same role and Joins with matching roles
|
92
|
-
def ==(role_ref)
|
93
|
-
role_ref.is_a?(ActiveFacts::Metamodel::RoleRef) &&
|
94
|
-
role_ref.role == role &&
|
95
|
-
all_join.size == role_ref.all_join.size &&
|
96
|
-
!all_join.sort_by{|j|j.join_step}.
|
97
|
-
zip(role_ref.all_join.sort_by{|j|j.join_step}).
|
98
|
-
detect{|j1,j2|
|
99
|
-
j1.input_role != j2.input_role ||
|
100
|
-
j1.output_role != j2.output_role
|
101
|
-
}
|
102
|
-
end
|
103
108
|
end
|
104
109
|
|
105
110
|
class RoleSequence
|
106
|
-
def describe
|
111
|
+
def describe(highlighted_role_ref = nil)
|
107
112
|
"("+
|
108
|
-
all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.describe }*", "+
|
113
|
+
all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.describe + (highlighted_role_ref == rr ? '*' : '') }*", "+
|
109
114
|
")"
|
110
115
|
end
|
116
|
+
|
117
|
+
def all_role_ref_in_order
|
118
|
+
all_role_ref.sort_by{|rr| rr.ordinal}
|
119
|
+
end
|
111
120
|
end
|
112
121
|
|
113
122
|
class ValueType
|
@@ -118,6 +127,10 @@ module ActiveFacts
|
|
118
127
|
def subtypes
|
119
128
|
all_value_type_as_supertype
|
120
129
|
end
|
130
|
+
|
131
|
+
def subtypes_transitive
|
132
|
+
[self] + subtypes.map{|st| st.subtypes_transitive}.flatten
|
133
|
+
end
|
121
134
|
end
|
122
135
|
|
123
136
|
class EntityType
|
@@ -268,6 +281,10 @@ module ActiveFacts
|
|
268
281
|
all_type_inheritance_as_supertype.map{|ti| ti.subtype }
|
269
282
|
end
|
270
283
|
|
284
|
+
def subtypes_transitive
|
285
|
+
[self] + subtypes.map{|st| st.subtypes_transitive}.flatten.uniq
|
286
|
+
end
|
287
|
+
|
271
288
|
def all_supertype_inheritance
|
272
289
|
all_type_inheritance_as_subtype.sort_by{|ti|
|
273
290
|
[ti.provides_identification ? 0 : 1, ti.supertype.name]
|
@@ -284,7 +301,6 @@ module ActiveFacts
|
|
284
301
|
# An array of self followed by all supertypes in order:
|
285
302
|
def supertypes_transitive
|
286
303
|
([self] + all_type_inheritance_as_subtype.map{|ti|
|
287
|
-
# debug ti.class.roles.verbalise; exit
|
288
304
|
ti.supertype.supertypes_transitive
|
289
305
|
}).flatten.uniq
|
290
306
|
end
|
@@ -301,6 +317,17 @@ module ActiveFacts
|
|
301
317
|
debug "Failed to find identifying supertype of #{name}"
|
302
318
|
return nil
|
303
319
|
end
|
320
|
+
|
321
|
+
# This entity type has just objectified a fact type. Create the necessary ImplicitFactTypes with phantom roles
|
322
|
+
def create_implicit_fact_types
|
323
|
+
fact_type.all_role.each do |role|
|
324
|
+
next if role.implicit_fact_type # Already exists
|
325
|
+
implicit_fact_type = @constellation.ImplicitFactType(:new, :role => role)
|
326
|
+
phantom_role = @constellation.Role(implicit_fact_type, 0, :concept => self)
|
327
|
+
# We could create a copy of the visible external role here, but there's no need yet...
|
328
|
+
# Nor is there a need for a presence constraint, readings, etc.
|
329
|
+
end
|
330
|
+
end
|
304
331
|
end
|
305
332
|
|
306
333
|
class Reading
|
@@ -312,21 +339,21 @@ module ActiveFacts
|
|
312
339
|
# REVISIT: This should probably be changed to be the fact role sequence.
|
313
340
|
#
|
314
341
|
# define_role_names here is false (use defined names), true (define names) or nil (neither)
|
315
|
-
def expand(frequency_constraints = [], define_role_names =
|
342
|
+
def expand(frequency_constraints = [], define_role_names = nil, literals = [], &subscript_block)
|
316
343
|
expanded = "#{text}"
|
317
344
|
role_refs = role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
|
318
345
|
(0...role_refs.size).each{|i|
|
319
346
|
role_ref = role_refs[i]
|
320
347
|
role = role_ref.role
|
321
|
-
la = "#{role_ref.leading_adjective}"
|
322
|
-
la.sub!(/(.\b|.\Z)/, '\1-')
|
348
|
+
la = "#{role_ref.leading_adjective}".sub(/(.\b|.\Z)/, '\1-').sub(/- /,'- ')
|
323
349
|
la = nil if la == ""
|
324
|
-
|
325
|
-
ta.sub
|
350
|
+
# Double the space to compensate for space removed below
|
351
|
+
ta = "#{role_ref.trailing_adjective}".sub(/(\b.|\A.)/, '-\1').sub(/ -/,' -')
|
326
352
|
ta = nil if ta == ""
|
327
353
|
|
328
354
|
expanded.gsub!(/\{#{i}\}/) {
|
329
|
-
|
355
|
+
role_ref = role_refs[i]
|
356
|
+
player = role_ref.role.concept
|
330
357
|
role_name = role.role_name
|
331
358
|
role_name = nil if role_name == ""
|
332
359
|
if role_name && define_role_names == false
|
@@ -346,12 +373,13 @@ module ActiveFacts
|
|
346
373
|
define_role_names == false && role_name ? role_name : player_name,
|
347
374
|
ta,
|
348
375
|
define_role_names && role_name && player.name != role_name ? "(as #{role_name})" : nil,
|
349
|
-
# Can't have both a literal and a
|
376
|
+
# Can't have both a literal and a value constraint, but we don't enforce that here:
|
350
377
|
literal ? literal : nil
|
351
|
-
].compact*" "
|
378
|
+
].compact*" " +
|
379
|
+
(subscript_block ? subscript_block.call(role_ref) : "")
|
352
380
|
}
|
353
381
|
}
|
354
|
-
expanded.gsub!(/
|
382
|
+
expanded.gsub!(/ ?- ?/, '-') # Remove single spaces around adjectives
|
355
383
|
#debug "Expanded '#{expanded}' using #{frequency_constraints.inspect}"
|
356
384
|
expanded
|
357
385
|
end
|
@@ -370,7 +398,7 @@ module ActiveFacts
|
|
370
398
|
end
|
371
399
|
end
|
372
400
|
|
373
|
-
class
|
401
|
+
class ValueConstraint
|
374
402
|
def describe
|
375
403
|
"restricted to {"+
|
376
404
|
all_allowed_range_sorted.map{|ar| ar.to_s(false) }*", "+
|
@@ -444,11 +472,13 @@ module ActiveFacts
|
|
444
472
|
((min && min > 0 && min != max) ? "at least #{min == 1 ? "one" : min.to_s}" : nil),
|
445
473
|
((max && min != max) ? "at most #{max == 1 ? "one" : max.to_s}" : nil),
|
446
474
|
((max && min == max) ? "#{max == 1 ? "one" : max.to_s}" : nil)
|
447
|
-
].compact * " and"
|
475
|
+
].compact * " and "
|
448
476
|
end
|
449
477
|
|
450
478
|
def describe
|
451
|
-
|
479
|
+
min = min_frequency
|
480
|
+
max = max_frequency
|
481
|
+
role_sequence.describe + " occurs " + frequency + " time#{(min&&min>1)||(max&&max>1) ? 's' : ''}"
|
452
482
|
end
|
453
483
|
end
|
454
484
|
|
@@ -458,5 +488,259 @@ module ActiveFacts
|
|
458
488
|
end
|
459
489
|
end
|
460
490
|
|
491
|
+
class JoinStep
|
492
|
+
def describe
|
493
|
+
input_role_ref = input_join_node.all_role_ref.detect{|rr| rr.role.fact_type == fact_type}
|
494
|
+
output_role_ref = output_join_node.all_role_ref.detect{|rr| rr.role.fact_type == fact_type}
|
495
|
+
"from node #{input_join_node.ordinal} #{input_role_ref ? input_role_ref.role.concept.name : input_join_node.concept.name}"+
|
496
|
+
" to node #{output_join_node.ordinal} #{output_role_ref ? output_role_ref.role.concept.name : output_join_node.concept.name}"+
|
497
|
+
": #{is_anti && 'not '}#{is_outer && 'maybe '}#{fact_type.default_reading}"
|
498
|
+
end
|
499
|
+
|
500
|
+
def is_unary_step
|
501
|
+
# Preserve this in case we have to use a real join_node for the phantom
|
502
|
+
# input_join_node.all_role_ref.detect{|rr| rr.role.fact_type.is_a?(ImplicitFactType) && rr.role.fact_type.role.fact_type.all_role.size == 1 }
|
503
|
+
input_join_node == output_join_node
|
504
|
+
end
|
505
|
+
|
506
|
+
def is_objectification_step
|
507
|
+
fact_type.is_a?(ImplicitFactType)
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
class JoinNode
|
512
|
+
def describe
|
513
|
+
concept.name
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
class Join
|
518
|
+
def show
|
519
|
+
debug :join, "Displaying full contents of Join #{join_id}" do
|
520
|
+
all_join_node.sort_by{|jn| jn.ordinal}.each do |join_node|
|
521
|
+
debug :join, "Node #{join_node.ordinal} for #{join_node.concept.name}" do
|
522
|
+
(join_node.all_join_step_as_input_join_node.to_a +
|
523
|
+
join_node.all_join_step_as_output_join_node.to_a).
|
524
|
+
uniq.
|
525
|
+
each do |join_step|
|
526
|
+
debug :join, "#{
|
527
|
+
join_step.is_unary_step ? 'unary ' : ''
|
528
|
+
}#{
|
529
|
+
join_step.is_objectification_step ? 'objectification ' : ''
|
530
|
+
}step #{join_step.describe}"
|
531
|
+
end
|
532
|
+
join_node.all_role_ref.each do |role_ref|
|
533
|
+
debug :join, "reference #{role_ref.describe} in '#{role_ref.role.fact_type.default_reading}' over #{role_ref.role_sequence.describe}#{role_ref.role_sequence == role_sequence ? ' (projected)' : ''}"
|
534
|
+
end
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
def validate
|
541
|
+
show
|
542
|
+
return
|
543
|
+
|
544
|
+
# Check all parts of this join for validity
|
545
|
+
jns = all_join_node.sort_by{|jn| jn.ordinal}
|
546
|
+
jns.each_with_index do |jn, i|
|
547
|
+
raise "Join node #{i} should have ordinal #{jn.ordinal}" unless jn.ordinal == i
|
548
|
+
end
|
549
|
+
|
550
|
+
# Check the join nodes:
|
551
|
+
steps = []
|
552
|
+
jns.each_with_index do |join_node, i|
|
553
|
+
raise "Join Node #{i} has missing concept" unless join_node.concept
|
554
|
+
if join_node.all_role_ref.detect{|rr| rr.role.concept != join_node.concept }
|
555
|
+
raise "All role references for join node #{join_node.ordinal} should be for #{
|
556
|
+
join_node.concept.name
|
557
|
+
} but we have #{
|
558
|
+
(join_node.all_role_ref.map{|rr| rr.role.concept.name}-[join_node.concept.name]).uniq*', '
|
559
|
+
}"
|
560
|
+
end
|
561
|
+
steps += join_node.all_join_step_as_input_join_node.to_a
|
562
|
+
steps += join_node.all_join_step_as_output_join_node.to_a
|
563
|
+
|
564
|
+
# REVISIT: All Role References must be in a role sequence that covers one fact type exactly (why?)
|
565
|
+
# REVISIT: All such role references must have a join node in this join. (why?)
|
566
|
+
end
|
567
|
+
|
568
|
+
# Check the join steps:
|
569
|
+
steps.uniq!
|
570
|
+
steps.each_with_index do |join_step, i|
|
571
|
+
raise "Join Step #{i} has missing fact type" unless join_step.fact_type
|
572
|
+
raise "Join Step #{i} has missing input node" unless join_step.input_join_node
|
573
|
+
raise "Join Step #{i} has missing output node" unless join_step.output_join_node
|
574
|
+
debugger
|
575
|
+
p join_step.fact_type.default_reading
|
576
|
+
p join_step.input_join_node.all_role_ref.map(&:describe)
|
577
|
+
p join_step.output_join_node.all_role_ref.map(&:describe)
|
578
|
+
=begin
|
579
|
+
unless join_step.input_join_node.all_role_ref.
|
580
|
+
detect do |rr|
|
581
|
+
rr.role.fact_type == join_step.fact_type
|
582
|
+
or rr.role.fact_type.is_a?(ImplicitFactType) && rr.role.fact_type.role.fact_type == rr.join_step.concept
|
583
|
+
end
|
584
|
+
raise "Join Step #{join_step.describe} has nodes not matching its fact type"
|
585
|
+
end
|
586
|
+
=end
|
587
|
+
end
|
588
|
+
|
589
|
+
# REVISIT: Do a connectivity check
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
class ImplicitFactType
|
594
|
+
def default_reading
|
595
|
+
# There are two cases, where role is in a unary fact type, and where the fact type is objectified
|
596
|
+
# If a unary fact type is objectified, only the ImplicitFactType for the objectification is asserted
|
597
|
+
if objectification = role.fact_type.entity_type
|
598
|
+
"#{objectification.name} involves #{role.concept.name}"
|
599
|
+
else
|
600
|
+
role.fact_type.default_reading+" Boolean" # Must be a unary FT
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
# This is only used for debugging, from RoleRef#describe
|
605
|
+
class ImplicitReading
|
606
|
+
attr_accessor :fact_type, :text
|
607
|
+
|
608
|
+
def initialize(fact_type, text)
|
609
|
+
@fact_type = fact_type
|
610
|
+
@text = text
|
611
|
+
end
|
612
|
+
|
613
|
+
class ImplicitReadingRoleSequence
|
614
|
+
class ImplicitReadingRoleRef
|
615
|
+
attr_reader :role
|
616
|
+
attr_reader :role_sequence
|
617
|
+
def initialize(role, role_sequence)
|
618
|
+
@role = role
|
619
|
+
@role_sequence = role_sequence
|
620
|
+
end
|
621
|
+
def join_node; nil; end
|
622
|
+
def leading_adjective; nil; end
|
623
|
+
def trailing_adjective; nil; end
|
624
|
+
def describe
|
625
|
+
@role.concept.name
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
def initialize roles
|
630
|
+
@role_refs = roles.map{|role| ImplicitReadingRoleRef.new(role, self) }
|
631
|
+
end
|
632
|
+
|
633
|
+
def all_role_ref
|
634
|
+
@role_refs
|
635
|
+
end
|
636
|
+
def describe
|
637
|
+
'('+@role_refs.map(&:describe)*', '+')'
|
638
|
+
end
|
639
|
+
def all_reading
|
640
|
+
[]
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
def role_sequence
|
645
|
+
ImplicitReadingRoleSequence.new([@fact_type.role, @fact_type.all_role.single])
|
646
|
+
end
|
647
|
+
|
648
|
+
def ordinal; 0; end
|
649
|
+
end
|
650
|
+
|
651
|
+
def all_reading
|
652
|
+
[@reading ||= ImplicitReading.new(
|
653
|
+
self,
|
654
|
+
role.fact_type.entity_type ? "{0} involves {1}" : role.fact_type.default_reading+" Boolean"
|
655
|
+
)]
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
# Some joins must be over the proximate roles, some over the counterpart roles.
|
660
|
+
# Return the common superclass of the appropriate roles, and the actual roles
|
661
|
+
def self.join_roles_over roles, options = :both # Or :proximate, :counterpart
|
662
|
+
# If we can stay inside this objectified FT, there's no join:
|
663
|
+
roles = Array(roles) # To be safe, in case we get a role collection proxy
|
664
|
+
return nil if roles.size == 1 or
|
665
|
+
options != :counterpart && roles.map{|role| role.fact_type}.uniq.size == 1
|
666
|
+
proximate_sups, counterpart_sups, obj_sups, counterpart_roles, objectification_roles =
|
667
|
+
*roles.inject(nil) do |d_c_o, role|
|
668
|
+
concept = role.concept
|
669
|
+
fact_type = role.fact_type
|
670
|
+
|
671
|
+
proximate_role_supertypes = concept.supertypes_transitive
|
672
|
+
|
673
|
+
# A role in an objectified fact type may indicate either the objectification or the counterpart player.
|
674
|
+
# This could be ambiguous. Figure out both and prefer the counterpart over the objectification.
|
675
|
+
counterpart_role_supertypes =
|
676
|
+
if fact_type.all_role.size > 2
|
677
|
+
possible_roles = fact_type.all_role.select{|r| d_c_o && d_c_o[1].include?(r.concept) }
|
678
|
+
if possible_roles.size == 1 # Only one candidate matches the types of the possible join nodes
|
679
|
+
counterpart_role = possible_roles[0]
|
680
|
+
d_c_o[1] # No change
|
681
|
+
else
|
682
|
+
# puts "#{constraint_type} #{name}: Awkward, try counterpart-role join on a >2ary '#{fact_type.default_reading}'"
|
683
|
+
# Try all roles; hopefully we don't have two roles with a matching candidate here:
|
684
|
+
# Find which role is compatible with the existing supertypes, if any
|
685
|
+
if d_c_o
|
686
|
+
st = nil
|
687
|
+
counterpart_role =
|
688
|
+
fact_type.all_role.detect{|r| ((st = r.concept.supertypes_transitive) & d_c_o[1]).size > 0}
|
689
|
+
st
|
690
|
+
else
|
691
|
+
counterpart_role = nil # This can't work, we don't have any basis for a decision (must be objectification)
|
692
|
+
[]
|
693
|
+
end
|
694
|
+
#fact_type.all_role.map{|r| r.concept.supertypes_transitive}.flatten.uniq
|
695
|
+
end
|
696
|
+
else
|
697
|
+
# Get the supertypes of the counterpart role (care with unaries):
|
698
|
+
ftr = role.fact_type.all_role.to_a
|
699
|
+
(counterpart_role = ftr[0] == role ? ftr[-1] : ftr[0]).concept.supertypes_transitive
|
700
|
+
end
|
701
|
+
|
702
|
+
if fact_type.entity_type
|
703
|
+
objectification_role_supertypes =
|
704
|
+
fact_type.entity_type.supertypes_transitive+concept.supertypes_transitive
|
705
|
+
objectification_role = role.implicit_fact_type.all_role.single # Find the phantom role here
|
706
|
+
else
|
707
|
+
objectification_role_supertypes = counterpart_role_supertypes
|
708
|
+
objectification_role = nil
|
709
|
+
end
|
710
|
+
|
711
|
+
if !d_c_o
|
712
|
+
d_c_o = [proximate_role_supertypes, counterpart_role_supertypes, objectification_role_supertypes, [counterpart_role], [objectification_role]]
|
713
|
+
#puts "role player supertypes starts #{d_c_o.map{|dco| dco.map(&:name).inspect}*' or '}"
|
714
|
+
else
|
715
|
+
#puts "continues #{[proximate_role_supertypes, counterpart_role_supertypes, objectification_role_supertypes]map{|dco| dco.map(&:name).inspect}*' or '}"
|
716
|
+
d_c_o[0] &= proximate_role_supertypes
|
717
|
+
d_c_o[1] &= counterpart_role_supertypes
|
718
|
+
d_c_o[2] &= objectification_role_supertypes
|
719
|
+
d_c_o[3] << (counterpart_role || objectification_role)
|
720
|
+
d_c_o[4] << (objectification_role || counterpart_role)
|
721
|
+
end
|
722
|
+
d_c_o
|
723
|
+
end # inject
|
724
|
+
|
725
|
+
# Discount a subtype join over an object type that's not a player here,
|
726
|
+
# if we can use an objectification join to an object type that is:
|
727
|
+
if counterpart_sups.size > 0 && obj_sups.size > 0 && counterpart_sups[0] != obj_sups[0]
|
728
|
+
debug :join, "ambiguous join, could be over #{counterpart_sups[0].name} or #{obj_sups[0].name}"
|
729
|
+
if !roles.detect{|r| r.concept == counterpart_sups[0]} and roles.detect{|r| r.concept == obj_sups[0]}
|
730
|
+
debug :join, "discounting #{counterpart_sups[0].name} in favour of direct objectification"
|
731
|
+
counterpart_sups = []
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
# Choose the first entry in the first non-empty supertypes list:
|
736
|
+
if options != :counterpart
|
737
|
+
[ proximate_sups[0], roles ]
|
738
|
+
elsif !counterpart_sups.empty?
|
739
|
+
[ counterpart_sups[0], counterpart_roles ]
|
740
|
+
else
|
741
|
+
[ obj_sups[0], objectification_roles ]
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
461
745
|
end
|
462
746
|
end
|