activefacts 0.8.6 → 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/Manifest.txt +33 -2
  2. data/README.rdoc +30 -36
  3. data/Rakefile +16 -20
  4. data/bin/afgen +17 -11
  5. data/bin/cql +313 -36
  6. data/download.html +43 -19
  7. data/examples/CQL/Address.cql +15 -15
  8. data/examples/CQL/Blog.cql +8 -8
  9. data/examples/CQL/CompanyDirectorEmployee.cql +6 -5
  10. data/examples/CQL/Death.cql +3 -3
  11. data/examples/CQL/Diplomacy.cql +48 -0
  12. data/examples/CQL/Genealogy.cql +41 -41
  13. data/examples/CQL/Insurance.cql +311 -0
  14. data/examples/CQL/JoinEquality.cql +35 -0
  15. data/examples/CQL/Marriage.cql +1 -1
  16. data/examples/CQL/Metamodel.cql +290 -185
  17. data/examples/CQL/MetamodelNext.cql +420 -0
  18. data/examples/CQL/Monogamy.cql +24 -0
  19. data/examples/CQL/MonthInSeason.cql +27 -0
  20. data/examples/CQL/Moon.cql +23 -0
  21. data/examples/CQL/MultiInheritance.cql +4 -4
  22. data/examples/CQL/NonRoleId.cql +14 -0
  23. data/examples/CQL/OddIdentifier.cql +18 -0
  24. data/examples/CQL/OilSupply.cql +24 -24
  25. data/examples/CQL/OneToOnes.cql +17 -0
  26. data/examples/CQL/Orienteering.cql +55 -55
  27. data/examples/CQL/OrienteeringER.cql +58 -0
  28. data/examples/CQL/PersonPlaysGame.cql +2 -2
  29. data/examples/CQL/RedundantDependency.cql +34 -0
  30. data/examples/CQL/SchoolActivities.cql +5 -5
  31. data/examples/CQL/SeparateSubtype.cql +28 -0
  32. data/examples/CQL/ServiceDirector.cql +283 -0
  33. data/examples/CQL/SimplestUnary.cql +2 -2
  34. data/examples/CQL/SubtypePI.cql +11 -11
  35. data/examples/CQL/Supervision.cql +38 -0
  36. data/examples/CQL/Tests.Test5.Load.cql +38 -0
  37. data/examples/CQL/WaiterTips.cql +33 -0
  38. data/examples/CQL/Warehousing.cql +55 -53
  39. data/examples/CQL/WindowInRoomInBldg.cql +9 -9
  40. data/examples/CQL/unit.cql +433 -544
  41. data/examples/index.html +314 -170
  42. data/examples/intro.html +6 -176
  43. data/examples/local.css +8 -4
  44. data/index.html +40 -25
  45. data/lib/activefacts/api/concept.rb +2 -2
  46. data/lib/activefacts/api/constellation.rb +4 -4
  47. data/lib/activefacts/api/instance.rb +2 -2
  48. data/lib/activefacts/api/instance_index.rb +4 -0
  49. data/lib/activefacts/api/numeric.rb +3 -1
  50. data/lib/activefacts/api/role.rb +1 -1
  51. data/lib/activefacts/api/standard_types.rb +23 -16
  52. data/lib/activefacts/api/support.rb +3 -1
  53. data/lib/activefacts/api/vocabulary.rb +4 -0
  54. data/lib/activefacts/cql/CQLParser.treetop +87 -39
  55. data/lib/activefacts/cql/Concepts.treetop +95 -69
  56. data/lib/activefacts/cql/Context.treetop +11 -2
  57. data/lib/activefacts/cql/Expressions.treetop +23 -59
  58. data/lib/activefacts/cql/FactTypes.treetop +141 -95
  59. data/lib/activefacts/cql/Language/English.treetop +33 -21
  60. data/lib/activefacts/cql/LexicalRules.treetop +6 -1
  61. data/lib/activefacts/cql/Terms.treetop +75 -26
  62. data/lib/activefacts/cql/ValueTypes.treetop +52 -54
  63. data/lib/activefacts/cql/compiler.rb +46 -1691
  64. data/lib/activefacts/cql/compiler/constraint.rb +602 -0
  65. data/lib/activefacts/cql/compiler/entity_type.rb +425 -0
  66. data/lib/activefacts/cql/compiler/fact.rb +300 -0
  67. data/lib/activefacts/cql/compiler/fact_type.rb +230 -0
  68. data/lib/activefacts/cql/compiler/reading.rb +832 -0
  69. data/lib/activefacts/cql/compiler/shared.rb +109 -0
  70. data/lib/activefacts/cql/compiler/value_type.rb +104 -0
  71. data/lib/activefacts/cql/parser.rb +132 -81
  72. data/lib/activefacts/generate/cql.rb +397 -274
  73. data/lib/activefacts/generate/oo.rb +13 -12
  74. data/lib/activefacts/generate/ordered.rb +107 -117
  75. data/lib/activefacts/generate/ruby.rb +34 -38
  76. data/lib/activefacts/generate/sql/mysql.rb +62 -45
  77. data/lib/activefacts/generate/sql/server.rb +59 -42
  78. data/lib/activefacts/input/cql.rb +6 -3
  79. data/lib/activefacts/input/orm.rb +991 -557
  80. data/lib/activefacts/persistence/columns.rb +16 -12
  81. data/lib/activefacts/persistence/foreignkey.rb +7 -4
  82. data/lib/activefacts/persistence/index.rb +3 -4
  83. data/lib/activefacts/persistence/reference.rb +5 -2
  84. data/lib/activefacts/support.rb +20 -14
  85. data/lib/activefacts/version.rb +1 -1
  86. data/lib/activefacts/vocabulary.rb +1 -0
  87. data/lib/activefacts/vocabulary/extensions.rb +328 -44
  88. data/lib/activefacts/vocabulary/metamodel.rb +145 -20
  89. data/lib/activefacts/vocabulary/verbaliser.rb +621 -0
  90. data/spec/absorption_spec.rb +4 -4
  91. data/spec/api/value_type.rb +1 -1
  92. data/spec/cql/context_spec.rb +45 -22
  93. data/spec/cql/deontic_spec.rb +88 -0
  94. data/spec/cql/matching_spec.rb +517 -0
  95. data/spec/cql/samples_spec.rb +88 -31
  96. data/spec/cql/unit_spec.rb +58 -37
  97. data/spec/cql_cql_spec.rb +12 -7
  98. data/spec/cql_mysql_spec.rb +3 -7
  99. data/spec/cql_parse_spec.rb +0 -4
  100. data/spec/cql_ruby_spec.rb +1 -4
  101. data/spec/cql_sql_spec.rb +5 -18
  102. data/spec/cql_symbol_tables_spec.rb +3 -0
  103. data/spec/cqldump_spec.rb +0 -2
  104. data/spec/helpers/array_matcher.rb +35 -0
  105. data/spec/helpers/ctrl_c_support.rb +52 -0
  106. data/spec/helpers/diff_matcher.rb +38 -0
  107. data/spec/helpers/file_matcher.rb +5 -3
  108. data/spec/helpers/string_matcher.rb +39 -0
  109. data/spec/helpers/test_parser.rb +13 -0
  110. data/spec/norma_cql_spec.rb +13 -5
  111. data/spec/norma_ruby_spec.rb +11 -3
  112. data/spec/{absorption_ruby_spec.rb → norma_ruby_sql_spec.rb} +37 -32
  113. data/spec/norma_sql_spec.rb +11 -5
  114. data/spec/norma_tables_spec.rb +33 -29
  115. data/spec/spec_helper.rb +4 -1
  116. data/status.html +92 -23
  117. metadata +102 -36
  118. 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
