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/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 '}'