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/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
+ }
@@ -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 into
799
- # DNF
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
- #TODO
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 TranslationException if not prevail and post.nil?
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
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'sfp'
3
- s.version = '0.3.4'
4
- s.date = '2013-07-14'
3
+ s.version = '0.3.5'
4
+ s.date = '2013-07-15'
5
5
  s.summary = 'SFP Parser'
6
6
  s.description = 'A Ruby API and script for SFP language parser'
7
7
  s.authors = ['Herry']
data/src/SfpLang.g CHANGED
@@ -522,11 +522,7 @@ nested_constraint
522
522
  constraint
523
523
  : ID 'constraint'
524
524
  {
525
- @now[$ID.text] = { '_self' => $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] = { '_parent' => @now,
563
- '_context' => 'constraint',
564
- '_type' => 'iterator',
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] = { '_parent' => @now,
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] = { '_parent' => @now,
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] = { '_parent' => @now,
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] = { '_parent' => @now,
765
- '_context' => 'constraint',
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, 'not')
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] = { '_parent' => @now,
786
- '_context' => 'constraint',
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] = { '_parent' => @now,
795
- '_context' => 'constraint',
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 '}'