activefacts 0.8.9 → 0.8.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. data/.gemtest +0 -0
  2. data/Manifest.txt +28 -33
  3. data/Rakefile +11 -12
  4. data/bin/cql +90 -46
  5. data/examples/CQL/Blog.cql +2 -1
  6. data/examples/CQL/CompanyDirectorEmployee.cql +2 -2
  7. data/examples/CQL/Death.cql +1 -1
  8. data/examples/CQL/Diplomacy.cql +9 -9
  9. data/examples/CQL/Genealogy.cql +3 -2
  10. data/examples/CQL/Insurance.cql +10 -7
  11. data/examples/CQL/JoinEquality.cql +2 -2
  12. data/examples/CQL/Marriage.cql +1 -1
  13. data/examples/CQL/Metamodel.cql +73 -53
  14. data/examples/CQL/MetamodelNext.cql +89 -67
  15. data/examples/CQL/OneToOnes.cql +2 -2
  16. data/examples/CQL/ServiceDirector.cql +10 -5
  17. data/examples/CQL/Supervision.cql +3 -3
  18. data/examples/CQL/Tests.Test5.Load.cql +1 -1
  19. data/examples/CQL/Warehousing.cql +4 -2
  20. data/lib/activefacts/cql/CQLParser.treetop +26 -60
  21. data/lib/activefacts/cql/Context.treetop +12 -2
  22. data/lib/activefacts/cql/Expressions.treetop +14 -30
  23. data/lib/activefacts/cql/FactTypes.treetop +165 -110
  24. data/lib/activefacts/cql/Language/English.treetop +167 -54
  25. data/lib/activefacts/cql/LexicalRules.treetop +16 -2
  26. data/lib/activefacts/cql/{Concepts.treetop → ObjectTypes.treetop} +36 -37
  27. data/lib/activefacts/cql/Terms.treetop +57 -27
  28. data/lib/activefacts/cql/ValueTypes.treetop +39 -13
  29. data/lib/activefacts/cql/compiler.rb +5 -3
  30. data/lib/activefacts/cql/compiler/{reading.rb → clause.rb} +407 -285
  31. data/lib/activefacts/cql/compiler/constraint.rb +178 -275
  32. data/lib/activefacts/cql/compiler/entity_type.rb +73 -64
  33. data/lib/activefacts/cql/compiler/expression.rb +418 -0
  34. data/lib/activefacts/cql/compiler/fact.rb +146 -145
  35. data/lib/activefacts/cql/compiler/fact_type.rb +197 -80
  36. data/lib/activefacts/cql/compiler/join.rb +159 -0
  37. data/lib/activefacts/cql/compiler/shared.rb +51 -23
  38. data/lib/activefacts/cql/compiler/value_type.rb +56 -2
  39. data/lib/activefacts/cql/parser.rb +15 -4
  40. data/lib/activefacts/generate/absorption.rb +7 -7
  41. data/lib/activefacts/generate/cql.rb +100 -37
  42. data/lib/activefacts/generate/oo.rb +28 -51
  43. data/lib/activefacts/generate/ordered.rb +60 -36
  44. data/lib/activefacts/generate/ruby.rb +6 -6
  45. data/lib/activefacts/generate/sql/server.rb +4 -4
  46. data/lib/activefacts/input/orm.rb +71 -53
  47. data/lib/activefacts/persistence.rb +1 -1
  48. data/lib/activefacts/persistence/columns.rb +27 -23
  49. data/lib/activefacts/persistence/foreignkey.rb +6 -6
  50. data/lib/activefacts/persistence/index.rb +17 -17
  51. data/lib/activefacts/persistence/{concept.rb → object_type.rb} +9 -9
  52. data/lib/activefacts/persistence/reference.rb +61 -36
  53. data/lib/activefacts/persistence/tables.rb +61 -59
  54. data/lib/activefacts/support.rb +54 -29
  55. data/lib/activefacts/version.rb +1 -1
  56. data/lib/activefacts/vocabulary/extensions.rb +99 -54
  57. data/lib/activefacts/vocabulary/metamodel.rb +43 -37
  58. data/lib/activefacts/vocabulary/verbaliser.rb +134 -109
  59. data/spec/absorption_spec.rb +8 -8
  60. data/spec/cql/comparison_spec.rb +91 -0
  61. data/spec/cql/contractions_spec.rb +251 -0
  62. data/spec/cql/entity_type_spec.rb +319 -0
  63. data/spec/cql/expressions_spec.rb +63 -0
  64. data/spec/cql/fact_type_matching_spec.rb +283 -0
  65. data/spec/cql/french_spec.rb +21 -0
  66. data/spec/cql/parser/bad_literals_spec.rb +86 -0
  67. data/spec/cql/parser/constraints_spec.rb +19 -0
  68. data/spec/cql/parser/entity_types_spec.rb +106 -0
  69. data/spec/cql/parser/expressions_spec.rb +179 -0
  70. data/spec/cql/parser/fact_types_spec.rb +41 -0
  71. data/spec/cql/parser/literals_spec.rb +312 -0
  72. data/spec/cql/parser/pragmas_spec.rb +89 -0
  73. data/spec/cql/parser/value_types_spec.rb +42 -0
  74. data/spec/cql/role_matching_spec.rb +147 -0
  75. data/spec/cql/samples_spec.rb +9 -9
  76. data/spec/cql_cql_spec.rb +1 -1
  77. data/spec/cql_dm_spec.rb +116 -0
  78. data/spec/cql_mysql_spec.rb +1 -1
  79. data/spec/cql_ruby_spec.rb +1 -1
  80. data/spec/cql_sql_spec.rb +3 -3
  81. data/spec/cql_symbol_tables_spec.rb +30 -30
  82. data/spec/cqldump_spec.rb +4 -4
  83. data/spec/helpers/array_matcher.rb +32 -27
  84. data/spec/helpers/diff_matcher.rb +6 -26
  85. data/spec/helpers/file_matcher.rb +41 -32
  86. data/spec/helpers/parse_to_ast_matcher.rb +76 -0
  87. data/spec/helpers/string_matcher.rb +32 -31
  88. data/spec/norma_cql_spec.rb +1 -1
  89. data/spec/norma_ruby_spec.rb +1 -1
  90. data/spec/norma_ruby_sql_spec.rb +1 -1
  91. data/spec/norma_sql_spec.rb +3 -1
  92. data/spec/norma_tables_spec.rb +1 -1
  93. data/spec/ruby_api_spec.rb +23 -0
  94. data/spec/spec_helper.rb +5 -4
  95. metadata +66 -66
  96. data/examples/CQL/OrienteeringER.cql +0 -58
  97. data/lib/activefacts/api.rb +0 -44
  98. data/lib/activefacts/api/concept.rb +0 -410
  99. data/lib/activefacts/api/constellation.rb +0 -128
  100. data/lib/activefacts/api/entity.rb +0 -256
  101. data/lib/activefacts/api/instance.rb +0 -60
  102. data/lib/activefacts/api/instance_index.rb +0 -80
  103. data/lib/activefacts/api/numeric.rb +0 -167
  104. data/lib/activefacts/api/role.rb +0 -80
  105. data/lib/activefacts/api/role_proxy.rb +0 -70
  106. data/lib/activefacts/api/role_values.rb +0 -117
  107. data/lib/activefacts/api/standard_types.rb +0 -87
  108. data/lib/activefacts/api/support.rb +0 -65
  109. data/lib/activefacts/api/value.rb +0 -135
  110. data/lib/activefacts/api/vocabulary.rb +0 -82
  111. data/spec/api/autocounter.rb +0 -82
  112. data/spec/api/constellation.rb +0 -130
  113. data/spec/api/entity_type.rb +0 -103
  114. data/spec/api/instance.rb +0 -461
  115. data/spec/api/roles.rb +0 -124
  116. data/spec/api/value_type.rb +0 -112
  117. data/spec/api_spec.rb +0 -13
  118. data/spec/cql/matching_spec.rb +0 -517
  119. data/spec/cql/unit_spec.rb +0 -394
  120. data/spec/spec.opts +0 -1
