sfp 0.1.0

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.
@@ -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