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.
- data/.gitignore +1 -0
- data/LICENSE +30 -0
- data/README.md +200 -0
- data/bin/sfp +24 -0
- data/bin/solver/linux/downward +0 -0
- data/bin/solver/linux/preprocess +0 -0
- data/bin/solver/macos/downward +0 -0
- data/bin/solver/macos/preprocess +0 -0
- data/lib/sfp/SfpLangLexer.rb +3127 -0
- data/lib/sfp/SfpLangParser.rb +9770 -0
- data/lib/sfp/Sfplib.rb +357 -0
- data/lib/sfp/parser.rb +128 -0
- data/lib/sfp/planner.rb +460 -0
- data/lib/sfp/sas.rb +966 -0
- data/lib/sfp/sas_translator.rb +1836 -0
- data/lib/sfp/sfw2graph.rb +168 -0
- data/lib/sfp/visitors.rb +132 -0
- data/lib/sfp.rb +17 -0
- data/sfp.gemspec +26 -0
- data/src/SfpLang.g +1005 -0
- data/src/build.sh +7 -0
- data/test/cloud-classes.sfp +77 -0
- data/test/cloud1.sfp +33 -0
- data/test/cloud2.sfp +41 -0
- data/test/cloud3.sfp +42 -0
- data/test/service-classes.sfp +151 -0
- data/test/service1.sfp +24 -0
- data/test/service3.sfp +27 -0
- data/test/task.sfp +22 -0
- data/test/test.inc +9 -0
- data/test/test.sfp +13 -0
- data/test/test1.sfp +19 -0
- data/test/test2.inc +40 -0
- data/test/test2.sfp +17 -0
- data/test/types.sfp +28 -0
- data/test/v1.1.sfp +22 -0
- metadata +120 -0
@@ -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
|