activefacts-cql 1.8.3 → 1.9.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 17b326a796d8f23d4b73ca8af114df35ae8d1cb0
4
- data.tar.gz: 9b99bebe1d49eb09878bb3df01302f7649e58fea
3
+ metadata.gz: 9c904dd901055b4ee41174b3d60a3d16d02c4af0
4
+ data.tar.gz: 8321e5b02ed21d4132878e5d43e556ecd27460fa
5
5
  SHA512:
6
- metadata.gz: 15e06b8dea51d28928b9618f0b3903c6db8721a4695f5682a8d0391f5d48a88851d04e6aa7db215f97b64296e97f0a4a4ab660a549634d5632efbbb96101badf
7
- data.tar.gz: bff64b710592ac5bf3ff9da9b1a99243da09a6dd8c613af478da0b15e363c5bfb7ef08826e4fa16573d0f23f8b63062fe2ea60287f899c9edf93da465c98add0
6
+ metadata.gz: 699900788c5a3e09206a553b0751cce243a2b17edd3b7c7bb6ea59b00fb3177dd458674247631bb90af1a282840b64892862dbd8902f8513a1ad324852a6e6bb
7
+ data.tar.gz: d29b4f20f3fc97a4dfb04366bb2f035f9ae837fcae73d617ce5917903429327be0c1bc2eee0a6b052226cb29949c68b501f8d2412b3bee4bc4488c6ae8c32851
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  /doc/
7
7
  /pkg/
8
8
  /spec/reports/
9
+ /spec/activefacts/cql/actual
9
10
  /tmp/
10
11
  lib/activefacts/cql/parser/[A-Z]*.rb
11
12
  lib/activefacts/cql/parser/Language/[A-Z]*.rb
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency "rake", "~> 10.0"
25
25
  spec.add_development_dependency "rspec", "~> 3.3"
26
26
 
27
- spec.add_runtime_dependency "activefacts-metamodel", "~> 1", ">= 1.9.5"
27
+ spec.add_runtime_dependency "activefacts-metamodel", "~> 1", ">= 1.9.14"
28
28
  spec.add_runtime_dependency "treetop", [">= 1.4.14", "~> 1.4"]
29
29
  end
30
30
 
@@ -14,6 +14,7 @@ require 'activefacts/cql/compiler/fact'
14
14
  require 'activefacts/cql/compiler/constraint'
15
15
  require 'activefacts/cql/compiler/query'
16
16
  require 'activefacts/cql/compiler/informal'
17
+ require 'activefacts/cql/compiler/transform_rule'
17
18
 
18
19
  module ActiveFacts
19
20
  module CQL
@@ -23,30 +24,35 @@ module ActiveFacts
23
24
  'fr' => 'French',
24
25
  'cn' => 'Mandarin'
25
26
  }
27
+ EXTENSIONS = ['fiml', 'fidl', 'fiql', 'cql']
28
+
26
29
  attr_reader :vocabulary
27
30
 
28
- def initialize *a
29
- @filename = a.shift || "stdio"
31
+ def initialize filepath, *a
32
+ @filepath = filepath
30
33
  super *a
31
34
  @constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
32
35
  @constellation.loggers << proc{|*k| trace :apilog, k.inspect} if trace(:apilog)
33
36
  @language = nil
37
+ @pending_import_topic = nil
38
+ @pending_import_role = ''
39
+ @pending_import_file_name = ''
34
40
  trace :file, "Parsing '#{@filename}'"
35
41
  end
36
42
 
37
- def compile_file filename
38
- old_filename = @filename
39
- @filename = filename
40
- File.open(filename) do |f|
43
+ def compile_file filepath
44
+ old_filepath = @filepath
45
+ @filepath = filename
46
+ File.open(filepath) do |f|
41
47
  compile(f.read)
42
48
  end
43
- @filename = old_filename
49
+ @filepath = old_filepath
44
50
  @vocabulary
45
51
  end
46
52
 
47
53
  # Load the appropriate natural language module
48
54
  def detect_language