- restrictions = []
147
- return ["BIT", params, restrictions] if references[-1].is_unary # It's a unary
146
+ constraints = []
147
+ return ["BIT", params, constraints] if references[-1].is_unary # It's a unary
148
148
 
149
- # Add a role value restriction
150
- # REVISIT: Can add join-role-value-restrictions here, if we ever provide a way to define them
151
- if references[-1].to_role && references[-1].to_role.role_value_restriction
152
- restrictions << references[-1].to_role.role_value_restriction
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
- restrictions << vt.value_restriction if vt.value_restriction
161
+ constraints << vt.value_constraint if vt.value_constraint
162
162
  vt = vt.supertype
163
163
  end
164
- return [vt.name, params, restrictions]
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, otherwise retaining their order:
332
- sups.index(ref.to) ||
333
- (!ref.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) && references_from.size+references_from.index(ref)) ||
334
- references_from.size*2+references_from.index(ref)
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
- # This catches references that would be created to secondary supertypes, when absorption is through primary.
41
- # There might be other cases where an exclusion like this is needed, but I can't reason it out.
42
- next array if TypeInheritance === ref.fact_type && absorbed_via && TypeInheritance === absorbed_via.fact_type
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
@@ -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(/[^a-zA-Z0-9]/).each{|k| $debug_keys[k.to_sym] = true }
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
- # Figure out whether this trace is enabled and nests:
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 "# "+" "*$debug_indent + args.join(' ') if args.size > 0 && enabled
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 += 1 if enabled
43
- r = yield # Return the value of the block
48
+ $debug_indent += enabled
49
+ return yield # Return the value of the block
44
50
  ensure
45
- $debug_indent -= 1 if enabled
51
+ $debug_indent -= enabled
46
52
  $debug_nesting = old_nested
47
53
  end
48
54
  else
49
- r = enabled # Return whether enabled
55
+ r = enabled == 1 # If no block, return whether enabled
50
56
  end
51
57
  r
52
58
  end
@@ -5,5 +5,5 @@
5
5
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
6
  #
7
7
  module ActiveFacts
8
- VERSION = '0.8.6'
8
+ VERSION = '0.8.8'
9
9
  end
@@ -6,3 +6,4 @@
6
6
  #
7
7
  require 'activefacts/vocabulary/metamodel'
8
8
  require 'activefacts/vocabulary/extensions'
9
+ require 'activefacts/vocabulary/verbaliser'
@@ -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 = false)
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
- # The reference traverses the Joins in sequence to the final role:
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.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.split(/\s/)
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 = false, literals = [])
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
- ta = "#{role_ref.trailing_adjective}"
325
- ta.sub!(/(\b.|\A.)/, '-\1')
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
- player = role_refs[i].role.concept
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 restriction, but we don't enforce that here:
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!(/ *- */, '-') # Remove spaces around adjectives
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 ValueRestriction
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
- role_sequence.describe + " occurs " + frequency + " time"
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