sfp 0.3.4 → 0.3.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +9 -29
- data/bin/sfp +1 -1
- data/lib/sfp/SfpLangLexer.rb +65 -65
- data/lib/sfp/SfpLangParser.rb +196 -220
- data/lib/sfp/Sfplib.rb +22 -1
- data/lib/sfp/sas_translator.rb +215 -18
- data/sfp.gemspec +2 -2
- data/src/SfpLang.g +15 -39
- data/src/SfpLang.g.bak +1088 -0
- metadata +8 -17
data/lib/sfp/Sfplib.rb
CHANGED
@@ -164,13 +164,19 @@ module Sfp
|
|
164
164
|
|
165
165
|
# Instance of this class will be returned as the value of a non-exist variable
|
166
166
|
class Undefined
|
167
|
+
def self.create(type)
|
168
|
+
@@list = {} if !defined? @@list
|
169
|
+
return @@list[type] if @@list.has_key?(type)
|
170
|
+
(@@list[type] = Undefined.new(nil, type))
|
171
|
+
end
|
172
|
+
|
167
173
|
attr_accessor :path, :type
|
168
174
|
def initialize(path=nil, type=nil)
|
169
175
|
@path = path
|
170
176
|
@type = type
|
171
177
|
end
|
172
178
|
def to_s
|
173
|
-
(@path.nil? ? "<sfp::undefined>" : "<sfp::undefined[#{@path}]>")
|
179
|
+
(@path.nil? ? "<sfp::undefined[#{type}]>" : "<sfp::undefined[#{type}][#{@path}]>")
|
174
180
|
end
|
175
181
|
end
|
176
182
|
|
@@ -365,3 +371,18 @@ String.send(:define_method, 'to_top') {
|
|
365
371
|
return self[0, self.length - parts[parts.length-1].length - 1]
|
366
372
|
}
|
367
373
|
|
374
|
+
String.send(:define_method, 'simplify') {
|
375
|
+
return self if not self.isref
|
376
|
+
ids = self.split('.')
|
377
|
+
i = 0
|
378
|
+
until i >= ids.length do
|
379
|
+
if i >= 3 and ids[i] == 'parent'
|
380
|
+
ids.delete_at(i)
|
381
|
+
ids.delete_at(i-1)
|
382
|
+
i -= 1
|
383
|
+
else
|
384
|
+
i += 1
|
385
|
+
end
|
386
|
+
end
|
387
|
+
ids.join('.')
|
388
|
+
}
|
data/lib/sfp/sas_translator.rb
CHANGED
@@ -54,6 +54,16 @@ module Sfp
|
|
54
54
|
attr_reader :variables, :types, :operators, :axioms, :goals
|
55
55
|
|
56
56
|
def to_sas
|
57
|
+
self.compile_step_1
|
58
|
+
self.compile_step_2
|
59
|
+
return self.sas
|
60
|
+
end
|
61
|
+
|
62
|
+
def compile_step_2
|
63
|
+
self.postprocess_simple_global_constraint
|
64
|
+
end
|
65
|
+
|
66
|
+
def compile_step_1
|
57
67
|
begin
|
58
68
|
@unknown_value = ::Sfp::Unknown.new
|
59
69
|
|
@@ -179,7 +189,6 @@ module Sfp
|
|
179
189
|
|
180
190
|
@vars = @variables.values
|
181
191
|
|
182
|
-
return create_output
|
183
192
|
rescue Exception => e
|
184
193
|
raise e
|
185
194
|
end
|
@@ -284,13 +293,173 @@ module Sfp
|
|
284
293
|
end
|
285
294
|
end
|
286
295
|
|
296
|
+
#####
|
297
|
+
# Method for postprocessing simple global constraints
|
298
|
+
#####
|
299
|
+
def postprocess_simple_global_constraint
|
300
|
+
@operators.keys.each do |name|
|
301
|
+
operator = @operators[name]
|
302
|
+
@operators.delete(name)
|
303
|
+
|
304
|
+
postprocess_simple_global_constraint_to_operator(operator).each { |op|
|
305
|
+
@operators[op.name] = op
|
306
|
+
}
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def postprocess_simple_global_constraint_to_operator(operator)
|
311
|
+
return [operator] if GlobalConstraintMethod == 2
|
312
|
+
|
313
|
+
def enforce_equals_constraint(operator, var, val)
|
314
|
+
if operator.has_key?(var)
|
315
|
+
return nil if !operator[var].pre.nil? or operator[var].pre != val
|
316
|
+
operator[var].pre = val
|
317
|
+
else
|
318
|
+
operator[var] = Parameter.new(@variables[var], val)
|
319
|
+
end
|
320
|
+
operator
|
321
|
+
end
|
322
|
+
|
323
|
+
if @global_simple_conjunctions.is_a?(Hash)
|
324
|
+
# simple conjunction
|
325
|
+
@global_simple_conjunctions.each do |var,c|
|
326
|
+
if c['_type'] == 'and'
|
327
|
+
c.each { |k,v| return [] if enforce_equals_constraint(operator, k, v['_value']).nil? }
|
328
|
+
else c['_type'] == 'equals'
|
329
|
+
return [] if enforce_equals_constraint(operator, var, c['_value']).nil?
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# TODO - test with premise and conclusion clauses have > 1 statements
|
335
|
+
#
|
336
|
+
# pre |= premise
|
337
|
+
def pre_support_premise(operator, premise)
|
338
|
+
premise.each { |var,c|
|
339
|
+
next if var[0,1] == '_'
|
340
|
+
return false if !operator.has_key?(var) or (!operator[var].pre.nil? and c['_value'] != operator[var].pre)
|
341
|
+
}
|
342
|
+
true
|
343
|
+
end
|
344
|
+
|
345
|
+
def post_threat_conclusion(operator, conclusion)
|
346
|
+
conclusion.each { |var,c|
|
347
|
+
next if var[0,1] == '_'
|
348
|
+
if operator.has_key?(var) and !operator[var].post.nil? and operator[var].post != c['_value']
|
349
|
+
return true
|
350
|
+
end
|
351
|
+
}
|
352
|
+
false
|
353
|
+
end
|
354
|
+
|
355
|
+
return [operator] if not @global_simple_implications.is_a?(Hash)
|
356
|
+
|
357
|
+
@global_simple_implications.each do |id,imply|
|
358
|
+
# If the operator's precondition support the premise, then
|
359
|
+
# it should support the conclusion as well.
|
360
|
+
if pre_support_premise(operator, imply['_premise'])
|
361
|
+
imply['_conclusion'].each { |var,c|
|
362
|
+
next if var[0,1] == '_'
|
363
|
+
if operator.has_key?(var)
|
364
|
+
if operator[var].pre.nil? # enforce the operator to support the conclusion
|
365
|
+
operator[var].pre = c['_value']
|
366
|
+
end
|
367
|
+
# this operator's does not support conclusion, then remove it from operators list
|
368
|
+
return [] if operator[var].pre != c['_value']
|
369
|
+
else
|
370
|
+
# enforce the operator to support the conclusion
|
371
|
+
operator[var] = Parameter.new(@variables[var], c['_value'])
|
372
|
+
end
|
373
|
+
}
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
results = []
|
378
|
+
@global_simple_implications.each do |id,imply|
|
379
|
+
imply['_premise'].each do |var,c|
|
380
|
+
next if var[0,1] == '_'
|
381
|
+
@variables[var].not(c['_value']).each { |x|
|
382
|
+
op = operator.clone
|
383
|
+
if op.has_key?(var)
|
384
|
+
if op[var].pre != x
|
385
|
+
op[var].pre == x
|
386
|
+
results << op
|
387
|
+
end
|
388
|
+
else
|
389
|
+
op[var] = Parameter.new(@variables[var], x)
|
390
|
+
results << op
|
391
|
+
end
|
392
|
+
}
|
393
|
+
end if post_threat_conclusion(operator, imply['_conclusion'])
|
394
|
+
end
|
395
|
+
return [operator] if results.length <= 0
|
396
|
+
results
|
397
|
+
end
|
398
|
+
|
399
|
+
def preprocess_simple_global_constraint
|
400
|
+
return if GlobalConstraintMethod == 2
|
401
|
+
|
402
|
+
def conjunction_only(id, f)
|
403
|
+
#return true if @variables.has_key?(id) and f['_type'] == 'equals'
|
404
|
+
return true if f['_type'] == 'equals'
|
405
|
+
if f['_type'] == 'and'
|
406
|
+
f.each { |k,v| return false if k[0,1] != '_' and not conjunction_only(k, v) }
|
407
|
+
return true
|
408
|
+
end
|
409
|
+
|
410
|
+
false
|
411
|
+
end
|
412
|
+
|
413
|
+
def simple_formulae(id, f)
|
414
|
+
if f['_type'] == 'imply'
|
415
|
+
f.each { |k,v| return false if k[0,1] != '_' and not conjunction_only(k, v) }
|
416
|
+
return true
|
417
|
+
end
|
418
|
+
conjunction_only(id, f)
|
419
|
+
end
|
420
|
+
|
421
|
+
global = @root['global']
|
422
|
+
simples = global.select { |k,v| k[0,1] != '_' and simple_formulae(k, v) }
|
423
|
+
|
424
|
+
@global_simple_conjunctions = {}
|
425
|
+
@global_simple_implications = {}
|
426
|
+
simples.each do |id,constraint|
|
427
|
+
case constraint['_type']
|
428
|
+
when 'equals', 'and'
|
429
|
+
raise TranslationException, 'Invalid global constraint' if not normalize_formula(constraint)
|
430
|
+
@global_simple_conjunctions[id] = constraint
|
431
|
+
when 'imply'
|
432
|
+
constraint.keys.each { |k|
|
433
|
+
next if k[0,1] == '_'
|
434
|
+
raise TranslationException, 'Invalid global constraint' if not normalize_formula(constraint[k])
|
435
|
+
constraint[k].select { |k,v| k[0,1] != '_' }
|
436
|
+
constraint['_premise'] = constraint[k] if constraint[k]['_subtype'] == 'premise'
|
437
|
+
constraint['_conclusion'] = constraint[k] if constraint[k]['_subtype'] == 'conclusion'
|
438
|
+
}
|
439
|
+
@global_simple_implications[id] = constraint
|
440
|
+
else
|
441
|
+
raise Exception, "Bug: non-simple formulae detected - #{constraint['_type']}"
|
442
|
+
end
|
443
|
+
global.delete(id)
|
444
|
+
end
|
445
|
+
end
|
446
|
+
##### End of methods for processing simple global constraints #####
|
447
|
+
|
448
|
+
|
287
449
|
def process_global_constraint
|
450
|
+
@total_complex_global_constraints = 0
|
451
|
+
|
288
452
|
### normalize global constraint formula ###
|
289
453
|
if @root.has_key?('global') and @root['global'].isconstraint
|
454
|
+
preprocess_simple_global_constraint
|
455
|
+
|
456
|
+
keys = @root['global'].keys.select { |k| k[0,1] != '_' }
|
457
|
+
@total_complex_global_constraints = keys.length
|
458
|
+
|
290
459
|
raise TranslationException, 'Invalid global constraint' if
|
291
460
|
not normalize_formula(@root['global'], true)
|
292
461
|
|
293
|
-
if GlobalConstraintMethod == 1
|
462
|
+
if GlobalConstraintMethod == 1 and @total_complex_global_constraints > 0
|
294
463
|
# dummy variable
|
295
464
|
@global_var = Variable.new(GlobalVariable, '$.Boolean', -1, false, true)
|
296
465
|
@global_var << true
|
@@ -400,6 +569,10 @@ module Sfp
|
|
400
569
|
end
|
401
570
|
|
402
571
|
def create_output
|
572
|
+
self.to_s
|
573
|
+
end
|
574
|
+
|
575
|
+
def sas
|
403
576
|
# version
|
404
577
|
out = "begin_version\n3\nend_version\n"
|
405
578
|
# metric
|
@@ -455,6 +628,7 @@ module Sfp
|
|
455
628
|
ops += op.to_sas(@root['initial'], @variables) + "\n"
|
456
629
|
total += 1
|
457
630
|
rescue Exception => exp
|
631
|
+
#puts "#{exp}\n#{exp.backtrace.join("\n")}"
|
458
632
|
end
|
459
633
|
}
|
460
634
|
out += "#{total}\n"
|
@@ -591,10 +765,12 @@ module Sfp
|
|
591
765
|
sas_op[@variables[k].name] = Parameter.new(@variables[k], pre, post)
|
592
766
|
}
|
593
767
|
|
594
|
-
if GlobalConstraintMethod == 1
|
768
|
+
if GlobalConstraintMethod == 1 and @total_complex_global_constraints > 0
|
595
769
|
@operators[sas_op.name] = sas_op if apply_global_constraint_method_1(sas_op)
|
596
770
|
elsif GlobalConstraintMethod == 2 or GlobalConstraintMethod == 3
|
597
771
|
@operators[sas_op.name] = sas_op if apply_global_constraint_method_2(sas_op)
|
772
|
+
else
|
773
|
+
@operators[sas_op.name] = sas_op
|
598
774
|
end
|
599
775
|
end
|
600
776
|
|
@@ -795,9 +971,11 @@ module Sfp
|
|
795
971
|
return true
|
796
972
|
end
|
797
973
|
|
798
|
-
# normalize the given first-order formula by transforming it
|
799
|
-
#
|
974
|
+
# normalize the given first-order formula by transforming it to DNF
|
975
|
+
#
|
800
976
|
def normalize_formula(formula, dump=false)
|
977
|
+
{:formula => formula}.accept(ImplicationToDisjunction)
|
978
|
+
|
801
979
|
def create_equals_constraint(value)
|
802
980
|
return {'_context'=>'constraint', '_type'=>'equals', '_value'=>value}
|
803
981
|
end
|
@@ -1283,6 +1461,16 @@ module Sfp
|
|
1283
1461
|
end
|
1284
1462
|
|
1285
1463
|
|
1464
|
+
ImplicationToDisjunction = Object.new
|
1465
|
+
def ImplicationToDisjunction.visit(name, value, parent)
|
1466
|
+
return if !value.is_a?(Hash)
|
1467
|
+
if value['_type'] == 'imply'
|
1468
|
+
value['_type'] = 'or'
|
1469
|
+
value.each_value { |f| f['_type'] = 'not' if f['_subtype'] == 'premise' }
|
1470
|
+
end
|
1471
|
+
true
|
1472
|
+
end
|
1473
|
+
|
1286
1474
|
### Helper Classes ###
|
1287
1475
|
|
1288
1476
|
class VariableNotFoundException < Exception; end
|
@@ -1367,14 +1555,6 @@ module Sfp
|
|
1367
1555
|
end
|
1368
1556
|
|
1369
1557
|
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
1558
|
return value.type if value.is_a?(Sfp::Undefined)
|
1379
1559
|
|
1380
1560
|
type = nil
|
@@ -1386,7 +1566,7 @@ module Sfp
|
|
1386
1566
|
end
|
1387
1567
|
end
|
1388
1568
|
type = self.isa?(value)
|
1389
|
-
|
1569
|
+
|
1390
1570
|
return "(#{value['_isa']})" if value.is_a?(Hash) and value.isset and value.has_key?('_isa')
|
1391
1571
|
|
1392
1572
|
return nil if type == nil
|
@@ -1585,6 +1765,10 @@ module Sfp
|
|
1585
1765
|
}
|
1586
1766
|
return sas += "end_variable"
|
1587
1767
|
end
|
1768
|
+
|
1769
|
+
def not(x)
|
1770
|
+
self.select { |y| y != x }
|
1771
|
+
end
|
1588
1772
|
end
|
1589
1773
|
|
1590
1774
|
# A class for Grounded Operator
|
@@ -1693,7 +1877,8 @@ module Sfp
|
|
1693
1877
|
end
|
1694
1878
|
|
1695
1879
|
def to_s
|
1696
|
-
return @name + ': ' + self.length.to_s
|
1880
|
+
#return @name + ': ' + self.length.to_s
|
1881
|
+
return @name + ": " + (self.map { |k,v| v.to_s }).join("|")
|
1697
1882
|
end
|
1698
1883
|
|
1699
1884
|
def to_sas(root, variables)
|
@@ -1769,12 +1954,24 @@ module Sfp
|
|
1769
1954
|
return 'axiom#' + @id.to_s
|
1770
1955
|
end
|
1771
1956
|
|
1772
|
-
def to_sas
|
1773
|
-
|
1957
|
+
def to_sas(root, variables)
|
1958
|
+
prevails = select { |var,param| param.post.nil? }
|
1959
|
+
preposts = select { |var,param| !param.post.nil? }
|
1960
|
+
raise Exception, "Invalid axiom: total preposts > 1" if preposts.length > 1
|
1961
|
+
raise Exception, "Invalid axiom: total preposts <= 0" if preposts.length <= 0
|
1962
|
+
|
1963
|
+
sas = "begin_rule"
|
1964
|
+
sas += "\n#{prevails.length}"
|
1965
|
+
prevails.each { |var,param| sas += "\n" + param.to_sas(root, variables) }
|
1966
|
+
preposts.each { |var,param| sas += "\n" + param.to_sas(root, variables) }
|
1967
|
+
sas += "\nend_rule"
|
1968
|
+
return sas
|
1774
1969
|
end
|
1775
1970
|
end
|
1776
1971
|
|
1777
1972
|
# A class for operator/axiom parameter (prevail or effect condition)
|
1973
|
+
# :pre = nil - it can be anything (translated to -1)
|
1974
|
+
# :post = nil - variable's value doesn't change
|
1778
1975
|
class Parameter
|
1779
1976
|
attr_accessor :var, :pre, :post
|
1780
1977
|
|
@@ -1806,7 +2003,7 @@ module Sfp
|
|
1806
2003
|
pre = ( (pre.is_a?(Hash) and pre.isnull) ? 0 : (pre == nil ? -1 : @var.index(pre)) )
|
1807
2004
|
post = ( (post.is_a?(Hash) and post.isnull) ? 0 : @var.index(post) )
|
1808
2005
|
|
1809
|
-
raise
|
2006
|
+
raise Exception, self.to_s if not prevail and post.nil?
|
1810
2007
|
|
1811
2008
|
return "#{@var.id} #{pre}" if post.nil?
|
1812
2009
|
return "0 #{@var.id} #{pre} #{post}"
|
data/sfp.gemspec
CHANGED
data/src/SfpLang.g
CHANGED
@@ -522,11 +522,7 @@ nested_constraint
|
|
522
522
|
constraint
|
523
523
|
: ID 'constraint'
|
524
524
|
{
|
525
|
-
@now[$ID.text] =
|
526
|
-
'_context' => 'constraint',
|
527
|
-
'_type' => 'and',
|
528
|
-
'_parent' => @now
|
529
|
-
}
|
525
|
+
@now[$ID.text] = self.create_constraint($ID.text, 'and')
|
530
526
|
@now = @now[$ID.text]
|
531
527
|
}
|
532
528
|
'{' NL* constraint_body '}' NL+
|
@@ -559,21 +555,13 @@ constraint_iterator
|
|
559
555
|
: 'foreach' '(' path 'as' ID ')' NL* '{' NL+
|
560
556
|
{
|
561
557
|
id = self.next_id.to_s
|
562
|
-
@now[id] =
|
563
|
-
|
564
|
-
|
565
|
-
'_self' => id,
|
566
|
-
'_value' => '$.' + $path.text,
|
567
|
-
'_variable' => $ID.text
|
568
|
-
}
|
558
|
+
@now[id] = self.create_constraint(id, 'iterator')
|
559
|
+
@now[id]['_value'] = '$.' + $path.text
|
560
|
+
@now[id]['_variable'] = $ID.text
|
569
561
|
@now = @now[id]
|
570
562
|
|
571
563
|
id = '_template'
|
572
|
-
@now[id] =
|
573
|
-
'_context' => 'constraint',
|
574
|
-
'_type' => 'and',
|
575
|
-
'_self' => id,
|
576
|
-
}
|
564
|
+
@now[id] = self.create_constraint(id, 'and')
|
577
565
|
@now = @now[id]
|
578
566
|
}
|
579
567
|
(constraint_statement
|
@@ -608,11 +596,7 @@ constraint_class_quantification
|
|
608
596
|
@now = @now[id]
|
609
597
|
|
610
598
|
id = '_template'
|
611
|
-
@now[id] =
|
612
|
-
'_context' => 'constraint',
|
613
|
-
'_type' => 'and',
|
614
|
-
'_self' => id
|
615
|
-
}
|
599
|
+
@now[id] = self.create_constraint(id, 'and')
|
616
600
|
@now = @now[id]
|
617
601
|
}
|
618
602
|
( ( binary_comp
|
@@ -746,10 +730,7 @@ conditional_constraint returns [key, val]
|
|
746
730
|
: 'if'
|
747
731
|
{
|
748
732
|
$key = id = self.next_id.to_s
|
749
|
-
@now[id] =
|
750
|
-
'_context' => 'constraint',
|
751
|
-
'_type' => 'or'
|
752
|
-
}
|
733
|
+
@now[id] = self.create_constraint(id, 'imply')
|
753
734
|
@now = @now[id]
|
754
735
|
}
|
755
736
|
conditional_constraint_if_part
|
@@ -761,16 +742,15 @@ conditional_constraint_if_part
|
|
761
742
|
: constraint_statement NL*
|
762
743
|
{
|
763
744
|
id = self.next_id
|
764
|
-
@now[id] =
|
765
|
-
|
766
|
-
'_type' => 'not'
|
767
|
-
}
|
745
|
+
@now[id] = self.create_constraint(id, 'and')
|
746
|
+
@now[id]['_subtype'] = 'premise'
|
768
747
|
@now[id][$constraint_statement.key] = $constraint_statement.val
|
769
748
|
}
|
770
749
|
| '{'
|
771
750
|
{
|
772
751
|
id = self.next_id
|
773
|
-
@now[id] = self.create_constraint(id, '
|
752
|
+
@now[id] = self.create_constraint(id, 'and')
|
753
|
+
@now[id]['_subtype'] = 'premise'
|
774
754
|
@now = @now[id]
|
775
755
|
}
|
776
756
|
NL+ constraint_body
|
@@ -782,19 +762,15 @@ conditional_constraint_then_part
|
|
782
762
|
: 'then' constraint_statement
|
783
763
|
{
|
784
764
|
id = self.next_id
|
785
|
-
@now[id] =
|
786
|
-
|
787
|
-
'_type' => 'and'
|
788
|
-
}
|
765
|
+
@now[id] = self.create_constraint(id, 'and')
|
766
|
+
@now[id]['_subtype'] = 'conclusion'
|
789
767
|
@now[id][$constraint_statement.key] = $constraint_statement.val
|
790
768
|
}
|
791
769
|
| 'then'
|
792
770
|
{
|
793
771
|
id = self.next_id
|
794
|
-
@now[id] =
|
795
|
-
|
796
|
-
'_type' => 'and'
|
797
|
-
}
|
772
|
+
@now[id] = self.create_constraint(id, 'and')
|
773
|
+
@now[id]['_subtype'] = 'conclusion'
|
798
774
|
@now = @now[id]
|
799
775
|
}
|
800
776
|
'{' NL+ constraint_body '}'
|