49
- @filename =~ /.*\.(..)\.cql$/i
55
+ @filepath =~ /.*\.(..)\.cql$/i
50
56
  language_code = $1
51
57
  @language = LANGUAGES[language_code] || 'English'
52
58
  end
@@ -67,6 +73,23 @@ module ActiveFacts
67
73
  end
68
74
  end
69
75
 
76
+ def create_import_if_pending new_topic
77
+ if @pending_import_topic
78
+ trace :import, "Topic #{@pending_import_topic.topic_name} imports #{new_topic.topic_name} as #{@pending_import_role} from file #{@pending_import_file_name}"
79
+
80
+ @constellation.Import(
81
+ topic: @pending_import_topic,
82
+ precursor_topic: new_topic,
83
+ import_role: @pending_import_role,
84
+ file_name: @pending_import_file_name
85
+ )
86
+
87
+ @pending_import_topic = nil
88
+ @pending_import_role = ''
89
+ @pending_import_file_name = ''
90
+ end
91
+ end
92
+
70
93
  def compile input
71
94
  include_language
72
95
 
@@ -87,10 +110,11 @@ module ActiveFacts
87
110
  value = compile_definition ast
88
111
  trace :definition, "Compiled to #{value.is_a?(Array) ? value.map{|v| v.verbalise}*', ' : value.verbalise}" if value
89
112
  if value.is_a?(ActiveFacts::Metamodel::Topic)
90
- topic_flood if @topic
113
+ topic_flood() if @topic
114
+ create_import_if_pending(value)
91
115
  @topic = value
92
116
  elsif ast.is_a?(Compiler::Vocabulary)
93
- topic_flood if @topic
117
+ topic_flood() if @topic
94
118
  @vocabulary = value
95
119
  @topic = @constellation.Topic(@vocabulary.name)
96
120
  end
@@ -104,33 +128,25 @@ module ActiveFacts
104
128
  raise ne
105
129
  end
106
130
  end
107
- topic_flood if @topic
131
+ topic_flood() if @topic
108
132
  end
109
133
  raise failure_reason unless ok
110
134
  vocabulary
111
135
  end
112
136
 
113
- def compile_import file, aliases
137
+ def compile_import file, import_role, aliases
114
138
  saved_index = @index
115
139
  saved_block = @block
116
140
  saved_string = @string
117
141
  saved_input_length = @input_length
118
- saved_topic = @topic
119
- old_filename = @filename
120
- @filename = import_filename(old_filename, file)
142
+ old_filepath = @filepath
143
+ @file = file
144
+ @filepath = import_filepath(old_filepath, file)
121
145
 
122
- # REVISIT: Save and use another @vocabulary for this file?
123
- File.open(@filename) do |f|
124
- topic_flood if @topic
125
- @topic = @constellation.Topic(File.basename(@filename, '.cql'))
126
- trace :import, "Importing #{@filename} as #{@topic.topic_name}" do
127
- ok = parse_all(f.read, nil, &@block)
128
- end
129
- @topic = saved_topic
130
- end
146
+ compile_import_file(@filepath, import_role)
131
147
 
132
148
  rescue => e
133
- ne = StandardError.new("In #{@filename} #{e.message.strip}")
149
+ ne = StandardError.new("In #{@filepath} #{e.message.strip}")
134
150
  ne.set_backtrace(e.backtrace)
135
151
  raise ne
136
152
  ensure
@@ -138,13 +154,52 @@ module ActiveFacts
138
154
  @index = saved_index
139
155
  @input_length = saved_input_length
140
156
  @string = saved_string
141
- @filename = old_filename
157
+ @filepath = old_filepath
142
158
  nil
143
159
  end
144
160
 