@@ -10,15 +10,21 @@ module ActiveFacts
10
10
  @value_constraint = value_constraint
11
11
  @parameters = parameters
12
12
  end
13
+
14
+ def to_s
15
+ "identified by its #{name}" +
16
+ ((p = @parameters).size > 0 ? '('+p*', '+')' : '') +
17
+ ((v = @value_constraint) ? v.to_s : '')
18
+ end
13
19
  end
14
20
 
15
- class EntityType < Concept
16
- def initialize name, supertypes, identification, pragmas, readings
21
+ class EntityType < ObjectType
22
+ def initialize name, supertypes, identification, pragmas, clauses
17
23
  super name
18
24
  @supertypes = supertypes
19
25
  @identification = identification
20
26
  @pragmas = pragmas
21
- @readings = readings || []
27
+ @clauses = clauses || []
22
28
  end
23
29
 
24
30
  def compile
@@ -36,10 +42,12 @@ module ActiveFacts
36
42
  # Identification may be via a mode (create it) or by forward-referenced entity types (allow those):
37
43
  prepare_identifier context
38
44
 
45
+ context.bind @clauses, @identification.is_a?(Array) ? @identification : []
46
+
39
47
  # Create the fact types that define the identifying roles:
40
48
  fact_types = create_identifying_fact_types context
