activefacts-cql 1.8.3 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
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