145
- # import_filename may be redefined in subclass
146
- def import_filename(old_filename, file)
147
- File.dirname(old_filename)+'/'+file+'.cql'
161
+ # redefine in subsclass for different behaviour
162
+ def import_filepath(old_filepath, file)
163
+ filepath = ''
164
+ EXTENSIONS.each do |extension|
165
+ filepath = File.dirname(old_filepath)+'/'+file+".#{extension}"
166
+ break if File.exist?(filepath)
167
+ end
168
+ filepath
169
+ end
170
+
171
+ # redefine in subsclass for different behaviour
172
+ def compile_import_file filepath, import_role
173
+ # REVISIT: Save and use another @vocabulary for this file?
174
+ File.open(filepath) do |f|
175
+ compile_import_input(f.read, import_role)
176
+ end
177
+ end
178
+
179
+ def compile_import_input input, import_role
180
+ topic_external_name = @file
181
+
182
+ if existing_topic = @constellation.Topic[[topic_external_name]]
183
+ # topic has already been loaded, just build import
184
+ trace :import, "Topic #{@topic.topic_name} has already been loaded, skip reload"
185
+ import = @constellation.Import(
186
+ topic: @topic, precursor_topic: existing_topic,
187
+ import_role: import_role, file_name: topic_external_name
188
+ )
189
+ else
190
+ # topic has not been loaded previously, import topic
191
+ saved_topic = @topic
192
+ topic_flood() if @topic
193
+
194
+ @pending_import_topic = saved_topic
195
+ @pending_import_role = import_role
196
+ @pending_import_file_name = topic_external_name
197
+
198
+ trace :import, "Importing #{@filepath} into #{@topic.topic_name}" do
199
+ ok = parse_all(input, nil, &@block)
200
+ end
201
+ @topic = saved_topic
202
+ end
148
203
  end
149
204
 
150
205
  def compile_definition ast
@@ -22,7 +22,7 @@ module ActiveFacts
22
22
  end
23
23
 
24
24
  def refs
25
- @phrases.select{|r| r.respond_to?(:player)}
25
+ @phrases.select{|r| r.is_a?(ActiveFacts::CQL::Compiler::Reference)}
26
26
  end
27
27
 
28
28
  # A clause that contains only the name of a ObjectType and no literal or reading text
@@ -142,7 +142,7 @@ module ActiveFacts
142
142
  # no change is made to this Clause object - those will be done later.
143
143
  #
144
144
  def match_existing_fact_type context, options = {}
145
- raise "Cannot match a clause that contains no object types" if refs.size == 0
145
+ raise "Cannot match a clause that contains no object types for #{self.inspect}" if refs.size == 0
146
146
  raise "Internal error, clause already matched, should not match again" if @fact_type
147
147
 
148
148
  if is_naked_object_type
@@ -184,7 +184,12 @@ module ActiveFacts
184
184
  contract_left = proc do
185
185
  contracted_from = left_contract_this_onto.refs[0]
186
186
  contraction_player = contracted_from.player
187
- contracted_role = Reference.new(contraction_player.name)
187
+ contracted_role = Reference.new(
188
+ term: contraction_player.name,
189
+ leading_adjective: contracted_from.leading_adjective,
190
+ trailing_adjective: contracted_from.trailing_adjective,
191
+ role_name: contracted_from.role_name
192
+ )
188
193
  supposed_roles << contracted_role
189
194
  left_insertion = contracted_role.inspect+' '
190
195
  contracted_role.player = contracted_from.player
@@ -199,7 +204,12 @@ module ActiveFacts
199
204
  contract_right = proc do
200
205
  contracted_from = left_contract_this_onto.refs[-1]
201
206
  contraction_player = contracted_from.player
202
- contracted_role = Reference.new(contraction_player.name)
207
+ contracted_role = Reference.new(
208
+ term: contraction_player.name,
209
+ leading_adjective: contracted_from.leading_adjective,
210
+ trailing_adjective: contracted_from.trailing_adjective,
211
+ role_name: contracted_from.role_name
212
+ )
203
213
  supposed_roles << contracted_role
204
214
  right_insertion = ' '+contracted_role.inspect
205
215
  contracted_role.player = contracted_from.player
@@ -306,7 +316,7 @@ module ActiveFacts
306
316
 
307
317
  if equal_best.size > 1 and equal_best.detect{|k,m| !m.fact_type.is_a?(Metamodel::TypeInheritance)}
308
318
  # Complain if there's more than one equivalent cost match (unless all are TypeInheritance):