41
49
 
42
- # At this point, @identification is an array of RoleRefs and/or Readings (for unary fact types)
50
+ # At this point, @identification is an array of VarRefs and/or Clauses (for unary fact types)
43
51
  # Have to do this after creating the necessary fact types
44
52
  complete_reference_mode_fact_type fact_types
45
53
 
@@ -48,9 +56,9 @@ module ActiveFacts
48
56
 
49
57
  make_preferred_identifier_over_roles identifying_roles
50
58
 
51
- @readings.each do |reading|
52
- next unless reading.context_note
53
- reading.context_note.compile(@constellation, @entity_type)
59
+ @clauses.each do |clause|
60
+ next unless clause.context_note
61
+ clause.context_note.compile(@constellation, @entity_type)
54
62
  end
55
63
 
56
64
  @entity_type
@@ -62,7 +70,7 @@ module ActiveFacts
62
70
  if @identification.is_a? ReferenceMode
63
71
  make_entity_type_refmode_valuetypes(name, @identification.name, @identification.parameters)
64
72
  vt_name = @reference_mode_value_type.name
65
- @identification = [Compiler::RoleRef.new(vt_name, nil, nil, nil, nil, nil, @identification.value_constraint, nil)]
73
+ @identification = [Compiler::VarRef.new(vt_name, nil, nil, nil, nil, nil, @identification.value_constraint, nil)]
66
74
  else
67
75
  context.allowed_forward_terms = legal_forward_references(@identification)
68
76
  end
@@ -70,29 +78,28 @@ module ActiveFacts
70
78
  end
71
79
 
72
80
  # Names used in the identifying roles list may be forward referenced:
73
- def legal_forward_references(identification_roles)
74
- identification_roles.map do |phrase|
75
- phrase.is_a?(RoleRef) ? phrase.term : nil
81
+ def legal_forward_references(identification_phrases)
82
+ identification_phrases.map do |phrase|
83
+ phrase.is_a?(VarRef) ? phrase.term : nil
76
84
  end.compact.uniq
77
85
  end
78
86
 
79
87
  def bind_identifying_roles context
80
88
  return unless @identification
81
89
  @identification.map do |id|
82
- if id.is_a?(RoleRef)
83
- id.identify_player(context)
84
- binding = id.bind(context)
85
- roles = binding.refs.map{|r| r.role}.compact.uniq
90
+ if id.is_a?(VarRef)
91
+ variable = id.variable
92
+ roles = variable.refs.map{|r| r.role}.compact.uniq
86
93
  raise "Looking for an occurrence of identifying role #{id.inspect}, but found #{roles.size == 0 ? "none" : roles.size}" if roles.size != 1
87
94
  roles[0]
88
95
  else
89
- # id is a reading of a unary fact type.
96
+ # id is a clause of a unary fact type.
90
97
  id.identify_other_players context
91
- id.bind_roles context
92
- matching_reading =
93
- @readings.detect { |reading| reading.phrases_match id.phrases }
94
- raise "Unary identifying role 'id.inspect' is not found in the defined fact types" unless matching_reading
95
- matching_reading.fact_type.all_role.single
98
+ id.bind context
99
+ matching_clause =
100
+ @clauses.detect { |clause| clause.phrases_match id.phrases }
101
+ raise "Unary identifying role '#{id.inspect}' is not found in the defined fact types" unless matching_clause
102
+ matching_clause.fact_type.all_role.single
96
103
  end
97
104
  end
98
105
  end
@@ -138,51 +145,47 @@ module ActiveFacts
138
145
 
139
146
  def create_identifying_fact_types context
140
147
  fact_types = []
141
- # Categorise the readings into fact types according to the roles they play.
142
- @readings.each{ |reading| reading.identify_players_with_role_name(context) }
143
- @readings.each{ |reading| reading.identify_other_players(context) }
144
- @readings.inject({}) do |hash, reading|
145
- players_key = reading.role_refs.map{|rr| rr.key.compact}.sort
146
- (hash[players_key] ||= []) << reading
148
+ # Categorise the clauses into fact types according to the roles they play.
149
+ @clauses.inject({}) do |hash, clause|
150
+ players_key = clause.var_refs.map{|vr| vr.key.compact}.sort
151
+ (hash[players_key] ||= []) << clause
147
152
  hash
