sfp 0.3.4 → 0.3.5
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/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 '}'
|