309
- raise "#{@phrases.inspect} could match any of the following:\n\t"+
319
+ trace :matching_fails, "#{@phrases.inspect} could match any of the following:\n\t"+
310
320
  best_matches.map { |reading| reading.expand + " with " + matches[reading].describe } * "\n\t"
311
321
  end
312
322
  end
@@ -317,7 +327,14 @@ module ActiveFacts
317
327
  @fact_type = @side_effects.fact_type
318
328
  trace :matching, "Matched '#{@fact_type.default_reading}'"
319
329
  @phrases = phrases
320
- apply_side_effects(context, @side_effects)
330
+
331
+ # REVISIT: Drop handling side-effects because roles are still required for processing conditions in transformation logic
332
+ # apply_side_effects(context, @side_effects)
333
+ @side_effects.role_side_effects.each do |side_effect|
334
+ phrase = side_effect.phrase
335
+ phrase.role_ref = side_effect.role_ref
336
+ end
337
+
321
338
  return @fact_type
322
339
  end
323
340
 
@@ -757,7 +774,7 @@ module ActiveFacts
757
774
 
758
775
  if @qualifiers && @qualifiers.size > 0
759
776
  # We shouldn't make a new ring constraint if there's already one over this ring.
760
- existing_rcs =
777
+ existing_rcs =
761
778
  @role_sequence.all_role_ref.map{|rr| rr.role.all_ring_constraint.to_a }.flatten.uniq
762
779
  unless existing_rcs[0]
763
780
  rc = RingConstraint.new(@role_sequence, @qualifiers)
@@ -904,6 +921,22 @@ module ActiveFacts
904
921
  }}"
905
922
  end
906
923
 
924
+ def var_name
925
+ if @role_name && !@role_name.is_a?(Integer)
926
+ @role_name
927
+ else
928
+ "#{
929
+ @leading_adjective && @leading_adjective.split.map(&:capitalize).join(' ') + ' '
930
+ }#{
931
+ @term
932
+ }#{
933
+ @trailing_adjective && ' ' + @trailing_adjective.split.map(&:capitalize).join(' ')
934
+ }#{
935
+ @role_name && "(#{@role_name})"
936
+ }"
937
+ end
938
+ end
939
+
907
940
  def <=>(other)
908
941
  ( 4*(@term <=> other.term) +
909
942
  2*((@leading_adjective||'') <=> (other.leading_adjective||'')) +
@@ -1038,7 +1071,7 @@ module ActiveFacts
1038
1071
  end
1039
1072
 
1040
1073
  def find_pc_over_roles(roles)
1041
- return nil if roles.size == 0 # Safeguard; this would chuck an exception otherwise
1074
+ raise "No Role for embedded_presence_constraint" if roles.size == 0 # Safeguard; this would chuck an exception otherwise
1042
1075
  roles[0].all_role_ref.each do |role_ref|
1043
1076
  next if role_ref.role_sequence.all_role_ref.map(&:role) != roles
1044
1077
  pc = role_ref.role_sequence.all_presence_constraint.single # Will return nil if there's more than one.
@@ -1049,7 +1082,7 @@ module ActiveFacts
1049
1082
  end
1050
1083
 
1051
1084
  def make_embedded_presence_constraint vocabulary
1052
- raise "No Role for embedded_presence_constraint" unless @role_ref
1085
+ return unless @role_ref
1053
1086
  fact_type = @role_ref.role.fact_type
1054
1087
  constellation = vocabulary.constellation
1055
1088
 
@@ -1098,6 +1131,15 @@ module ActiveFacts
1098
1131
  def result(context = nil)
1099
1132
  self
1100
1133
  end
1134
+
1135
+ def compile(context)
1136
+ identify_player(context)
1137
+ constellation = context.vocabulary.constellation
1138
+ constellation.Expression(
1139
+ :new, :expression_type => 'Role', :object_type => @player,
1140
+ :leading_adjective => leading_adjective, :trailing_adjective => trailing_adjective
1141
+ )
1142
+ end
1101
1143
  end
1102
1144
 
1103
1145
  # REVISIT: This needs to handle annotations for some/that/which, etc.
@@ -297,7 +297,7 @@ module ActiveFacts
297
297
 
298
298
  # Create a query with a variable for every binding and all steps:
299
299
  query = build_variables(clauses_list)
300
- roles_by_binding = build_all_steps(clauses_list)
300
+ roles_by_binding = build_all_steps(query, clauses_list)
301
301
  query.validate
302
302
 
303
303
  # Create the projected RoleSequence for the constraint:
@@ -195,14 +195,6 @@ module ActiveFacts
195
195
  end
196
196
  end
197
197
 
198
- =begin
199
- def project lr
200
- @projection = lr
201
- projected_rr = lr == :left ? @e2 : @e1
202
- true
203
- end
204
- =end
205
-
206
198
  def inspect; to_s; end
207
199
 
208
200
  def to_s
@@ -222,6 +214,15 @@ module ActiveFacts
222
214
  @qualifiers.empty? ? '' : ', ['+@qualifiers*', '+']'
223
215
  })"