148
- end.each do |players_key, readings|
149
- readings.each{ |reading| reading.bind_roles context } # Create the Compiler::Bindings
150
-
153
+ end.each do |players_key, clauses|
151
154
  # REVISIT: Loose binding goes here; it might merge some Compiler#Roles
152
155
 
153
- fact_type = create_identifying_fact_type(context, readings)
156
+ fact_type = create_identifying_fact_type(context, clauses)
154
157
  fact_types << fact_type if fact_type
155
- objectify_existing_fact_type(fact_type) unless fact_type.all_role.detect{|r| r.concept == @entity_type}
158
+ objectify_existing_fact_type(fact_type) unless fact_type.all_role.detect{|r| r.object_type == @entity_type}
156
159
  end
157
160
  fact_types
158
161
  end
159
162
 
160
- def create_identifying_fact_type context, readings
163
+ def create_identifying_fact_type context, clauses
161
164
  # Remove uninteresting assertions:
162
- readings.reject!{|reading| reading.is_existential_type }
163
- return nil unless readings.size > 0 # Nothing interesting was said.
165
+ clauses.reject!{|clause| clause.is_existential_type }
166
+ return nil unless clauses.size > 0 # Nothing interesting was said.
164
167
 
165
168
  # See if any fact type already exists (this ET cannot be a player, but might objectify it)
166
- existing_readings = readings.select{ |reading| reading.match_existing_fact_type context }
167
- any_matched = existing_readings.size > 0
169
+ existing_clauses = clauses.select{ |clause| clause.match_existing_fact_type context }
170
+ any_matched = existing_clauses.size > 0
168
171
 
169
172
  operation = any_matched ? 'Objectifying' : 'Creating'
170
- player_names = readings[0].role_refs.map{|rr| rr.key.compact*'-'}
171
- debug :matching, "#{operation} fact type for #{readings.size} readings over (#{player_names*', '})" do
173
+ player_names = clauses[0].var_refs.map{|vr| vr.key.compact*'-'}
174
+ debug :matching, "#{operation} fact type for #{clauses.size} clauses over (#{player_names*', '})" do
172
175
  if any_matched # There's an existing fact type we must be objectifying
173
- fact_type = objectify_existing_fact_type(existing_readings[0].fact_type)
176
+ fact_type = objectify_existing_fact_type(existing_clauses[0].fact_type)
174
177
  end
175
178
 
176
179
  unless fact_type
177
- fact_type = readings[0].make_fact_type(@vocabulary)
178
- readings[0].make_reading(@vocabulary, fact_type)
179
- readings[0].make_embedded_constraints vocabulary
180
- existing_readings = [readings[0]]
180
+ fact_type = clauses[0].make_fact_type(@vocabulary)
181
+ clauses[0].make_reading(@vocabulary, fact_type)
182
+ clauses[0].make_embedded_constraints vocabulary
183
+ existing_clauses = [clauses[0]]
181
184
  end
182
185
 
183
- (readings - existing_readings).each do |reading|
184
- reading.make_reading(@vocabulary, fact_type)
185
- reading.make_embedded_constraints vocabulary
186
+ (clauses - existing_clauses).each do |clause|
187
+ clause.make_reading(@vocabulary, fact_type)
188
+ clause.make_embedded_constraints vocabulary
186
189
  end
187
190
 
188
191
  fact_type
@@ -226,24 +229,20 @@ module ActiveFacts
226
229
  inheritance_fact.assimilation = assimilations[0]
227
230
 
228
231
  # Create a reading:
229
- sub_role = @constellation.Role(inheritance_fact, 0, :concept => @entity_type)
230
- super_role = @constellation.Role(inheritance_fact, 1, :concept => supertype)
232
+ sub_role = @constellation.Role(inheritance_fact, 0, :object_type => @entity_type)
233
+ super_role = @constellation.Role(inheritance_fact, 1, :object_type => supertype)
231
234
 
232
235
  rs = @constellation.RoleSequence(:new)
233
236
  @constellation.RoleRef(rs, 0, :role => sub_role)
234
237
  @constellation.RoleRef(rs, 1, :role => super_role)
235
238
  @constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :text => "{0} is a kind of {1}")
236
- # REVISIT: Can we get away with adding "{0} is a/n {1}" here?
237
- # REVISIT: Can we deprecate the "is a subtype of" reading?
238
- @constellation.Reading(inheritance_fact, 1, :role_sequence => rs, :text => "{0} is a subtype of {1}")
239
239
 
240
240
  rs2 = @constellation.RoleSequence(:new)
241
241
  @constellation.RoleRef(rs2, 0, :role => super_role)
242
242
  @constellation.RoleRef(rs2, 1, :role => sub_role)
