sfp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1836 @@
1
+ # 07-01-2013
2
+ # - disable removing "immutable" variables because the method has bugs
3
+ # - allow using "parent" in reference of constraint or effect
4
+ #
5
+ # 31-10-2012
6
+ # - rename primitive types: $.Boolean, $.Integer, $.String
7
+ # - set SFp to only recognise integer value
8
+ # - enable primitive type as procedure's parameter
9
+ #
10
+ # 14-10-2012
11
+ # - find and reduce the domain of immutable variables
12
+ #
13
+ # 12-10-2012
14
+ # - support CLASS enumerator
15
+ #
16
+ # 01-09-2012
17
+ # - support IN/NOT-IN SET constraint
18
+ # - support ARRAY data-structure, index operator
19
+ #
20
+ # 29-08-2012
21
+ # - support EQUALS, NOT-EQUALS, AND, OR, IF-THEN constraints
22
+ #
23
+ # 23-08-2012
24
+ # - grounding procedure's parameters to produce operator
25
+ # - normalize and apply global constraint to all operators
26
+ #
27
+ # 01-07-2012
28
+ # - collecting classes and variables
29
+ # - foreach subclasses, inherits attributes and procedures from superclass
30
+ # - foreach objects, inherits attributes and procedures from class
31
+
32
+ module Sfp
33
+
34
+ class TranslationException < Exception; end
35
+
36
+ # include 'Sas' module to enable processing Sfp into Finite-Domain Representation (FDR)
37
+ #
38
+ # TODO:
39
+ # - nested reference on right-side statement (value of EQUALS/NOT-EQUALS)
40
+ # - a first-order logic formula
41
+ # - enumeration of values of particular type
42
+ # - SET abstract data-type, membership operators
43
+ module SasTranslator
44
+ GlobalOperator = '-globalop-'
45
+ GlobalVariable = '_global_var'
46
+ SometimeOperatorPrefix = '-sometime-'
47
+ SometimeVariablePrefix = '-sometime-'
48
+ GoalOperator = '-goal-'
49
+ GoalVariable = '-goal-'
50
+
51
+ GlobalConstraintMethod = 1 # 1: proposed method, 2: patrik's, 3: concurrent-actions
52
+
53
+ attr_accessor :root
54
+ attr_reader :variables, :types, :operators, :axioms, :goals
55
+
56
+ def to_sas
57
+ begin
58
+ @unknown_value = ::Sfp::Unknown.new
59
+
60
+ @arrays = Hash.new
61
+ if @parser_arrays != nil
62
+ @parser_arrays.each do |k,v|
63
+ first, rest = k.explode[1].explode
64
+ next if rest == nil
65
+ @arrays['$.' + rest.to_s] = v
66
+ end
67
+ end
68
+
69
+ return nil if @root == nil
70
+ return nil if not @root.has_key?('initial') or not @root.has_key?('goal')
71
+
72
+ @variables = Hash.new
73
+ @types = { '$.Boolean' => [true, false],
74
+ '$.Integer' => Array.new,
75
+ '$.String' => Array.new
76
+ }
77
+ @operators = Hash.new
78
+ @axioms = Array.new
79
+
80
+ @g_operators = []
81
+
82
+ # collect classes
83
+ #self.collect_classes
84
+
85
+ # unlink 'initial', 'goal', 'global' with root
86
+ @root['initial'].delete('_parent')
87
+ @root['goal'].delete('_parent')
88
+ if @root['goal'].has_key?('always')
89
+ @root['global'] = @root['goal']['always']
90
+ @root['goal'].delete('always')
91
+ @root['global']['_self'] = 'global'
92
+ @root['global']['_type'] = 'and'
93
+ end
94
+ @root['global'].delete('_parent') if @root.has_key?('global')
95
+
96
+ if @root['goal'].has_key?('sometime')
97
+ @root['sometime'] = @root['goal']['sometime']
98
+ @root['goal'].delete('sometime')
99
+ @root['sometime']['_type'] = 'or'
100
+ @root['sometime'].delete('_parent')
101
+ end
102
+
103
+ if @root['goal'].has_key?('sometime-after')
104
+ @root['sometime-after'] = @root['goal']['sometime-after']
105
+ @root['goal'].delete('sometime')
106
+ @root['sometime-after'].delete('_parent')
107
+ end
108
+
109
+ @root['initial'].accept(Sfp::Visitor::ReferenceModifier.new)
110
+ @root['goal'].accept(Sfp::Visitor::ReferenceModifier.new) if @root.has_key?('goal')
111
+ @root['global'].accept(Sfp::Visitor::ReferenceModifier.new) if @root.has_key?('global')
112
+ #@root['sometime'].accept(ReferenceModifier.new)
113
+ #@root['sometime-after'].accept(ReferenceModifier.new)
114
+
115
+ ### collect variables ###
116
+ @root['initial'].accept(VariableCollector.new(self))
117
+
118
+ ### collect values ###
119
+ # collect values from goal constraint
120
+ value_collector = Sfp::SasTranslator::ValueCollector.new(self)
121
+ @root['goal'].accept(value_collector) if @root.has_key?('goal') and
122
+ @root['goal'].isconstraint
123
+ # collect values from global constraint
124
+ @root['global'].accept(value_collector) if @root.has_key?('global') and
125
+ @root['global'].isconstraint
126
+ # collect values from sometime constraint
127
+ @root['sometime'].accept(value_collector) if @root.has_key?('sometime')
128
+
129
+ # remove duplicates from type's set of value
130
+ @types.each_value { |type| type.uniq! }
131
+
132
+ # set domain values for each variable
133
+ self.set_variable_values
134
+
135
+ # identify immutable variables
136
+ #self.identify_immutable_variables
137
+
138
+ # re-evaluate set variables and types
139
+ self.evaluate_set_variables_and_types
140
+
141
+ ### process goal constraint ###
142
+ process_goal(@root['goal']) if @root.has_key?('goal') and
143
+ @root['goal'].isconstraint
144
+
145
+ ### process global constrait
146
+ self.process_global_constraint
147
+
148
+ ### normalize sometime formulae ###
149
+ if @root.has_key?('sometime')
150
+ raise TranslationException, 'Invalid sometime constraint' if
151
+ not normalize_formula(@root['sometime'])
152
+ end
153
+
154
+ ### process all procedures
155
+ @variables.each_value { |var|
156
+ if var.is_final
157
+ var.init.each { |k,v|
158
+ if v.is_a?(Hash) and v.isprocedure
159
+ process_procedure(v, var.init)
160
+ end
161
+ }
162
+ end
163
+ }
164
+ self.reset_operators_name
165
+
166
+ ### process sometime modalities ###
167
+ self.process_sometime if @root.has_key?('sometime')
168
+ ### process sometime-after modalities ###
169
+ self.process_sometime_after if @root.has_key?('sometime-after')
170
+
171
+ # detect and merge mutually inclusive operators
172
+ self.search_and_merge_mutually_inclusive_operators
173
+
174
+ self.add_unknown_value_to_nonstatic_variables
175
+
176
+ #self.dump_types
177
+ #self.dump_vars
178
+ #self.dump_operators
179
+
180
+ @vars = @variables.values
181
+
182
+ return create_output
183
+ rescue Exception => e
184
+ raise e
185
+ end
186
+ end
187
+
188
+ def variable_name_and_value(var_id, value_index)
189
+ i = @vars.index { |v| v.id == var_id }
190
+ var = @vars[i]
191
+ return nil, nil if var.nil?
192
+ return var.name, nil if value_index >= var.length or
193
+ value_index < 0
194
+ value = var[value_index]
195
+ if value.is_a?(Hash)
196
+ return var.name, value.ref if value.isobject
197
+ return var.name, nil
198
+ else
199
+ return var.name, value
200
+ end
201
+ end
202
+
203
+ def search_and_merge_mutually_inclusive_operators
204
+ return if GlobalConstraintMethod != 3
205
+ last = @g_operators.length-1
206
+ @g_operators.each_index do |i|
207
+ op1 = @g_operators[i]
208
+ for j in i+1..last
209
+ op2 = @g_operators[j]
210
+ next if op1.modifier_id != op2.modifier_id or op1.conflict?(op2)
211
+ next if not (op1.supports?(op2) and op2.supports?(op1))
212
+ if op1.supports?(op2) and op2.supports?(op1)
213
+ op = op1.merge(op2)
214
+ op.modifier_id = op1.modifier_id
215
+ op1 = op
216
+ end
217
+ end
218
+ @operators[op1.name] = op1 if op1 != @g_operators[i]
219
+ end
220
+ end
221
+
222
+ def evaluate_set_variables_and_types
223
+ @variables.each_value do |var|
224
+ next if not var.isset
225
+ new_values = []
226
+ var.each { |x| new_values << x['_values'] if x.is_a?(Hash) and x.isset }
227
+ new_values.each { |x| var << x }
228
+ #var.delete_if { |x| x.nil? or x == '' }
229
+ var.delete_if { |x| x.nil? or x == '' or (x.is_a?(Hash) and x.isset) }
230
+ var.each { |x| x.sort! }
231
+ var.uniq!
232
+
233
+ var.init = var.init['_values'] if var.init.is_a?(Hash) and var.init.isset
234
+ var.goal = var.goal['_values'] if var.goal.is_a?(Hash) and var.goal.isset
235
+ end
236
+
237
+ @types.each do |name,values|
238
+ next if name[0,1] != '('
239
+ new_values = []
240
+ values.each { |x| new_values << x['_values'] if x.is_a?(Hash) and x.isset }
241
+ new_values.each { |x| values << x }
242
+ values.delete_if { |x| x.nil? or x == '' or (x.is_a?(Hash) and x.isset) }
243
+ #values.delete_if { |x| x == nil or x == '' }
244
+ values.each { |x| x.sort! }
245
+ values.uniq!
246
+ end
247
+ end
248
+
249
+ # Find immutable variables -- variables that will never be affected with any
250
+ # actions. Then reduce the size of their domain by 1 only i.e. the possible
251
+ # value is only their initial value.
252
+ # BUGGY! -- operator's effects may contain other object's variable
253
+ def identify_immutable_variables
254
+ def is_this(ref)
255
+ ref.length > 7 and (ref[0,7] == '$.this.' or ref[0,7] == '$.self.')
256
+ end
257
+
258
+ mutables = {}
259
+ @variables.each_key { |k| mutables[k] = false }
260
+ @variables.each_value do |var|
261
+ next if not var.is_final
262
+ var.init.each do |k1,v1|
263
+ next if k1[0,1] == '_' or
264
+ not v1.is_a?(Hash) or
265
+ not v1.isprocedure
266
+ v1['_effect'].each do |k2,v2|
267
+ next if k2[0,1] == '_' or not k2.isref
268
+ if is_this(k2)
269
+ vname = var.name + k2[6..k2.length-1]
270
+ mutables[vname] = true
271
+ else
272
+ # TODO
273
+ end
274
+ end
275
+ end
276
+ end
277
+ mutables.each do |vname, is_mutable|
278
+ var = @variables[vname]
279
+ if var != nil and not var.is_final and (not is_mutable)
280
+ var.clear
281
+ var << var.init
282
+ var.mutable = false
283
+ end
284
+ end
285
+ end
286
+
287
+ def process_global_constraint
288
+ ### normalize global constraint formula ###
289
+ if @root.has_key?('global') and @root['global'].isconstraint
290
+ raise TranslationException, 'Invalid global constraint' if
291
+ not normalize_formula(@root['global'], true)
292
+
293
+ if GlobalConstraintMethod == 1
294
+ # dummy variable
295
+ @global_var = Variable.new(GlobalVariable, '$.Boolean', -1, false, true)
296
+ @global_var << true
297
+ @global_var << false
298
+ @variables[@global_var.name] = @global_var
299
+ # dummy operator
300
+ eff = Parameter.new(@global_var, false, true)
301
+ if @root['global']['_type'] == 'and'
302
+ op = Operator.new(GlobalOperator, 0)
303
+ op[eff.var.name] = eff
304
+ @operators[op.name] = op
305
+ else
306
+ index = 0
307
+ @root['global'].each do |name,constraint|
308
+ next if name[0,1] == '_'
309
+ map_cond = and_equals_constraint_to_map(constraint)
310
+ op = Operator.new(GlobalOperator + index.to_s, 0)
311
+ op[eff.var.name] = eff
312
+ map_cond.each do |k,v|
313
+ next if k[0,1] == '_'
314
+ raise VariableNotFoundException, k if not @variables.has_key?(k)
315
+ op[@variables[k].name] = Parameter.new(@variables[k], v)
316
+ end
317
+ @operators[op.name] = op
318
+ end
319
+ end
320
+ end
321
+ end
322
+ end
323
+
324
+ def process_sometime
325
+ @root['sometime'].each do |k,v|
326
+ next if k[0,1] == '_'
327
+ # dummy-variable
328
+ var = Variable.new(SometimeVariablePrefix + k, '$.Boolean', -1, false, true)
329
+ var << true
330
+ var << false
331
+ @variables[var.name] = var
332
+ # dummy-operator
333
+ op = Operator.new(SometimeOperatorPrefix + k, 0)
334
+ eff = Parameter.new(var, false, true)
335
+ op[eff.var.name] = eff
336
+ map = and_equals_constraint_to_map(v)
337
+ map.each { |k1,v1|
338
+ op[@variables[k1].name] = Parameter.new(@variables[k1], v1, nil)
339
+ }
340
+ @operators[op.name] = op
341
+ end
342
+ end
343
+
344
+ def process_sometime_after
345
+ # TODO
346
+ @root['sometime-after'].each do |k,v|
347
+ next if k[0,1] == '_'
348
+ # dummy-variable
349
+ var = Variable.new('sometime_after_' + k, '$.Boolean', -1, true, true)
350
+ var << true
351
+ var << false
352
+ @variables[var.name] = var
353
+ # normalize formula
354
+
355
+ # dummy-operator
356
+ op = Operator.new('-sometime_after_activate-' + k, 0)
357
+ eff = Parameter.new(var, true, false)
358
+ op[eff.var.name] = eff
359
+ end
360
+ end
361
+
362
+ def process_goal(goal)
363
+ raise TranslationException, 'invalid goal constraint' if not normalize_formula(goal)
364
+ @goals = []
365
+ if goal['_type'] == 'and'
366
+ map = and_equals_constraint_to_map(goal)
367
+ map.each { |name,value|
368
+ var = @variables[name]
369
+ value = @types[var.type][0] if value.is_a?(Hash) and value.isnull
370
+ value = @root['initial'].at?(value) if value.is_a?(String) and value.isref
371
+ var.goal = value
372
+ if not var.mutable
373
+ var.init = var.goal
374
+ var.clear
375
+ var << var.init
376
+ end
377
+ }
378
+ @goals << map
379
+ elsif goal['_type'] == 'or'
380
+ count = 0
381
+ var = Variable.new(GoalVariable, '$.Boolean', -1, false, true)
382
+ var << true
383
+ var << false
384
+ @variables[var.name] = var
385
+ eff = Parameter.new(var, false, true)
386
+
387
+ goal.each { |k,g|
388
+ next if k[0,1] == '_'
389
+ op = Operator.new("#{GoalOperator}#{count}", 0)
390
+ op[var.name] = eff
391
+ map = and_equals_constraint_to_map(g)
392
+ map.each { |k1,v1|
393
+ var = @variables[k1]
394
+ op[@variables[k1].name] = Parameter.new(@variables[k1], v1, nil)
395
+ }
396
+ @operators[op.name] = op
397
+ @goals << map
398
+ }
399
+ end
400
+ end
401
+
402
+ def create_output
403
+ # version
404
+ out = "begin_version\n3\nend_version\n"
405
+ # metric
406
+ out += "begin_metric\n1\nend_metric\n"
407
+ # variables
408
+ variable_index = @variables.keys
409
+ variable_index.sort!
410
+ out += "#{variable_index.length}\n"
411
+ variable_index.each { |i|
412
+ @variables[i].id = variable_index.index(i) # set variable's index
413
+ out += @variables[i].to_sas(@root['initial']) + "\n"
414
+ }
415
+ # mutex
416
+ out += "0\n"
417
+ # initial state
418
+ out += "begin_state\n"
419
+ #pre = ( (pre.is_a?(Hash) and pre.isnull) ? 0 : (pre == nil ? -1 : @var.index(pre)) )
420
+ variable_index.each { |i|
421
+ if @variables[i].init.is_a?(Hash) and @variables[i].init.isnull
422
+ out += "0\n"
423
+ elsif @variables[i].init.is_a?(::Sfp::Unknown)
424
+ out += "#{@variables[i].length-1}\n"
425
+ else
426
+ val = @variables[i].index(@variables[i].init).to_s
427
+ out += "#{val}\n"
428
+ if val.length <= 0
429
+ raise TranslationException,
430
+ "Unknown init: #{@variables[i].name} = #{@variables[i].init.inspect}"
431
+ end
432
+ end
433
+ }
434
+ out += "end_state\n"
435
+ # goal
436
+ out += "begin_goal\n"
437
+ count = 0
438
+ goal = ''
439
+ variable_index.each { |i|
440
+ if @variables[i].goal != nil
441
+ goal += variable_index.index(i).to_s + ' ' +
442
+ @variables[i].index(@variables[i].goal).to_s + "\n"
443
+ count += 1
444
+ end
445
+ }
446
+ out += "#{count}\n#{goal}end_goal\n"
447
+ # operators
448
+ #out += "#{@operators.length}\n"
449
+ ops = ''
450
+ total = 0
451
+ @operators.each_value { |op|
452
+ next if op.total_preposts <= 0
453
+ # HACK! - an exception may arise if a value in condition or effect is not in variable's domain
454
+ begin
455
+ ops += op.to_sas(@root['initial'], @variables) + "\n"
456
+ total += 1
457
+ rescue Exception => exp
458
+ end
459
+ }
460
+ out += "#{total}\n"
461
+ out += ops
462
+ # axioms
463
+ out += "0"
464
+
465
+ return out
466
+ end
467
+
468
+ def reset_operators_name
469
+ Sfp::SasTranslator.reset_operator_id
470
+ ops = Hash.new
471
+ @operators.each_value { |op|
472
+ op.id = Sfp::SasTranslator.next_operator_id
473
+ @operators.delete(op.name)
474
+ op.update_name
475
+ ops[op.name] = op
476
+ }
477
+ @operators = ops
478
+ end
479
+
480
+ def self.reset_operator_id
481
+ @@op_id = -1
482
+ end
483
+
484
+ # return the next operator's id
485
+ def self.next_operator_id
486
+ return (@@op_id += 1) if defined?(@@op_id) != nil
487
+ @@op_id = 0
488
+ return @@op_id
489
+ end
490
+
491
+ # return the next constraint's id
492
+ def self.next_constraint_id
493
+ return (@@constraint_id += 1) if defined?(@@constraint_id) != nil
494
+ @@constraint_id = 0
495
+ return @@constraint_id
496
+ end
497
+
498
+ def self.next_constraint_key
499
+ return 'constraint_' + next_constraint_id.to_s
500
+ end
501
+
502
+ # return the next variable's id
503
+ def self.next_variable_id
504
+ return (@@variable_id += 1) if defined?(@@variable_id) != nil
505
+ @@variable_id = 0
506
+ return @@variable_id
507
+ end
508
+
509
+ # return the next axiom's id
510
+ def self.next_axiom_id
511
+ return (@@axiom_id += 1) if defined?(@@axiom_id) != nil
512
+ @@axiom_id = 0
513
+ return @@axiom_id
514
+ end
515
+
516
+
517
+ # process the conditions of an operator and return all possible set
518
+ # of conditions
519
+ def process_conditions(cond)
520
+ map = Hash.new
521
+ cond.each { |k,v|
522
+ next if k[0,1] == '_'
523
+ if v['_type'] == 'equals'
524
+ map[k] = v['_value']
525
+ end
526
+ }
527
+ return map
528
+ end
529
+
530
+ def and_equals_constraint_to_map(constraint)
531
+ map = Hash.new
532
+ constraint.each { |k,v|
533
+ next if k[0,1] == '_'
534
+ map[k] = v['_value'] if v['_type'] == 'equals'
535
+ }
536
+ return map
537
+ end
538
+
539
+ # process the effects of operator and return all possible sets
540
+ # of effects
541
+ def process_effects(eff)
542
+ map = Hash.new
543
+ eff.each { |k,v|
544
+ next if k[0,1] == '_'
545
+ if v['_type'] = 'equals'
546
+ map[k] = v['_value']
547
+ end
548
+ }
549
+ return map
550
+ end
551
+
552
+ # process given operator
553
+ def process_operator(op)
554
+ # return if given operator is not valid
555
+ # - method "normalize_formula" return false
556
+ # - there's an exception during normalization process
557
+ begin
558
+ return if not normalize_formula(op['_condition'])
559
+ rescue Exception => exp
560
+ return
561
+ end
562
+ # at this step, the conditions formula has been normalized (AND/OR tree)
563
+ # AND conditions
564
+ if op['_condition']['_type'] == 'and'
565
+ process_grounded_operator(op, op['_condition'], op['_effect'])
566
+ else
567
+ op['_condition'].each { |k,v|
568
+ next if k[0,1] == '_'
569
+ process_grounded_operator(op, v, op['_effect'])
570
+ }
571
+ end
572
+ end
573
+
574
+ # process grounded operator (no parameter exists)
575
+ def process_grounded_operator(op, conditions, effects)
576
+ map_cond = and_equals_constraint_to_map(conditions)
577
+ map_eff = and_equals_constraint_to_map(effects)
578
+ keys = map_cond.keys.concat(map_eff.keys)
579
+
580
+ # combine map_cond & map_eff if any of them has >1 items
581
+ op_id = Sfp::SasTranslator.next_operator_id
582
+ sas_op = Operator.new(op.ref, op['_cost'])
583
+ sas_op.params = op['_parameters']
584
+
585
+ keys.each { |k|
586
+ return if not @variables.has_key?(k)
587
+ pre = (!map_cond.has_key?(k) ? nil : map_cond[k])
588
+ #pre = @root['initial'].at?(pre) if pre.is_a?(String) and pre.isref
589
+ post = (!map_eff.has_key?(k) ? nil : map_eff[k])
590
+ #post = @root['initial'].at?(post) if post.is_a?(String) and post.isref
591
+ sas_op[@variables[k].name] = Parameter.new(@variables[k], pre, post)
592
+ }
593
+
594
+ if GlobalConstraintMethod == 1
595
+ @operators[sas_op.name] = sas_op if apply_global_constraint_method_1(sas_op)
596
+ elsif GlobalConstraintMethod == 2 or GlobalConstraintMethod == 3
597
+ @operators[sas_op.name] = sas_op if apply_global_constraint_method_2(sas_op)
598
+ end
599
+ end
600
+
601
+ =begin
602
+ def substitute_goal_value_in_effects(procedure)
603
+ effects = procedure['_effect']
604
+ effects.each_key do |key|
605
+ effects[key]
606
+ end
607
+ end
608
+ =end
609
+
610
+ # process all object procedures in order to get
611
+ # grounded SAS-operator
612
+ def process_procedure(procedure, object)
613
+ #substitute_goal_value_in_effects(procedure)
614
+
615
+ operators = ground_procedure_parameters(procedure)
616
+ if operators != nil
617
+ invalid_operators = []
618
+ operators.each do |op|
619
+ #begin
620
+ process_operator(op)
621
+ #rescue UndefinedValueException
622
+ # invalid_operators << op
623
+ # puts "TODO -- invalid operator: " + op['_self'].to_s
624
+ #end
625
+ end
626
+ #operators.delete_if { |op| not invalid_operators.index(op).nil? }
627
+ else
628
+ #puts 'proc: ' + procedure.ref + ' cannot be grounded'
629
+ end
630
+ # remove the procedure because we don't need it anymore
631
+ object.delete(procedure['_self'])
632
+ end
633
+
634
+ # determine all possible sets of parameters' value
635
+ # and create an operator for each set
636
+ def ground_procedure_parameters(procedure)
637
+ params = Hash.new
638
+ procedure.each { |k,v|
639
+ next if k[0,1] == '_'
640
+ # if the specified parameter does not have any value,
641
+ # then it's invalid procedure
642
+ if not @types.has_key?( v['_isa'] )
643
+ return nil
644
+ end
645
+ params[k] = Array.new
646
+ type = (v.isnull ? v['_isa'] : (v.isset ? "(#{v['_isa']})" : nil))
647
+ next if type == nil
648
+ raise TypeNotFoundException, type if not @types.has_key?(type)
649
+ @types[ type ].each { |val| params[k] << val if not (val.is_a?(Hash) and val.isnull) }
650
+ #puts k.to_s + ": " + params[k].length.to_s
651
+ }
652
+ # combinatorial method for all possible values of parameters
653
+ # using recursive method
654
+ def combinator(bucket, grounder, procedure, names, params, selected, index)
655
+ if index >= names.length
656
+ p = Sfp::Helper.deep_clone(procedure) # procedure.clone
657
+ # grounding all references
658
+ selected['$.this'] = procedure['_parent'].ref
659
+ selected.each { |k,v| selected[k] = (v.is_a?(Hash) ? v.ref : v) }
660
+ grounder.map = selected
661
+ p['_condition'].accept(grounder)
662
+ p['_effect'].accept(grounder)
663
+ p['_context'] = 'operator'
664
+ p['_parameters'] = selected.clone
665
+ # remove parameters because it's already grounded
666
+ p.each { |k,v| p.delete(k) if k[0,1] != '_' }
667
+ bucket << p
668
+ else
669
+ params[ names[index] ].each { |val|
670
+ ref = '$.' + names[index]
671
+ selected[ref] = val
672
+ combinator(bucket, grounder, procedure, names, params, selected, index+1)
673
+ selected.delete(ref)
674
+ }
675
+ end
676
+ end
677
+ bucket = Array.new
678
+ grounder = ParameterGrounder.new(@root['initial'])
679
+ combinator(bucket, grounder, procedure, params.keys, params, Hash.new, 0)
680
+ return bucket
681
+ end
682
+
683
+ def dump_types
684
+ puts '--- types'
685
+ @types.each { |name,values|
686
+ next if values == nil
687
+ print name + ": "
688
+ values.each { |val|
689
+ if val.is_a?(Hash)
690
+ print (val.isnull ? 'null ' : val.ref + " ")
691
+ else
692
+ print val.to_s + " "
693
+ end
694
+ }
695
+ puts '| ' + values.length.to_s
696
+ }
697
+ end
698
+
699
+ def dump_vars
700
+ puts '--- variables'
701
+ @variables.each_value { |value| puts value.to_s }
702
+ end
703
+
704
+ def dump_operators
705
+ puts '--- operators'
706
+ @operators.each_value { |op| puts op.to_s + ' -- ' + op.params.inspect }
707
+ end
708
+
709
+ def dump_axioms
710
+ puts '--- axioms'
711
+ @axioms.each { |ax| puts ax.to_s }
712
+ end
713
+
714
+ # set possible values for each variable
715
+ def set_variable_values
716
+ @variables.each_value { |var|
717
+ var.clear
718
+ if not var.is_final
719
+ @types[var.type].each { |v| var << v }
720
+ else
721
+ var << var.init
722
+ end
723
+ }
724
+ end
725
+
726
+ def add_unknown_value_to_nonstatic_variables
727
+ @variables.each_value { |variable|
728
+ next if variable.is_final
729
+ variable << @unknown_value
730
+ }
731
+ end
732
+
733
+ def apply_global_constraint_method_1(operator)
734
+ return true if not @root.has_key?('global') or not @root['global'].isconstraint
735
+ operator[@global_var.name] = Parameter.new(@global_var, true, false)
736
+ end
737
+
738
+ # return true if global constraint could be applied, otherwise false
739
+ def apply_global_constraint_method_2(operator)
740
+ return true if not @root.has_key?('global') or #@root['global'] == nil or
741
+ not @root['global'].isconstraint
742
+
743
+ # return true if operator's effect is consistent with "left := right"
744
+ def consistent_with_equals(left, right, operator)
745
+ return false if operator.has_key?(left) and operator[left].post != nil and
746
+ operator[left].post != right['_value']
747
+ return true
748
+ end
749
+
750
+ # return true if operator's effect is consistent with given 'and' constraint
751
+ def consistent_with_and(constraint, operator)
752
+ constraint.each { |k,v|
753
+ next if k[0,1] == '_'
754
+ return false if not consistent_with_equals(k, v, operator)
755
+ }
756
+ return true
757
+ end
758
+
759
+ # global constraint is AND formula
760
+ return consistent_with_and(@root['global'], operator) if
761
+ @root['global']['_type'] == 'and'
762
+
763
+ # global constraint is OR formula
764
+ total = 0
765
+ satisfied = Array.new
766
+ @root['global'].each { |k,v|
767
+ next if k[0,1] == '_'
768
+ total += 1
769
+ satisfied << k if consistent_with_and(v, operator)
770
+ }
771
+ if satisfied.length < total
772
+ # partial satisfaction or OR formula
773
+ satisfied.each { |key|
774
+ # create an operator for each satisfied sub-formula
775
+ op = operator.clone
776
+ op.modifier_id = key
777
+ map_cond = and_equals_constraint_to_map(@root['global'][key])
778
+ map_cond.each { |k,v|
779
+ next if k[0,1] == '_'
780
+ raise VariableNotFoundException, 'Variable not found: ' + k if
781
+ not @variables.has_key?(k)
782
+ if not op.has_key?(@variables[k].name)
783
+ op[@variables[k].name] = Parameter.new(@variables[k], v, nil)
784
+ else
785
+ #op[@variables[k].name].pre = v
786
+ end
787
+ }
788
+ @operators[op.name] = op
789
+ @g_operators << op
790
+ }
791
+ # the original operator is not required
792
+ return false
793
+ end
794
+
795
+ return true
796
+ end
797
+
798
+ # normalize the given first-order formula by transforming it into
799
+ # DNF
800
+ def normalize_formula(formula, dump=false)
801
+ def create_equals_constraint(value)
802
+ return {'_context'=>'constraint', '_type'=>'equals', '_value'=>value}
803
+ end
804
+
805
+ def create_not_equals_constraint(value)
806
+ return {'_context'=>'constraint', '_type'=>'not-equals', '_value'=>value}
807
+ end
808
+
809
+ def create_not_constraint(key, parent)
810
+ return {'_context'=>'constraint', '_type'=>'not', '_self'=>key, '_parent'=>parent}
811
+ end
812
+
813
+ def create_and_constraint(key, parent)
814
+ return {'_context'=>'constraint', '_type'=>'and', '_self'=>key, '_parent'=>parent}
815
+ end
816
+
817
+ def create_or_constraint(key, parent)
818
+ return {'_context'=>'constraint', '_type'=>'or', '_self'=>key, '_parent'=>parent}
819
+ end
820
+
821
+ def array_to_or_constraint(arr)
822
+ c = {'_context'=>'constraint', '_type'=>'or'}
823
+ arr.each { |v| c[Sfp::SasTranslator.next_constraint_key] = v }
824
+ return c
825
+ end
826
+
827
+ # combinatorial method for all possible values of nested reference
828
+ # using recursive method
829
+ def ref_combinator(bucket, parent, names, last_value, last_names=nil,
830
+ index=0, selected=Hash.new)
831
+
832
+ return if names[index] == nil
833
+ var_name = parent + '.' + names[index]
834
+ if not @variables.has_key?(var_name)
835
+ raise VariableNotFoundException, 'Variable not found: ' + var_name
836
+ end
837
+
838
+ if index >= names.length or (index >= names.length-1 and last_value != nil)
839
+ selected[var_name] = last_value if last_value != nil
840
+ last_names << var_name if last_names != nil
841
+ result = selected.clone
842
+ result['_context'] = 'constraint'
843
+ result['_type'] = 'and'
844
+ bucket << result
845
+ else
846
+ @variables[var_name].each { |v|
847
+ next if v.is_a?(Hash) and v.isnull
848
+ v = v.ref if v.is_a?(Hash) and v.isobject
849
+ selected[var_name] = create_equals_constraint(v)
850
+ ref_combinator(bucket, v, names, last_value, last_names, index+1, selected)
851
+ }
852
+ end
853
+ selected.delete(var_name)
854
+ end
855
+
856
+ def break_nested_reference(ref)
857
+ rest, last = ref.pop_ref
858
+ names = [last]
859
+ while rest != '$' and not @variables.has_key?(rest)
860
+ rest, last = rest.pop_ref
861
+ names.unshift(last)
862
+ end
863
+ rest, last = rest.pop_ref
864
+ names.unshift(last)
865
+ return [names, rest]
866
+ end
867
+
868
+ def normalize_nested_right_only_multiple_values(left, right, formula)
869
+ # TODO -- evaluate this method
870
+ ref = right['_value']
871
+ key1 = Sfp::SasTranslator.next_constraint_key
872
+ c_or = create_or_constraint(key1, formula)
873
+ @variables[ref].each do |v|
874
+ value = ( (v.is_a?(Hash) and v.isobject) ? v.ref : v)
875
+ key2 = Sfp::SasTranslator.next_constraint_key
876
+ c_and = create_and_constraint(key2, c_or)
877
+ #c_and[ref] = create_equals_constraint(value) ## HACK! -- this should be uncomment
878
+ c_and[left] = create_equals_constraint(value) if right['_type'] == 'equals'
879
+ c_and[left] = create_not_equals_constraint(value) if right['_type'] == 'not-equals'
880
+ c_or[key2] = c_and
881
+ end
882
+ formula.delete(left)
883
+ formula[key1] = c_or
884
+ return key1
885
+ end
886
+
887
+ def normalize_nested_right_values(left, right, formula)
888
+ # TODO
889
+ #puts 'nested right: ' + left + ' = ' + right['_value']
890
+
891
+ raise TranslationException, 'not implemented: normalized_nested_right'
892
+ end
893
+
894
+ def normalize_nested_right_only(left, right, formula)
895
+ value = right['_value']
896
+ return if @variables.has_key?(value) and @variables[value].is_final
897
+
898
+ if @variables.has_key?(value)
899
+ normalize_nested_right_only_multiple_values(left, right, formula)
900
+ else
901
+ normalize_nested_right_values(left, right, formula)
902
+ end
903
+ end
904
+
905
+ def normalize_nested_left_right(left, right, formula)
906
+ # TODO
907
+ #puts 'nested left-right'
908
+ #names, rest = break_nested_reference(left)
909
+ #bucket1 = Array.new
910
+ #last_names1 = Array.new
911
+ #ref_combinator(bucket1, rest, names, nil, last_names1)
912
+
913
+ raise TranslationException, 'not implemented: normalized_nested_left_right'
914
+ end
915
+
916
+ def normalize_nested_left_only(left, right, formula)
917
+ names, rest = break_nested_reference(left)
918
+ bucket = Array.new
919
+ ref_combinator(bucket, rest, names, right)
920
+ formula.delete(left)
921
+ if bucket.length > 0
922
+ key = Sfp::SasTranslator.next_constraint_key
923
+ formula[key] = array_to_or_constraint(bucket)
924
+ to_and_or_graph(formula[key])
925
+ return key
926
+ end
927
+ end
928
+
929
+ # transform a first-order formula into AND/OR graph
930
+ def to_and_or_graph(formula)
931
+ keys = formula.keys
932
+ keys.each { |k|
933
+ next if k[0,1] == '_'
934
+ v = formula[k]
935
+ if k.isref and not @variables.has_key?(k)
936
+ if v.is_a?(Hash) and v.isconstraint
937
+ if (v['_type'] == 'equals' or v['_type'] == 'not-equals') and
938
+ v['_value'].is_a?(String) and v['_value'].isref and
939
+ not @variables.has_key?(v['_value'])
940
+ # nested left & right
941
+ normalize_nested_left_right(k, v, formula)
942
+ elsif (v['_type'] == 'or' or v['_type'] == 'and')
943
+ to_and_or_graph(v)
944
+ else
945
+ # nested left only
946
+ normalize_nested_left_only(k, v, formula)
947
+ end
948
+ end
949
+ else
950
+ if v.is_a?(Hash) and v.isconstraint
951
+ if (v['_type'] == 'equals' or v['_type'] == 'not-equals') and
952
+ v['_value'].is_a?(String) and v['_value'].isref
953
+ # nested right only
954
+ normalize_nested_right_only(k, v, formula)
955
+ elsif (v['_type'] == 'or' or v['_type'] == 'and')
956
+ to_and_or_graph(v)
957
+ end
958
+ end
959
+ end
960
+ }
961
+ end
962
+
963
+
964
+ # Recursively pull statements that has the same AND/OR operator.
965
+ # Only receive a formula with AND, OR, EQUALS constraints.
966
+ #
967
+ # return false if there is any contradiction of facts, otherwise true
968
+ def flatten_and_or_graph(formula)
969
+ # transform formula into a format:
970
+ # (x1 and x2) or (y1 and y2 and y3) or z1
971
+ is_and_or_tree = false
972
+ formula.keys.each { |k|
973
+ next if k[0,1] == '_'
974
+ v = formula[k]
975
+ if v.is_a?(Hash) and v.isconstraint
976
+ if v['_type'] == 'or' or v['_type'] == 'and'
977
+ if not flatten_and_or_graph(v)
978
+ # remove it because it's not consistent
979
+ formula.delete(k)
980
+ v['_parent'] = nil
981
+ end
982
+
983
+ if formula['_type'] == v['_type']
984
+ # pull-out all node's elements
985
+ v.keys.each { |k1|
986
+ next if k1[0,1] == '_'
987
+ v1 = v[k1]
988
+ # check contradiction facts
989
+ if formula.has_key?(k1)
990
+ return false if formula[k1]['_type'] != v1['_type']
991
+ return false if formula[k1]['_value'] != v1['_value']
992
+ else
993
+ formula[k1] = v1
994
+ end
995
+ }
996
+ formula.delete(k)
997
+ end
998
+ is_and_or_tree = true if formula['_type'] == 'and' and v['_type'] == 'or'
999
+ end
1000
+ end
1001
+ }
1002
+ # dot-product the nodes
1003
+ def cross_product_and(bucket, names, formula, values=Hash.new, index=0)
1004
+ if index >= names.length
1005
+ key = Sfp::SasTranslator.next_constraint_key
1006
+ c = create_and_constraint(key, formula)
1007
+ values.each { |k,v| c[k] = v }
1008
+ if flatten_and_or_graph(c)
1009
+ # add the constraint because it's consistent
1010
+ formula[key] = c
1011
+ end
1012
+ else
1013
+ key = names[index]
1014
+ val = formula[ key ]
1015
+ if val.is_a?(Hash) and val.isconstraint and val['_type'] == 'or'
1016
+ val.each { |k,v|
1017
+ next if k[0,1] == '_'
1018
+ values[k] = v
1019
+ cross_product_and(bucket, names, formula, values, index+1)
1020
+ values.delete(k)
1021
+ }
1022
+ else
1023
+ values[key] = val
1024
+ cross_product_and(bucket, names, formula, values, index+1)
1025
+ end
1026
+ end
1027
+ end
1028
+ if is_and_or_tree
1029
+ # change it into OR->AND tree
1030
+ names = Array.new
1031
+ formula.keys.each { |k| names << k if k[0,1] != '_' }
1032
+ bucket = Array.new
1033
+ cross_product_and(bucket, names, formula)
1034
+ names.each { |k| formula.delete(k) }
1035
+ formula['_type'] = 'or'
1036
+ end
1037
+
1038
+ return true
1039
+ end
1040
+
1041
+ # 'var_name' := x, value := p1
1042
+ # variable x := p1 | p2 | p3
1043
+ # return an array (p2, p3)
1044
+ def get_list_not_value_of(var_name, value)
1045
+ raise VariableNotFoundException, 'Variable not found: ' + var_name if
1046
+ not @variables.has_key?(var_name)
1047
+ if value.is_a?(String) and value.isref
1048
+ value = @root['initial'].at?(value)
1049
+ elsif value.is_a?(Hash) and value.isnull
1050
+ value = @variables[var_name][0]
1051
+ end
1052
+ return (@variables[var_name] - [value])
1053
+ end
1054
+
1055
+ # variable x := p1 | p2 | p3
1056
+ # then formula (x not-equals p1) is transformed into
1057
+ # formula ( (x equals p2) or (x equals p3) )
1058
+ def not_equals_statement_to_or_constraint(formula)
1059
+ formula.keys.each do |k|
1060
+ next if k[0,1] == '_'
1061
+ v = formula[k]
1062
+ if v.is_a?(Hash) and v.isconstraint
1063
+ if v['_type'] == 'or' or v['_type'] == 'and'
1064
+ not_equals_statement_to_or_constraint(v)
1065
+ elsif v['_type'] == 'not-equals'
1066
+ key1 = Sfp::SasTranslator.next_constraint_key
1067
+ c_or = create_or_constraint(key1, formula)
1068
+ get_list_not_value_of(k, v['_value']).each do |val1|
1069
+ val1 = val1.ref if val1.is_a?(Hash) and val1.isobject
1070
+ key2 = Sfp::SasTranslator.next_constraint_key
1071
+ c_and = create_and_constraint(key2, c_or)
1072
+ c_and[k] = create_equals_constraint(val1)
1073
+ c_or[key2] = c_and
1074
+ end
1075
+ formula.delete(k)
1076
+ formula[key1] = c_or
1077
+ end
1078
+ end
1079
+ end
1080
+ end
1081
+
1082
+ def substitute_template(grounder, template, parent)
1083
+ id = Sfp::SasTranslator.next_constraint_key
1084
+ c_and = Sfp::Helper.deep_clone(template)
1085
+ c_and['_self'] = id
1086
+ c_and.accept(grounder)
1087
+ parent[id] = c_and
1088
+ remove_not_and_iterator_constraint(c_and)
1089
+ c_and
1090
+ end
1091
+
1092
+ # Remove the following type of constraint in the given formula:
1093
+ # - NOT constraints by transforming them into EQUALS, NOT-EQUALS, AND, OR constraints
1094
+ # - ARRAY-Iterator constraint by extracting all possible values of given ARRAY
1095
+ #
1096
+ def remove_not_and_iterator_constraint(formula)
1097
+ # (not (equals) (not-equals) (and) (or))
1098
+ if formula.isconstraint and formula['_type'] == 'not'
1099
+ formula['_type'] = 'and'
1100
+ formula.each { |k,v|
1101
+ next if k[0,1] == '_'
1102
+ if v.is_a?(Hash) and v.isconstraint
1103
+ case v['_type']
1104
+ when 'equals'
1105
+ v['_type'] = 'not-equals'
1106
+ when 'not-equals'
1107
+ v['_type'] = 'equals'
1108
+ when 'and'
1109
+ v['_type'] = 'or'
1110
+ v.keys.each { |k1|
1111
+ next if k1[0,1] == '_'
1112
+ v1 = v[k1]
1113
+ k2 = Sfp::SasTranslator.next_constraint_key
1114
+ c_not = create_not_constraint(k2, v)
1115
+ c_not[k1] = v1
1116
+ v1['_parent'] = c_not
1117
+ v.delete(k1)
1118
+ remove_not_and_iterator_constraint(c_not)
1119
+ }
1120
+ when 'or'
1121
+ v['_type'] = 'and'
1122
+ v.keys.each { |k1|
1123
+ next if k1[0,1] == '_'
1124
+ v1 = v[k1]
1125
+ k2 = Sfp::SasTranslator.next_constraint_key
1126
+ c_not = create_not_constraint(k2, v)
1127
+ c_not[k1] = v1
1128
+ v1['_parent'] = c_not
1129
+ v.delete(k1)
1130
+ remove_not_and_iterator_constraint(c_not)
1131
+ }
1132
+ else
1133
+ raise TranslationException, 'unknown rules: ' + v['_type']
1134
+ end
1135
+ end
1136
+ }
1137
+ elsif formula.isconstraint and formula['_type'] == 'iterator'
1138
+ ref = formula['_value']
1139
+ var = '$.' + formula['_variable']
1140
+ if @arrays.has_key?(ref)
1141
+ # substitute ARRAY
1142
+ total = @arrays[ref]
1143
+ grounder = ParameterGrounder.new(@root['initial'], {})
1144
+ for i in 0..(total-1)
1145
+ grounder.map.clear
1146
+ grounder.map[var] = ref + "[#{i}]"
1147
+ substitute_template(grounder, formula['_template'], formula)
1148
+ end
1149
+ else
1150
+ setvalue = (ref.is_a?(Array) ? ref : @root['initial'].at?(ref))
1151
+ if setvalue.is_a?(Hash) and setvalue.isset
1152
+ # substitute SET
1153
+ grounder = ParameterGrounder.new(@root['initial'], {})
1154
+ setvalue['_values'].each do |v|
1155
+ grounder.map.clear
1156
+ grounder.map[var] = v
1157
+ substitute_template(grounder, formula['_template'], formula)
1158
+ end
1159
+ elsif setvalue.is_a?(Array)
1160
+ grounder = ParameterGrounder.new(@root['initial'], {})
1161
+ setvalue.each do |v|
1162
+ grounder.map.clear
1163
+ grounder.map[var] = v
1164
+ substitute_template(grounder, formula['_template'], formula)
1165
+ end
1166
+ else
1167
+ #puts setvalue.inspect + ' -- ' + formula.ref + ' -- ' + var.to_s
1168
+ #raise UndefinedValueException, 'Undefined'
1169
+ raise UndefinedValueException.new(var)
1170
+ end
1171
+ end
1172
+ formula['_type'] = 'and'
1173
+ formula.delete('_value')
1174
+ formula.delete('_variable')
1175
+ formula.delete('_template')
1176
+ elsif formula.isconstraint and formula['_type'] == 'forall'
1177
+ classref = '$.' + formula['_class']
1178
+ raise TypeNotFoundException, classref if not @types.has_key?(classref)
1179
+ var = '$.' + formula['_variable']
1180
+ grounder = ParameterGrounder.new(@root['initial'], {})
1181
+ @types[classref].each do |v|
1182
+ next if v == nil or (v.is_a?(Hash) and v.isnull)
1183
+ grounder.map.clear
1184
+ grounder.map[var] = v.ref
1185
+ substitute_template(grounder, formula['_template'], formula)
1186
+ end
1187
+ formula['_type'] = 'and'
1188
+ formula.delete('_class')
1189
+ formula.delete('_variable')
1190
+ formula.delete('_template')
1191
+ else
1192
+ formula.each { |k,v|
1193
+ next if k[0,1] == '_'
1194
+ remove_not_and_iterator_constraint(v) if v.is_a?(Hash) and v.isconstraint
1195
+ }
1196
+ end
1197
+ end
1198
+
1199
+ ### testing/debugging methods
1200
+ def calculate_depth(formula, depth)
1201
+ formula.each { |k,v|
1202
+ next if k[0,1] == '_'
1203
+ depth = calculate_depth(v, depth+1)
1204
+ break
1205
+ }
1206
+ depth
1207
+ end
1208
+
1209
+ def total_element(formula, total=0, total_or=0, total_and=0)
1210
+ formula.each { |k,v|
1211
+ next if k[0,1] == '_'
1212
+ if v['_type'] == 'or'
1213
+ total_or += 1
1214
+ elsif v['_type'] == 'and'
1215
+ total_and += 1
1216
+ else
1217
+ end
1218
+ total, total_or, total_and = total_element(v, total+1, total_or, total_and)
1219
+ }
1220
+ [total,total_or,total_and]
1221
+ end
1222
+
1223
+ def visit_and_or_graph(formula, map={}, total=0)
1224
+ if formula['_type'] == 'and'
1225
+ map = map.clone
1226
+ is_end = true
1227
+ formula.each do |k,v|
1228
+ next if k[0,1] == '_'
1229
+ if v['_type'] == 'equals'
1230
+ # bad branch
1231
+ if map.has_key?(k) and map[k] != v['_value']
1232
+ return
1233
+ end
1234
+ map[k] = v['_value']
1235
+ elsif v['_type'] == 'and' or v['_type'] == 'or'
1236
+ is_end = false
1237
+ end
1238
+ end
1239
+
1240
+ if is_end
1241
+ # map is valid conjunction
1242
+ else
1243
+ formula.each do |k,v|
1244
+ next if k[0,1] == '_'
1245
+ if v['_type'] == 'and' or v['_type'] == 'or'
1246
+ return visit_and_or_graph(v, map, total)
1247
+ end
1248
+ end
1249
+ end
1250
+
1251
+ elsif formula['_type'] == 'or'
1252
+ formula.each do |k,v|
1253
+ next if k[0,1] == '_'
1254
+ if v['_type'] == 'equals'
1255
+ # bad branch
1256
+ if map.has_key?(k) and map[k] != v['_value']
1257
+ end
1258
+ final_map = map.clone
1259
+ final_map[k] = v['_value']
1260
+ # map is valid conjunction
1261
+ elsif v['_type'] == 'and' or v['_type'] == 'or'
1262
+ visit_and_or_graph(v, map, total)
1263
+ end
1264
+ end
1265
+
1266
+ end
1267
+ total
1268
+ end
1269
+ ### end of testing/debugging methods
1270
+
1271
+ remove_not_and_iterator_constraint(formula)
1272
+ to_and_or_graph(formula)
1273
+ not_equals_statement_to_or_constraint(formula)
1274
+
1275
+ return flatten_and_or_graph(formula)
1276
+ end
1277
+
1278
+
1279
+ def self.null_of(class_ref=nil)
1280
+ return { '_context' => 'null', '_isa' => class_ref } if class_ref.is_a?(String) and
1281
+ class_ref != '' and class_ref.isref
1282
+ return nil
1283
+ end
1284
+
1285
+
1286
+ ### Helper Classes ###
1287
+
1288
+ class VariableNotFoundException < Exception; end
1289
+
1290
+ class TypeNotFoundException < Exception; end
1291
+
1292
+ class UndefinedValueException < Exception
1293
+ attr_accessor :var
1294
+
1295
+ def to_s; return @var; end
1296
+ end
1297
+
1298
+ # Visitor class has 3 attributes
1299
+ # - root : Hash instance of root Context
1300
+ # - variables: Hash instance that holds all Variable instances
1301
+ # - types: Hash instance that holds all types (primitive or non-primitive)
1302
+ class Visitor
1303
+ def initialize(main)
1304
+ @main = main
1305
+ @root = main.root
1306
+ @vars = main.variables
1307
+ @types = main.types
1308
+ end
1309
+ end
1310
+
1311
+ # Visitor that set goal value of each variable
1312
+ class GoalVisitor < Visitor
1313
+ def set_equals(name, value)
1314
+ value = @types[@vars[name].type][0] if value.is_a?(Hash) and value.isnull
1315
+ value = @root['initial'].at?(value) if value.is_a?(String) and value.isref
1316
+ @vars[name].goal = value
1317
+ end
1318
+
1319
+ def visit(name, value, obj)
1320
+ return if name[0,1] == '_'
1321
+ raise VariableNotFoundException, 'Variable not found: ' + name if
1322
+ not @vars.has_key?(name)
1323
+ if value.isconstraint
1324
+ self.set_equals(name, value['_value']) if value['_type'] == 'equals'
1325
+ end
1326
+ return true
1327
+ end
1328
+ end
1329
+
1330
+ # collecting all variables and put them into @bucket
1331
+ class VariableCollector < Visitor
1332
+ def initialize(main)
1333
+ super(main)
1334
+ @init = main.root['initial']
1335
+ end
1336
+
1337
+ def visit(name, value, parent)
1338
+ return false if name[0,1] == '_'
1339
+ return false if (value.is_a?(Hash) and not (value.isobject or value.isnull or value.isset))
1340
+ # or value.is_a?(Array)
1341
+
1342
+ var_name = parent.ref.push(name)
1343
+ isfinal = self.is_final(value)
1344
+ isref = (value.is_a?(String) and value.isref)
1345
+ isset = false
1346
+ value = @init.at?(value) if isref
1347
+ type = (isfinal ? self.isa?(value) : self.get_type(name, value, parent))
1348
+ if type == nil
1349
+ raise Exception, "Unrecognized type of variable: #{var_name}"
1350
+ else
1351
+ value = null_value(type) if value == nil
1352
+ isset = true if type[0,1] == '('
1353
+ var = Variable.new(var_name, type, -1, value, nil, isfinal)
1354
+ var.isset = isset
1355
+ @vars[var.name] = var
1356
+ if isfinal and value.is_a?(Hash)
1357
+ value['_classes'].each { |c| add_value(c, value) }
1358
+ elsif not isref
1359
+ add_value(type, value)
1360
+ end
1361
+ end
1362
+ return true
1363
+ end
1364
+
1365
+ def null_value(isa)
1366
+ return {'_context' => 'null', '_isa' => isa}
1367
+ end
1368
+
1369
+ def get_type(name, value, parent)
1370
+ =begin
1371
+ type = self.isa?(value)
1372
+ if type == nil and parent.is_a?(Hash) and parent.has_key?('_isa')
1373
+ isa = @main.root.at?(parent['_isa'])
1374
+ type = isa.type?(name) if isa != nil
1375
+ return type if type != nil
1376
+ end
1377
+ =end
1378
+ type = nil
1379
+ if parent.has_key?('_isa')
1380
+ isa = @main.root.at?(parent['_isa'])
1381
+ if not isa.nil?
1382
+ type = isa.type?(name)
1383
+ return type if not type.nil?
1384
+ end
1385
+ end
1386
+ type = self.isa?(value)
1387
+
1388
+ return "(#{value['_isa']})" if value.is_a?(Hash) and value.isset and value.has_key?('_isa')
1389
+
1390
+ return nil if type == nil
1391
+
1392
+ return type if type.is_a?(String) and type.isref
1393
+
1394
+ parent_class = @root.at?( @vars[parent.ref].type )
1395
+ return parent_class[name]['_isa']
1396
+ end
1397
+
1398
+ def add_value(type, value)
1399
+ if not @types.has_key?(type)
1400
+ @types[type] = Array.new
1401
+ @types[type] << Sfp::SasTranslator.null_of(type) if @types[type].length <= 0
1402
+ end
1403
+ @types[type] << value if not (value.is_a?(Hash) and value.isnull)
1404
+ end
1405
+
1406
+ def isa?(value)
1407
+ return '$.Boolean' if value.is_a?(TrueClass) or value.is_a?(FalseClass)
1408
+ return '$.Integer' if value.is_a?(Numeric)
1409
+ return '$.String' if value.is_a?(String) and not value.isref
1410
+ return value['_isa'] if value.is_a?(Hash) and value.isobject
1411
+ return nil
1412
+ end
1413
+
1414
+ def is_final(value)
1415
+ return true if value.is_a?(Hash) and not value.isnull and not value.isset
1416
+ return false
1417
+ end
1418
+ end
1419
+
1420
+ # Collects all values (primitive or non-primitive)
1421
+ class ValueCollector
1422
+ def initialize(sas)
1423
+ @bucket = sas.types
1424
+ @sas = sas
1425
+ end
1426
+
1427
+ def visit(name, value, obj)
1428
+ return true if name[0,1] == '_' and name != '_value'
1429
+ type = get_type(value)
1430
+ if type != nil
1431
+ @bucket[type] << value
1432
+ elsif value.is_a?(Hash)
1433
+ if value.isobject
1434
+ value['_classes'].each { |c| @bucket[c] << value }
1435
+ elsif value.isset
1436
+ raise TranslationException, 'not implemented -- set: ' + value['_isa']
1437
+ end
1438
+ elsif value.is_a?(Array)
1439
+ if value.length > 0
1440
+ type = get_type(value[0])
1441
+ if type != nil
1442
+ type = "(#{type})" # an array
1443
+ #raise Exception, "type not found: #{type}" if not @bucket.has_key?(type)
1444
+ @bucket[type] = [] if not @bucket.has_key?(type)
1445
+ @bucket["#{type}"] << value
1446
+ elsif value[0].is_a?(String) and value[0].isref
1447
+ val = @sas.root['initial'].at?(value[0])
1448
+ return true if val == nil
1449
+ type = get_type(val)
1450
+ if type != nil
1451
+ @bucket["(#{type})"] << value
1452
+ elsif val.is_a?(Hash) and val.isobject
1453
+ val['_classes'].each { |c| @bucket["(#{c})"] << value if @bucket.has_key?("(#{c})") }
1454
+ end
1455
+ end
1456
+ end
1457
+ else
1458
+ end
1459
+ return true
1460
+ end
1461
+
1462
+ def get_type(value)
1463
+ if value.is_a?(String) and not value.isref
1464
+ '$.String'
1465
+ elsif value.is_a?(Numeric)
1466
+ '$.Integer'
1467
+ elsif value.is_a?(TrueClass) or value.is_a?(FalseClass)
1468
+ '$.Boolean'
1469
+ else
1470
+ nil
1471
+ end
1472
+ end
1473
+ end
1474
+
1475
+ class ParameterGrounder
1476
+ attr_accessor :map
1477
+
1478
+ def initialize(root, map={})
1479
+ @root = root
1480
+ @map = map
1481
+ end
1482
+
1483
+ def visit(name, value, obj)
1484
+ return if name[0,1] == '_' and name != '_value' and name != '_template'
1485
+ if name[0,1] != '_'
1486
+ modified = false
1487
+ map.each { |k,v|
1488
+ if name == k
1489
+ obj[v] = value
1490
+ obj.delete(name)
1491
+ name = v
1492
+ value['_self'] = name if value.is_a?(Hash)
1493
+ modified = true
1494
+ break
1495
+ elsif name.length > k.length and name[k.length,1] == '.' and name[0, k.length] == k
1496
+ grounded = v + name[k.length, (name.length-k.length)]
1497
+ obj[grounded] = value
1498
+ obj.delete(name)
1499
+ name = grounded
1500
+ value['_self'] = name if value.is_a?(Hash)
1501
+ modified = true
1502
+ break
1503
+ end
1504
+ }
1505
+
1506
+ if modified and (name =~ /.*\.parent(\..*)?/ )
1507
+ parent, last = name.pop_ref
1508
+ parent_value = @root.at?(parent)
1509
+ raise VariableNotFoundException, parent if parent_value.nil?
1510
+ new_name = @root.at?(parent).ref.push(last) if last != 'parent'
1511
+ new_name = @root.at?(parent).ref.to_top if last == 'parent'
1512
+ obj[new_name] = value
1513
+ obj.delete(name)
1514
+ name = new_name
1515
+ value['_self'] = name if value.is_a?(Hash)
1516
+ end
1517
+ end
1518
+ # TODO ----- HACK! -----
1519
+ if obj.is_a?(Hash) and obj.isconstraint and obj['_type'] == 'iterator' and
1520
+ value.is_a?(String) and value.isref and map.has_key?(value)
1521
+ obj[name] = value = map[value]
1522
+ #puts map[value].inspect
1523
+ #puts "==>> " + obj.ref.push(name)
1524
+ end
1525
+ # ------ END of HACK! ----
1526
+ if value.is_a?(String) and value.isref
1527
+ map.each { |k,v|
1528
+ if value == k
1529
+ obj[name] = v
1530
+ break
1531
+ elsif value.length > k.length and value[k.length,1] == '.' and value[0,k.length] == k
1532
+ obj[name] = v + value[k.length, (value.length-k.length)]
1533
+ break
1534
+ end
1535
+ }
1536
+ end
1537
+ return true
1538
+ end
1539
+ end
1540
+ end
1541
+
1542
+
1543
+ # SAS Variable is a finite-domain variable
1544
+ # It has a finite set of possible values
1545
+ class Variable < Array
1546
+ # @name -- name of variable
1547
+ # @type -- type of variable ('string','boolean','number', or fullpath of a class)
1548
+ # @layer -- axiom layer ( '-1' if this is not axiom variable, otherwise >0)
1549
+ # @init -- initial value
1550
+ # @goal -- goal value (desired value)
1551
+ attr_accessor :name, :type, :layer, :init, :goal, :is_final, :id, :mutable, :isset
1552
+ attr_reader :is_primitive
1553
+
1554
+ def initialize(name, type, layer=-1, init=nil, goal=nil, is_final=false)
1555
+ @name = name
1556
+ @type = type
1557
+ @layer = layer
1558
+ @init = init
1559
+ @goal = goal
1560
+ @is_final = is_final
1561
+ @is_primitive = (type == '$.String' or type == '$.Integer' or type == '$.Boolean')
1562
+ @mutable = true
1563
+ end
1564
+
1565
+ def to_s
1566
+ s = @name.to_s + '|' + @type.to_s
1567
+ s += '|' + (@init == nil ? '-' : (@init.is_a?(Hash) ? @init.tostring : @init.to_s))
1568
+ s += '|' + (@goal == nil ? '-' : (@goal.is_a?(Hash) ? @goal.tostring : @goal.to_s))
1569
+ s += '|' + (@is_final ? 'final' : 'notfinal') + "\n"
1570
+ s += "\t["
1571
+ self.each { |v| s += (v.is_a?(Hash) ? v.tostring : v.to_s) + ',' }
1572
+ s = (self.length > 0 ? s.chop : s) + "]"
1573
+ return s
1574
+ end
1575
+
1576
+ # return variable representation in SAS+ format
1577
+ def to_sas(root)
1578
+ sas = "begin_variable\nvar_#{@id}#{@name}\n#{@layer}\n#{self.length}\n"
1579
+ self.each { |v|
1580
+ v = root.at?(v) if v.is_a?(String) and v.isref
1581
+ v = '"' + v + '"' if v.is_a?(String)
1582
+ sas += (v.is_a?(Hash) ? (v.isnull ? "null\n" : "#{v.ref}\n") : "#{v}\n")
1583
+ }
1584
+ return sas += "end_variable"
1585
+ end
1586
+ end
1587
+
1588
+ # A class for Grounded Operator
1589
+ class Operator < Hash
1590
+ attr_accessor :id, :name, :cost, :params, :modifier_id
1591
+ attr_reader :ref
1592
+
1593
+ def initialize(ref, cost=1, id=nil)
1594
+ @id = (id == nil ? Sfp::SasTranslator.next_operator_id : id)
1595
+ @cost = cost
1596
+ @ref = ref
1597
+ @modifier_id = nil
1598
+ self.update_name
1599
+ end
1600
+
1601
+ def clone
1602
+ op = Operator.new(@ref, @cost)
1603
+ op.params = @params
1604
+ self.each { |key,param| op[key] = param.clone }
1605
+ return op
1606
+ end
1607
+
1608
+ def update_name
1609
+ @name = 'op_' + @id.to_s + @ref
1610
+ end
1611
+
1612
+ # return true if this operator supports given operator's precondition
1613
+ # otherwise return false
1614
+ def supports?(operator)
1615
+ operator.each_value do |p2|
1616
+ # precondition is any value or this operator does not effecting variable 'p2'
1617
+ next if p2.pre == nil or not self.has_key?(p2.var.name) or
1618
+ self[p2.var.name].post == nil
1619
+ return true if self[p2.var.name].post == p2.pre
1620
+ end
1621
+ false
1622
+ end
1623
+
1624
+ # return true if this operator requires an effect of given operator
1625
+ # otherwise return false
1626
+ def requires?(operator)
1627
+ self.each_value do |p1|
1628
+ next if p1.pre == nil # can be any value
1629
+ p2 = operator[p1.var.name]
1630
+ if p2 != nil and p2.post != nil and p1.pre == p2.post and p2.pre == nil
1631
+ return true
1632
+ end
1633
+ end
1634
+ return false
1635
+ end
1636
+
1637
+ def merge(operator)
1638
+ cost = (@cost > operator.cost ? @cost : operator.cost)
1639
+ names = @name.split('#')
1640
+ name = (names.length > 1 ? names[1] : names[0])
1641
+ op = Operator.new('#' + name + '|' + operator.name, cost)
1642
+ self.each_value { |p| op[p.var.name] = p.clone }
1643
+ operator.each_value do |p|
1644
+ if not op.has_key?(p.var.name)
1645
+ op[p.var.name] = p.clone
1646
+ elsif p.post != nil
1647
+ op[p.var.name] = p.clone
1648
+ end
1649
+ end
1650
+ return op
1651
+ end
1652
+
1653
+ def total_prevails
1654
+ count = 0
1655
+ self.each_value { |p| count += 1 if p.post.nil? }
1656
+ count
1657
+ end
1658
+
1659
+ def total_preposts
1660
+ count = 0
1661
+ self.each_value { |p| count += 1 if not p.post.nil? }
1662
+ count
1663
+ end
1664
+
1665
+ def get_pre_state
1666
+ state = {}
1667
+ self.each_value { |p| state[p.var.name] = p.pre if p.pre != nil }
1668
+ state
1669
+ end
1670
+
1671
+ def get_post_state
1672
+ state = {}
1673
+ self.each_value { |p| state[p.var.name] = p.post if p.post != nil }
1674
+ state
1675
+ end
1676
+
1677
+ # two operators can be parallel if
1678
+ # - their preconditions are non consistent
1679
+ # - their effects are not consistent
1680
+ # - one's during condition is not consistent with another
1681
+ def conflict?(operator)
1682
+ self.each_value do |param1|
1683
+ next if not operator.has_key?(param1.var.name)
1684
+ param2 = operator[param1.var.name]
1685
+ return true if param1.pre != nil and param2.pre != nil and param1.pre != param2.pre
1686
+ return true if param1.post != nil and param2.post != nil and param1.post != param2.post
1687
+ return true if param1.pre != nil and param2.post != nil and param1.pre != param2.post
1688
+ return true if param1.post != nil and param2.pre != nil and param1.post != param2.pre
1689
+ end
1690
+ return false
1691
+ end
1692
+
1693
+ def to_s
1694
+ return @name + ': ' + self.length.to_s
1695
+ end
1696
+
1697
+ def to_sas(root, variables)
1698
+ prevail = Array.new
1699
+ prepost = Array.new
1700
+ self.each_value { |p|
1701
+ if p.post == nil
1702
+ prevail << p
1703
+ else
1704
+ prepost << p
1705
+ end
1706
+ }
1707
+ sas = "begin_operator\n#{@name}"
1708
+ @params.each { |k,v| sas += " #{k}=#{v}" if k != '$.this' } if @params != nil
1709
+ sas += "\n#{prevail.length}\n"
1710
+ prevail.each { |p|
1711
+ line = p.to_sas(root, variables)
1712
+ raise TranslationException if line[line.length-1] == ' '
1713
+ sas += "#{line}\n"
1714
+ }
1715
+ sas += "#{prepost.length}\n"
1716
+ prepost.each { |p|
1717
+ line = p.to_sas(root, variables, false)
1718
+ raise TranslationException if line[line.length-1] == ' '
1719
+ sas += "#{line}\n"
1720
+ }
1721
+ sas += "#{@cost}\nend_operator"
1722
+ return sas
1723
+ end
1724
+
1725
+ def to_sfw
1726
+ if not (@name =~ /.*\$.*/)
1727
+ id , name = @name.split('-', 2)
1728
+ else
1729
+ id, name = @name.split('$', 2)
1730
+ end
1731
+
1732
+ sfw = { 'name' => '$' + name,
1733
+ 'parameters' => {},
1734
+ 'condition' => {},
1735
+ 'effect' => {} }
1736
+
1737
+ @params.each { |k,v|
1738
+ sfw['parameters'][k.to_s] = v if k != '$.this'
1739
+ } if @params != nil
1740
+
1741
+ self.each_value do |param|
1742
+ next if param.var.name == Sfp::SasTranslator::GlobalVariable
1743
+ p = param.to_sfw
1744
+ if not p['pre'].nil?
1745
+ sfw['condition'][p['name']] = (p['pre'].is_a?(Sfp::Null) ? nil : p['pre'])
1746
+ end
1747
+ if not p['post'].nil?
1748
+ sfw['effect'][p['name']] = (p['post'].is_a?(Sfp::Null) ? nil : p['post'])
1749
+ end
1750
+
1751
+ #sfw['condition'][ p['name'] ] = p['pre'] if p['pre'] != nil
1752
+ #sfw['effect'][ p['name'] ] = p['post'] if p['post'] != nil
1753
+ end
1754
+ return sfw
1755
+ end
1756
+ end
1757
+
1758
+ # A class for Grounded Axiom
1759
+ class Axiom < Hash
1760
+ attr_accessor :id, :target
1761
+
1762
+ def initialize
1763
+ @id = Sfp::SasTranslator.next_axiom_id
1764
+ end
1765
+
1766
+ def to_s
1767
+ return 'axiom#' + @id.to_s
1768
+ end
1769
+
1770
+ def to_sas
1771
+ #TODO
1772
+ end
1773
+ end
1774
+
1775
+ # A class for operator/axiom parameter (prevail or effect condition)
1776
+ class Parameter
1777
+ attr_accessor :var, :pre, :post
1778
+
1779
+ def initialize(var, pre, post=nil)
1780
+ @var = var
1781
+ @pre = pre
1782
+ @post = post
1783
+ end
1784
+
1785
+ def prevail?
1786
+ return (@post == nil)
1787
+ end
1788
+
1789
+ def effect?
1790
+ return (@post != nil)
1791
+ end
1792
+
1793
+ def clone
1794
+ return Parameter.new(@var, @pre, @post)
1795
+ end
1796
+
1797
+ def to_sas(root, variables, prevail=true)
1798
+ # resolve the reference
1799
+ #pre = ( (@pre.is_a?(String) and @pre.isref) ? root.at?(@pre) : @pre )
1800
+ #post = ( (@post.is_a?(String) and @post.isref) ? root.at?(@post) : @post )
1801
+ pre = ((@pre.is_a?(String) and @pre.isref) ? variables[@pre].init : @pre)
1802
+ post = ((@post.is_a?(String) and @post.isref) ? variables[@post].init : @post)
1803
+ # calculate the index
1804
+ pre = ( (pre.is_a?(Hash) and pre.isnull) ? 0 : (pre == nil ? -1 : @var.index(pre)) )
1805
+ post = ( (post.is_a?(Hash) and post.isnull) ? 0 : @var.index(post) )
1806
+
1807
+ raise TranslationException if not prevail and post.nil?
1808
+
1809
+ return "#{@var.id} #{pre}" if post.nil?
1810
+ return "0 #{@var.id} #{pre} #{post}"
1811
+ end
1812
+
1813
+ def to_s
1814
+ return @var.name + ',' +
1815
+ (@pre == nil ? '-' : (@pre.is_a?(Hash) ? @pre.tostring : @pre.to_s)) + ',' +
1816
+ (@post == nil ? '-' : (@post.is_a?(Hash) ? @post.tostring : @post.to_s))
1817
+ end
1818
+
1819
+ def to_sfw
1820
+ pre = @pre
1821
+ pre = @pre.ref if @pre.is_a?(Hash) and @pre.isobject
1822
+ pre = Sfp::Null.new if @pre.is_a?(Hash) and @pre.isnull
1823
+
1824
+ post = @post
1825
+ post = @post.ref if @post.is_a?(Hash) and @post.isobject
1826
+ post = Sfp::Null.new if @post.is_a?(Hash) and @post.isnull
1827
+
1828
+ return {
1829
+ 'name' => @var.name,
1830
+ 'pre' => pre,
1831
+ 'post' => post
1832
+ }
1833
+ end
1834
+ end
1835
+
1836
+ end