224
216
  end
217
+
218
+ def compile(context)
219
+ op1 = e1.compile(context)
220
+ op2 = e2.compile(context)
221
+ context.vocabulary.constellation.Expression(
222
+ :new, :expression_type => 'Binary', :operator => operator,
223
+ :first_operand_expression => op1, :second_operand_expression => op2
224
+ )
225
+ end
225
226
  end
226
227
 
227
228
  class Sum < Operation
@@ -254,19 +255,29 @@ module ActiveFacts
254
255
  "SUM_OF<#{ @terms.map{|f| f.player.name}*', ' }>"
255
256
  end
256
257
 
257
- =begin
258
- def result_value_type(context, name)
259
- # REVISIT: If there are units involved, check compatibility
260
- vt = super
261
- vt
262
- end
263
- =end
264
-
265
258
  def inspect; to_s; end
266
259
 
267
260
  def to_s
268
261
  'SUM(' + @terms.map{|term| "#{term.to_s}" } * ' PLUS ' + ')'
269
262
  end
263
+
264
+ def compile(context)
265
+ compile_terms(context, @terms)
266
+ end
267
+
268
+ def compile_terms(context, terms)
269
+ if terms.size == 1
270
+ terms[0].compile(context)
271
+ else
272
+ lhs = terms.shift
273
+ lhs_expr = lhs.compile(context)
274
+ rhs_expr = compile_terms(context, terms)
275
+ context.vocabulary.constellation.Expression(
276
+ :new, :expression_type => 'Binary', :operator => operator,
277
+ :first_operand_expression => lhs_expr, :second_operand_expression => rhs_expr
278
+ )
279
+ end
280
+ end
270
281
  end
271
282
 
272
283
  class Product < Operation
@@ -298,19 +309,29 @@ module ActiveFacts
298
309
  "PRODUCT_OF<#{ @factors.map{|f| f.player.name}*' ' }>"
299
310
  end
300
311
 
301
- =begin
302
- def result_value_type(context, name)
303
- vt = super
304
- # REVISIT: If there are units involved, create the result units
305
- vt
306
- end
307
- =end
308
-
309
312
  def inspect; to_s; end
310
313
 
311
314
  def to_s
312
315
  'PRODUCT(' + @factors.map{|factor| "#{factor.to_s}" } * ' TIMES ' + ')'
313
316
  end
317
+
318
+ def compile(context)
319
+ compile_factors(context, @factors)
320
+ end
321
+
322
+ def compile_factors(context, factors)
323
+ if factors.size == 1
324
+ factors[0].compile(context)
325
+ else
326
+ lhs = factors.shift
327
+ lhs_expr = lhs.compile(context)
328
+ rhs_expr = compile_factors(context, factors)
329
+ context.vocabulary.constellation.Expression(
330
+ :new, :expression_type => 'Binary', :operator => operator,
331
+ :first_operand_expression => lhs_expr, :second_operand_expression => rhs_expr
332
+ )
333
+ end
334
+ end
314
335
  end