243
243
  # Decide in which order to include is a/is an. Provide both, but in order.
244
- n = 'aeiouh'.include?(sub_role.concept.name.downcase[0]) ? 1 : 0
245
- @constellation.Reading(inheritance_fact, 2+n, :role_sequence => rs2, :text => "{0} is a {1}")
246
- @constellation.Reading(inheritance_fact, 3-n, :role_sequence => rs2, :text => "{0} is an {1}")
244
+ n = 'aeiouh'.include?(sub_role.object_type.name.downcase[0]) ? 'n' : ''
245
+ @constellation.Reading(inheritance_fact, 2, :role_sequence => rs2, :text => "{0} is a#{n} {1}")
247
246
 
248
247
  if is_identifying_supertype
249
248
  inheritance_fact.provides_identification = true
@@ -278,8 +277,8 @@ module ActiveFacts
278
277
  vt = nil
279
278
  debug :entity, "Preparing value type #{vt_name} for reference mode" do
280
279
  # Find or Create an appropriate ValueType called '#{vt_name}', of the supertype '#{mode}'
281
- unless vt = @constellation.Concept[[@vocabulary.identifying_role_values, vt_name]] or
282
- vt = @constellation.Concept[[@vocabulary.identifying_role_values, vt_name = "#{name} #{mode}"]]
280
+ unless vt = @constellation.ObjectType[[@vocabulary.identifying_role_values, vt_name]] or
281
+ vt = @constellation.ObjectType[[@vocabulary.identifying_role_values, vt_name = "#{name} #{mode}"]]
283
282
  base_vt = @constellation.ValueType(@vocabulary, mode)
284
283
  vt = @constellation.ValueType(@vocabulary, vt_name, :supertype => base_vt)
285
284
  if parameters
@@ -306,16 +305,16 @@ module ActiveFacts
306
305
  # Find an existing fact type, if any:
307
306
  entity_role = identifying_role = nil
308
307
  fact_type = fact_types.detect do |ft|
309
- identifying_role = ft.all_role.detect{|r| r.concept == identifying_type } and
310
- entity_role = ft.all_role.detect{|r| r.concept == @entity_type }
308
+ identifying_role = ft.all_role.detect{|r| r.object_type == identifying_type } and
309
+ entity_role = ft.all_role.detect{|r| r.object_type == @entity_type }
311
310
  end
312
311
 
313
312
  # Create an identifying fact type if needed:
314
313
  unless fact_type
315
314
  fact_type = @constellation.FactType(:new)
316
315
  fact_types << fact_type
317
- entity_role = @constellation.Role(fact_type, 0, :concept => @entity_type)
318
- identifying_role = @constellation.Role(fact_type, 1, :concept => identifying_type)
316
+ entity_role = @constellation.Role(fact_type, 0, :object_type => @entity_type)
317
+ identifying_role = @constellation.Role(fact_type, 1, :object_type => identifying_type)
319
318
  end
320
319
  @identification[0].role = identifying_role
321
320
 
@@ -342,7 +341,7 @@ module ActiveFacts
342
341
  end
343
342
  if rs01.all_reading.empty?
344
343
  @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence => rs01, :text => "{0} has {1}")
345
- debug :mode, "Creating new forward reading '#{entity_role.concept.name} has #{identifying_type.name}'"
344
+ debug :mode, "Creating new forward reading '#{entity_role.object_type.name} has #{identifying_type.name}'"
346
345
  else
347
346
  debug :mode, "Using existing forward reading"
348
347
  end
@@ -356,7 +355,7 @@ module ActiveFacts
356
355
  end
357
356
  if rs10.all_reading.empty?
358
357
  @constellation.Reading(fact_type, fact_type.all_reading.size, :role_sequence => rs10, :text => "{0} is of {1}")
359
- debug :mode, "Creating new reverse reading '#{identifying_type.name} is of #{entity_role.concept.name}'"
358
+ debug :mode, "Creating new reverse reading '#{identifying_type.name} is of #{entity_role.object_type.name}'"
360
359
  else
361
360
  debug :mode, "Using existing reverse reading"
362
361
  end
@@ -417,9 +416,19 @@ module ActiveFacts
417
416
  end
418
417
  end
419
418
 
419
+ def to_s
420
+ "EntityType: #{super} #{
421
+ @supertypes.size > 0 ? "< #{@supertypes*','} " : ''
422
+ }#{
423
+ @identification.is_a?(ReferenceMode) ? @identification.to_s : @identification.inspect
424
+ }#{
425
+ @clauses.size > 0 ? " where #{@clauses.inspect}" : ''
426
+ }#{
427
+ @pragmas.size > 0 ? ", pragmas [#{@pragmas*','}]" : ''
428
+ };"
429
+ end
420
430
  end