315
336
 
316
337
  class Reciprocal < Operation
@@ -337,17 +358,19 @@ module ActiveFacts
337
358
  end
338
359
  end
339
360
 
340
- =begin
341
- def result_type_name(context)
342
- raise hell
343
- end
344
- =end
345
-
346
361
  def inspect; to_s; end
347
362
 
348
363
  def to_s
349
364
  "RECIPROCAL(#{factor.to_s})"
350
365
  end
366
+
367
+ def compile(context)
368
+ op1 = @divisor.compile(context)
369
+ context.vocabulary.constellation.Expression(
370
+ :new, :expression_type => 'Unary', :operator => operator,
371
+ :first_operand_expression => op1
372
+ )
373
+ end
351
374
  end
352
375
 
353
376
  class Negate
@@ -368,16 +391,227 @@ module ActiveFacts
368
391
  end
369
392
  end
370
393
 
371
- =begin
394
+ def inspect; to_s; end
395
+
396
+ def to_s
397
+ "NEGATIVE(#{term.to_s})"
398
+ end
399
+
400
+ def compile(context)
401
+ op1 = @term.compile(context)
402
+ context.vocabulary.constellation.Expression(
403
+ :new, :expression_type => 'Unary', :operator => operator,
404
+ :first_operand_expression => op1
405
+ )
406
+ end
407
+ end
408
+
409
+ class Negation
410
+ attr_accessor :term
411
+ def initialize term
412
+ @term = term
413
+ end
414
+
415
+ def operator
416
+ 'not'
417
+ end
418
+
419
+ def identify_player context
420
+ @player || begin
421
+ # The player in @term have already been identified
422
+ v = context.vocabulary
423
+ @player = @term.player
424
+ end
425
+ end
426
+
427
+ def inspect; to_s; end
428
+
429
+ def to_s
430
+ "NEGATION(#{term.to_s})"
431
+ end
432
+
433
+ def compile(context)
434
+ op1 = @term.compile(context)
435
+ context.vocabulary.constellation.Expression(
436
+ :new, :expression_type => 'Unary', :operator => operator,
437
+ :first_operand_expression => op1
438
+ )
439
+ end
440
+ end
441
+
442
+ class LogicalAnd < Operation
443
+ attr_accessor :factors
444
+ def initialize *factors
445
+ @factors = factors
446
+ end
447
+
448
+ def refs
449
+ @factors
450
+ end
451
+
452
+ def operator
453
+ 'and'
454
+ end
455
+
456
+ def identify_player context
457
+ @player || begin
458
+ v = context.vocabulary
459
+ @player = @factors[0].player
460
+ end
461
+ end
462
+
372
463
  def result_type_name(context)
373
- raise hell
464
+ "CONJUNCTION_OF<#{ @factors.map{|f| f.player.name}*' ' }>"
374
465
  end
375
- =end
376
466
 
377
467
  def inspect; to_s; end
378
468
 
379
469
  def to_s