421
431
 
422
432
  end
423
433
  end
424
434
  end
425
-
@@ -0,0 +1,418 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler
4
+
5
+ # An Operation is a binary or ternary fact type involving an operator,
6
+ # a result, and one or two operands.
7
+ # Viewed as a result, it behaves like a VarRef with a nested Clause.
8
+ # Viewed as a fact type, it behaves like a Clause.
9
+ #
10
+ # The only exception here is an equality comparison, where it may
11
+ # turn out that the equality is merely a projection. In this case
12
+ # the Operation is dropped from the clauses and is replaced by the
13
+ # projected operand.
14
+ #
15
+ # Each operand may be a Literal, a VarRef, or another Operation,
16
+ # so we need to recurse down the tree to build the join.
17
+ #
18
+ class Operation
19
+ # VarRef (in)compatibility:
20
+ [ :term, :leading_adjective, :trailing_adjective, :role_name, :quantifier,
21
+ :value_constraint, :embedded_presence_constraint, :literal
22
+ ].each do |s|
23
+ define_method(s) { raise "Unexpected call to Operation\##{s}" }
24
+ define_method(:"#{s}=") { raise "Unexpected call to Operation\##{s}=" }
25
+ end
26
+ def role_name; nil; end
27
+ def leading_adjective; nil; end
28
+ def trailing_adjective; nil; end
29
+ def value_constraint; nil; end
30
+ def literal; nil; end
31
+ attr_accessor :player # What ObjectType does the Variable denote
32
+ attr_accessor :variable # What Variable for that ObjectType
33
+ attr_accessor :clause # What clause does the result participate in?
34
+ attr_accessor :role # Which Role of this ObjectType
35
+ attr_accessor :role_ref # Which RoleRef to that Role
36
+ def nested_clauses; @nested_clauses ||= [self]; end
37
+ def clause; self; end
38
+ def objectification_of; @fact_type; end
39
+
40
+ # Clause (in)compatibility:
41
+ [ :phrases, :qualifiers, :context_note, :reading, :role_sequence, :fact
42
+ ].each do |s|
43
+ define_method(s) { raise "Unexpected call to Operation\##{s}" }
44
+ define_method(:"#{s}=") { raise "Unexpected call to Operation\##{s}=" }
45
+ end
46
+ def conjunction; nil; end
47
+ attr_reader :fact_type
48
+ def objectified_as; self; end # The VarRef which objectified this fact type
49
+
50
+ def operands context = nil
51
+ raise "REVISIT: Implement operand enumeration in the operator subclass #{self.class.name}"
52
+ end
53
+
54
+ def identify_players_with_role_name context
55
+ # Just recurse, there's no way (yet: REVISIT?) to add a role name to the result of an expression
56
+ var_refs.each { |o|
57
+ o.identify_players_with_role_name(context)
58
+ }
59
+ # As yet, an operation cannot have a role name:
60
+ # identify_player context if role_name
61
+ end
62
+
63
+ def identify_other_players context
64
+ # Just recurse, there's no way (yet: REVISIT?) to add a role name to the result of an expression
65
+ var_refs.each { |o|
66
+ o.identify_other_players(context)
67
+ }
68
+ identify_player context
69
+ end
70
+
71
+ def bind context
72
+ var_refs.each do |o|
73
+ o.bind context
74
+ end
75
+ name = result_type_name(context)
76
+ @player = result_value_type(context, name)
77
+ key = "#{name} #{object_id}" # Every Operation result is a unique Variable
78
+ @variable = (context.variables[key] ||= Variable.new(@player))
79
+ @variable.refs << self
80
+ @variable
81
+ end
82
+
83
+ def result_type_name(context)
84
+ raise "REVISIT: Implement result_type_name in the #{self.class.name} subclass"
85
+ end
86
+
87
+ def result_value_type(context, name)
88
+ vocabulary = context.vocabulary
89
+ constellation = vocabulary.constellation
90
+ constellation.ValueType(vocabulary, name)
91
+ end
92
+
93
+ def is_naked_object_type
94
+ false # All Operations are non-naked
95
+ end
96
+
97
+ def match_existing_fact_type context
98
+ opnds = var_refs
99
+ result_var_ref = VarRef.new(@variable.player.name)
100
+ result_var_ref.player = @variable.player
101
+ result_var_ref.variable = @variable
102
+ @variable.refs << result_var_ref
103
+ clause_ast = Clause.new(
104
+ [result_var_ref, '='] +
105
+ (opnds.size > 1 ? [opnds[0]] : []) +
106
+ [operator, opnds[-1]]
107
+ )
108
+
109
+ # REVISIT: All operands must be value-types or simply-identified Entity Types.
110
+
111
+ # REVISIT: We should auto-create joins from Entity Types to an identifying ValueType
112
+ # REVISIT: We should traverse up the supertype of ValueTypes to find a DataType
113
+ @fact_type = clause_ast.match_existing_fact_type(context, :exact_type => true)
114
+ return @fact_type if @fact_type
115
+
116
+ @fact_type = clause_ast.make_fact_type context.vocabulary
117
+ reading = clause_ast.make_reading context.vocabulary, @fact_type
118
+ rrs = reading.role_sequence.all_role_ref_in_order
119
+ opnds[0].role_ref = rrs[0]
120
+ opnds[-1].role_ref = rrs[-1]
121
+ opnds.each do |opnd|
122
+ next unless opnd.is_a?(Operation)
123
+ opnd.match_existing_fact_type context
124
+ end
125
+ @fact_type
126
+ end
127
+
128
+ def is_existential_type
129
+ false
130
+ end
131
+
132
+ def is_equality_comparison
133
+ false
134
+ end
135
+
136
+ def operator
137
+ raise "REVISIT: Implement operator access in the operator subclass #{self.class.name}"
138
+ end
139
+
140
+ end
141
+
142
+ class Comparison < Operation
143
+ attr_accessor :operator, :e1, :e2, :qualifiers, :conjunction
144
+
145
+ def initialize operator, e1, e2, qualifiers = []
146
+ @operator, @e1, @e2, @qualifiers = operator, e1, e2, qualifiers
147
+ end
148
+
149
+ def var_refs
150
+ [@e1, @e2]
151
+ end
152
+
153
+ def bind context
154
+ var_refs.each do |o|
155
+ o.bind context
156
+ end
157
+
158
+ # REVISIT: Return the projected binding instead:
159
+ return @result = nil if @projection
160
+
161
+ name = 'Boolean'
162
+ @player = result_value_type(context, name)
163
+ key = "#{name} #{object_id}" # Every Comparison result is a unique Variable
164
+ @variable = (context.variables[key] ||= Variable.new(@player))
165
+ @variable.refs << self
166
+ @variable
167
+ end
168
+
169
+ def result_type_name(context)
170
+ "compare#{operator}(#{[@e1,@e2].map{|e| e.player.name}*', '}))"
171
+ end
172
+
173
+ def is_equality_comparison
174
+ @operator == '='
175
+ end
176
+
177
+ def identify_player context
178
+ @player || begin
179
+ if @projection
180
+ raise "REVISIT: The player is the projected expression"
181
+ end
182
+ v = context.vocabulary
183
+ @player = v.constellation.ValueType(v, 'Boolean')
184
+ end
185
+ end
186
+
187
+ =begin
188
+ def project lr
189
+ @projection = lr
190
+ projected_rr = lr == :left ? @e2 : @e1
191
+ true
192
+ end
193
+ =end
194
+
195
+ def inspect; to_s; end
196
+
197
+ def to_s
198
+ "compare#{operator}(#{e1.to_s} #{e2.to_s}#{@qualifiers.empty? ? '' : ', ['+@qualifiers*', '+']'})"
199
+ end
200
+ end
201
+
202
+ class Sum < Operation
203
+ attr_accessor :terms
204
+ def initialize *terms
205
+ @terms = terms
206
+ end
207
+
208
+ def var_refs
209
+ @terms
210
+ end
211
+
212
+ def operator
213
+ '+'
214
+ end
215
+
216
+ def identify_player context
217
+ @player || begin
218
+ # The players in the @terms have already been identified
219
+ # REVISIT: Check compliance of all units in @terms, and apply conversions where necessary
220
+ # REVISIT: The type of this result should be derived from type promotion rules. Here, we take the left-most.
221
+ # REVISIT: We should define a subtype of the result type here, and apply the units to it.
222
+ v = context.vocabulary
223
+ @player = @terms[0].player
224
+ @player
225
+ end
226
+ end
227
+
228
+ def result_type_name(context)
229
+ "sum(#{ @terms.map{|f| f.player.name}*', ' })"
230
+ end
231
+
232
+ =begin
233
+ def result_value_type(context, name)
234
+ # REVISIT: If there are units involved, check compatibility
235
+ vt = super
236
+ vt
237
+ end
238
+ =end
239
+
240
+ def inspect; to_s; end
241
+
242
+ def to_s
243
+ 'sum(' + @terms.map{|term| "#{term.to_s}" } * ' ' + ')'
244
+ end
245
+ end
246
+
247
+ class Product < Operation
248
+ attr_accessor :factors
249
+ def initialize *factors
250
+ @factors = factors
251
+ end
252
+
253
+ def var_refs
254
+ @factors
255
+ end
256
+
257
+ def operator
258
+ '*'
259
+ end
260
+
261
+ def identify_player context
262
+ @player || begin
263
+ # The players in the @factors have already been identified
264
+ # REVISIT: Calculate the units of the result from the units in @factors
265
+ # REVISIT: The type of this result should be derived from type promotion rules. Here, we take the left-most.
266
+ # REVISIT: We should define a subtype of the result type here, and apply the units to it.
267
+ v = context.vocabulary
268
+ @player = @factors[0].player
269
+ end
270
+ end
271
+
272
+ def result_type_name(context)
273
+ "product(#{ @factors.map{|f| f.player.name}*', ' })"
274
+ end
275
+
276
+ =begin
277
+ def result_value_type(context, name)
278
+ vt = super
279
+ # REVISIT: If there are units involved, create the result units
280
+ vt
281
+ end
282
+ =end
283
+
284
+ def inspect; to_s; end
285
+
286
+ def to_s
287
+ 'product(' + @factors.map{|factor| "#{factor.to_s}" } * ' ' + ')'
288
+ end
289
+ end
290
+
291
+ class Reciprocal < Operation
292
+ attr_accessor :divisor
293
+ def initialize divisor
294
+ @divisor = divisor
295
+ end
296
+
297
+ def operator
298
+ '1/'
299
+ end
300
+
301
+ def var_refs
302
+ [@divisor]
303
+ end
304
+
305
+ def identify_player context
306
+ @player || begin
307
+ # The player in @divisor has already been identified
308
+ # REVISIT: Calculate the units of the result from the units in @divisor
309
+ # REVISIT: Do we want integer division?
310
+ v = context.vocabulary
311
+ @player = v.constellation.ValueType(v, 'Real')
312
+ end
313
+ end
314
+
315
+ =begin
316
+ def result_type_name(context)
317
+ raise hell
318
+ end
319
+ =end
320
+
321
+ def inspect; to_s; end
322
+
323
+ def to_s
324
+ "reciprocal(#{factor.to_s})"
325
+ end
326
+ end
327
+
328
+ class Negate
329
+ attr_accessor :term
330
+ def initialize term
331
+ @term = term
332
+ end
333
+
334
+ def operator
335
+ '0-'
336
+ end
337
+
338
+ def identify_player context
339
+ @player || begin
340
+ # The player in @term have already been identified
341
+ v = context.vocabulary
342
+ @player = @term.player
343
+ end
344
+ end
345
+
346
+ =begin
347
+ def result_type_name(context)
348
+ raise hell
349
+ end
350
+ =end
351
+
352
+ def inspect; to_s; end
353
+
354
+ def to_s
355
+ "negate(#{term.to_s})"
356
+ end
357
+ end
358
+
359
+ class Literal
360
+ attr_accessor :literal, :unit, :role, :role_ref, :clause
361
+ attr_reader :objectification_of, :leading_adjective, :trailing_adjective, :value_constraint
362
+
363
+ def initialize literal, unit
364
+ @literal, @unit = literal, unit
365
+ end
366
+
367
+ # Stubs:
368
+ def role_name; nil; end
369
+ def nested_clauses; nil; end
370
+
371
+ def inspect; to_s; end
372
+
373
+ def to_s
374
+ unit ? "(#{@literal.to_s} in #{unit.to_s})" : @literal.to_s
375
+ end
376
+
377
+ def player
378
+ @player
379
+ end
380
+
381
+ def identify_players_with_role_name(context)
382
+ # Nothing to do here, move along
383
+ end
384
+
385
+ def identify_other_players(context)
386
+ identify_player context
387
+ end
388
+
389
+ def identify_player context
390
+ @player || begin
391
+ player_name =
392
+ case @literal
393
+ when String; 'String'
394
+ when Float; 'Real'
395
+ when Numeric; 'Integer'
396
+ when TrueClass, FalseClass; 'Boolean'
397
+ end
398
+ v = context.vocabulary
399
+ @player = v.constellation.ValueType(v, player_name)
400
+ end
401
+ end
402
+
403
+ def bind context
404
+ @variable || begin
405
+ key = "#{@player.name} #{@literal}"
406
+ @variable = (context.variables[key] ||= Variable.new(@player))
407
+ @variable.refs << self
408
+ end
409
+ end
410
+
411
+ def variable
412
+ @variable
413
+ end
414
+ end
415
+
416
+ end
417
+ end
418
+ end