380
- "NEGATIVE(#{term.to_s})"
470
+ 'CONJUNCTION(' + @factors.map{|factor| "#{factor.to_s}" } * ' AND ' + ')'
471
+ end
472
+
473
+ def compile(context)
474
+ compile_factors(context, @factors)
475
+ end
476
+
477
+ def compile_factors(context, factors)
478
+ if factors.size == 1
479
+ factors[0].compile(context)
480
+ else
481
+ lhs = factors.shift
482
+ lhs_expr = lhs.compile(context)
483
+ rhs_expr = compile_factors(context, factors)
484
+ context.vocabulary.constellation.Expression(
485
+ :new, :expression_type => 'Binary', :operator => operator,
486
+ :first_operand_expression => lhs_expr, :second_operand_expression => rhs_expr
487
+ )
488
+ end
489
+ end
490
+ end
491
+
492
+ class LogicalOr < Operation
493
+ attr_accessor :factors
494
+ def initialize *factors
495
+ @factors = factors
496
+ end
497
+
498
+ def refs
499
+ @factors
500
+ end
501
+
502
+ def operator
503
+ 'or'
504
+ end
505
+
506
+ def identify_player context
507
+ @player || begin
508
+ v = context.vocabulary
509
+ @player = @factors[0].player
510
+ end
511
+ end
512
+
513
+ def result_type_name(context)
514
+ "DISJUNCTION_OF<#{ @factors.map{|f| f.player.name}*' ' }>"
515
+ end
516
+
517
+ def inspect; to_s; end
518
+
519
+ def to_s
520
+ 'DISJUNCTION(' + @factors.map{|factor| "#{factor.to_s}" } * ' OR ' + ')'
521
+ end
522
+
523
+ def compile(context)
524
+ compile_factors(context, @factors)
525
+ end
526
+
527
+ def compile_factors(context, factors)
528
+ if factors.size == 1
529
+ factors[0].compile(context)
530
+ else
531
+ lhs = factors.shift
532
+ lhs_expr = lhs.compile(context)
533
+ rhs_expr = compile_factors(context, factors)
534
+ context.vocabulary.constellation.Expression(
535
+ :new, :expression_type => 'Binary', :operator => operator,
536
+ :first_operand_expression => lhs_expr, :second_operand_expression => rhs_expr
537
+ )
538
+ end
539
+ end
540
+ end
541
+
542
+ class Ternary < Operation
543
+ attr_accessor :condition, :true_value, :false_value
544
+ def initialize condition, true_value, false_value
545
+ @condition = condition
546
+ @true_value = true_value
547
+ @false_value = false_value
548
+ end
549
+
550
+ def refs
551
+ [@condition, @true_value, @false_value]
552
+ end
553
+
554
+ def operator
555
+ '?'
556
+ end
557
+
558
+ def identify_player context
559
+ @player || begin
560
+ v = context.vocabulary
561
+ @player = @true_value.player
562
+ end
563
+ end
564
+
565
+ def inspect; to_s; end
566
+
567
+ def to_s
568
+ "TERNARY(#{@condition.to_s}, #{@true_value.to_s}, #{@false_value.to_s})"
569
+ end
570
+
571
+ def compile(context)
572
+ op1 = @condition.compile(context)
573
+ op2 = @true_value.compile(context)
574
+ op3 = @false_value.compile(context)
575
+ context.vocabulary.constellation.Expression(
576
+ :new, :expression_type => 'Ternary', :operator => operator,
577
+ :first_operand_expression => op1, :second_operand_expression => op2, :third_operand_expression => op3
578
+ )
579
+ end
580
+ end
581
+
582
+ class Aggregate < Operation
583
+ attr_accessor :operation, :aggregand
584
+ def initialize operation, aggregand
585
+ @operation = operation
586
+ @aggregand = aggregand
587
+ end
588
+
589
+ def refs
590
+ [@operation, @aggregand]
591
+ end
592
+
593
+ def operator
594
+ operation
595
+ end
596
+
597
+ def identify_player context
598
+ @player || begin
599
+ @player = @aggregand.player
600
+ end
601
+ end
602
+
603
+ def inspect; to_s; end
604
+
605
+ def to_s
606
+ "AGGREGATE(#{@operation.to_s}, #{@aggregand.to_s})"
607
+ end
608
+
609
+ def compile(context)
610
+ op1 = @aggregand.compile(context)
611
+ context.vocabulary.constellation.Expression(
612
+ :new, :expression_type => 'Unary', :operator => operator,
613
+ :first_operand_expression => op1
614
+ )
381
615
  end
382
616
  end
383
617
 
@@ -436,8 +670,19 @@ module ActiveFacts
436
670
  def binding
437
671
  @binding
438
672
  end
673
+
674
+ def compile(context)
675
+ literal_string = case @literal
676
+ when String; "'#{@literal.to_s}'"
677
+ else @literal.to_s
678
+ end
679
+ context.vocabulary.constellation.Expression(
680
+ :new, :expression_type => 'Literal', :literal_string => literal_string
681
+ )
682
+ end
439
683
  end
440
684
 
441
685
  end
686
+
442
687
  end
443
688
  end