adsl 0.0.3 → 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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -20
  3. data/README.md +14 -21
  4. data/bin/adsl-verify +8 -8
  5. data/lib/adsl.rb +3 -0
  6. data/lib/adsl/adsl.rb +3 -0
  7. data/lib/adsl/ds/data_store_spec.rb +339 -0
  8. data/lib/adsl/extract/instrumenter.rb +206 -0
  9. data/lib/adsl/extract/meta.rb +33 -0
  10. data/lib/adsl/extract/rails/action_block_builder.rb +233 -0
  11. data/lib/adsl/extract/rails/action_instrumenter.rb +400 -0
  12. data/lib/adsl/extract/rails/action_runner.rb +57 -0
  13. data/lib/adsl/extract/rails/active_record_metaclass_generator.rb +555 -0
  14. data/lib/adsl/extract/rails/callback_chain_simulator.rb +135 -0
  15. data/lib/adsl/extract/rails/invariant_extractor.rb +48 -0
  16. data/lib/adsl/extract/rails/invariant_instrumenter.rb +70 -0
  17. data/lib/adsl/extract/rails/other_meta.rb +57 -0
  18. data/lib/adsl/extract/rails/rails_extractor.rb +211 -0
  19. data/lib/adsl/extract/rails/rails_instrumentation_test_case.rb +34 -0
  20. data/lib/adsl/extract/rails/rails_special_gem_instrumentation.rb +120 -0
  21. data/lib/adsl/extract/rails/rails_test_helper.rb +140 -0
  22. data/lib/adsl/extract/sexp_utils.rb +54 -0
  23. data/lib/adsl/fol/first_order_logic.rb +261 -0
  24. data/lib/adsl/parser/adsl_parser.racc +159 -0
  25. data/lib/{parser → adsl/parser}/adsl_parser.rex +4 -4
  26. data/lib/{parser → adsl/parser}/adsl_parser.rex.rb +6 -6
  27. data/lib/adsl/parser/adsl_parser.tab.rb +1031 -0
  28. data/lib/adsl/parser/ast_nodes.rb +1410 -0
  29. data/lib/adsl/railtie.rb +67 -0
  30. data/lib/adsl/spass/bin.rb +230 -0
  31. data/lib/{spass → adsl/spass}/ruby_extensions.rb +0 -0
  32. data/lib/adsl/spass/spass_ds_extensions.rb +931 -0
  33. data/lib/adsl/spass/spass_translator.rb +393 -0
  34. data/lib/adsl/spass/util.rb +13 -0
  35. data/lib/adsl/util/csv_hash_formatter.rb +94 -0
  36. data/lib/adsl/util/general.rb +228 -0
  37. data/lib/adsl/util/test_helper.rb +71 -0
  38. data/lib/adsl/verification/formula_generators.rb +231 -0
  39. data/lib/adsl/verification/instrumentation_filter.rb +50 -0
  40. data/lib/adsl/verification/invariant.rb +19 -0
  41. data/lib/adsl/verification/rails_verification.rb +33 -0
  42. data/lib/adsl/verification/utils.rb +20 -0
  43. data/lib/adsl/verification/verification_case.rb +13 -0
  44. data/test/integration/rails/rails_branch_verification_test.rb +112 -0
  45. data/test/integration/rails/rails_verification_test.rb +253 -0
  46. data/test/integration/spass/basic_translation_test.rb +563 -0
  47. data/test/integration/spass/control_flow_translation_test.rb +421 -0
  48. data/test/unit/adsl/ds/data_store_spec_test.rb +54 -0
  49. data/test/unit/adsl/extract/instrumenter_test.rb +103 -0
  50. data/test/unit/adsl/extract/meta_test.rb +142 -0
  51. data/test/unit/adsl/extract/rails/action_block_builder_test.rb +178 -0
  52. data/test/unit/adsl/extract/rails/action_instrumenter_test.rb +68 -0
  53. data/test/unit/adsl/extract/rails/active_record_metaclass_generator_test.rb +336 -0
  54. data/test/unit/adsl/extract/rails/callback_chain_simulator_test.rb +76 -0
  55. data/test/unit/adsl/extract/rails/invariant_extractor_test.rb +92 -0
  56. data/test/unit/adsl/extract/rails/rails_extractor_test.rb +1380 -0
  57. data/test/unit/adsl/extract/rails/rails_test_helper_test.rb +25 -0
  58. data/test/unit/adsl/extract/sexp_utils_test.rb +100 -0
  59. data/test/unit/adsl/fol/first_order_logic_test.rb +227 -0
  60. data/test/unit/adsl/parser/action_parser_test.rb +1040 -0
  61. data/test/unit/adsl/parser/ast_nodes_test.rb +359 -0
  62. data/test/unit/adsl/parser/class_parser_test.rb +288 -0
  63. data/test/unit/adsl/parser/general_parser_test.rb +67 -0
  64. data/test/unit/adsl/parser/invariant_parser_test.rb +432 -0
  65. data/test/unit/adsl/parser/parser_util_test.rb +126 -0
  66. data/test/unit/adsl/spass/bin_test.rb +65 -0
  67. data/test/unit/adsl/spass/ruby_extensions_test.rb +39 -0
  68. data/test/unit/adsl/spass/spass_ds_extensions_test.rb +7 -0
  69. data/test/unit/adsl/spass/spass_translator_test.rb +342 -0
  70. data/test/unit/adsl/util/csv_hash_formatter_test.rb +68 -0
  71. data/test/unit/adsl/util/general_test.rb +303 -0
  72. data/test/unit/adsl/util/test_helper_test.rb +120 -0
  73. data/test/unit/adsl/verification/formula_generators_test.rb +200 -0
  74. data/test/unit/adsl/verification/instrumentation_filter_test.rb +39 -0
  75. data/test/unit/adsl/verification/utils_test.rb +39 -0
  76. data/test/unit/adsl/verification/verification_case_test.rb +8 -0
  77. metadata +229 -29
  78. data/lib/ds/data_store_spec.rb +0 -292
  79. data/lib/fol/first_order_logic.rb +0 -260
  80. data/lib/parser/adsl_ast.rb +0 -779
  81. data/lib/parser/adsl_parser.racc +0 -151
  82. data/lib/parser/adsl_parser.tab.rb +0 -976
  83. data/lib/parser/dsdl_parser.rex.rb +0 -196
  84. data/lib/parser/dsdl_parser.tab.rb +0 -976
  85. data/lib/spass/bin.rb +0 -164
  86. data/lib/spass/spass_ds_extensions.rb +0 -870
  87. data/lib/spass/spass_translator.rb +0 -388
  88. data/lib/spass/util.rb +0 -11
  89. data/lib/util/csv_hash_formatter.rb +0 -47
  90. data/lib/util/test_helper.rb +0 -33
  91. data/lib/util/util.rb +0 -114
@@ -0,0 +1,34 @@
1
+ require 'test/unit'
2
+ require 'adsl/util/test_helper'
3
+ require 'adsl/extract/rails/active_record_metaclass_generator'
4
+ require 'adsl/extract/rails/rails_test_helper'
5
+
6
+ class ADSL::Extract::Rails::RailsInstrumentationTestCase < Test::Unit::TestCase
7
+ def setup
8
+ assert_false class_defined? :ADSLMetaAsd, :ADSLMetaKme, 'Mod::ADSLMetaBlah'
9
+
10
+ initialize_test_context
11
+ end
12
+
13
+ def teardown
14
+ unload_class :Asd, :Kme, 'Mod::Blah'
15
+ end
16
+
17
+ def initialize_metaclasses
18
+ ADSL::Extract::Rails::ActiveRecordMetaclassGenerator.new(Asd).generate_class
19
+ ADSL::Extract::Rails::ActiveRecordMetaclassGenerator.new(Kme).generate_class
20
+ ADSL::Extract::Rails::ActiveRecordMetaclassGenerator.new(Mod::Blah).generate_class
21
+ end
22
+
23
+ def create_rails_extractor(invariant_string = '')
24
+ ADSL::Extract::Rails::RailsExtractor.new :ar_classes => ar_classes, :invariants => invariant_string
25
+ end
26
+
27
+ def ar_class_names
28
+ ['Asd', 'Kme', 'Mod::Blah']
29
+ end
30
+
31
+ def ar_classes
32
+ ar_class_names.map(&:constantize)
33
+ end
34
+ end
@@ -0,0 +1,120 @@
1
+ require 'adsl/extract/rails/other_meta'
2
+
3
+ module ADSL
4
+ module Extract
5
+ module Rails
6
+ module RailsSpecialGemInstrumentation
7
+
8
+ def instrument_gem_cancan(controller_class, action)
9
+ return unless klass = Object.lookup_const('CanCan::ControllerResource')
10
+
11
+ model_class_name = controller_class.controller_name.singularize.camelize
12
+ klass.class_eval <<-ruby, __FILE__, __LINE__
13
+ def name_from_controller
14
+ "#{ model_class_name.underscore.singularize }"
15
+ end
16
+
17
+ def namespace
18
+ [#{ model_class_name.split('::')[0..-2].map{ |a| "'#{a}'" }.join(', ') }]
19
+ end
20
+
21
+ def authorize_resource; end
22
+
23
+ def find_resource
24
+ #{ model_class_name }.find
25
+ end
26
+
27
+ def build_resource
28
+ #{ model_class_name }.new
29
+ end
30
+
31
+ def load_collection
32
+ #{ model_class_name }.where
33
+ end
34
+
35
+ def load_resource
36
+ unless skip?(:load)
37
+ var_name, objset = if load_instance?
38
+ [instance_name.to_s, load_resource_instance]
39
+ else
40
+ [instance_name.to_s.pluralize, load_collection]
41
+ end
42
+ ins_explore_all 'load_resource' do
43
+ ins_stmt(ADSL::Parser::ASTAssignment.new(
44
+ :var_name => ADSL::Parser::ASTIdent.new(:text => "at__\#{var_name}"),
45
+ :objset => objset.adsl_ast
46
+ ))
47
+ nil
48
+ end
49
+ @controller.instance_variable_set(
50
+ "@\#{ var_name }",
51
+ #{ model_class_name }.new(:adsl_ast => ADSL::Parser::ASTVariable.new(
52
+ :var_name => ADSL::Parser::ASTIdent.new(:text => "at__\#{var_name}")
53
+ ))
54
+ )
55
+ end
56
+ end
57
+ ruby
58
+
59
+ controller_class.class_exec do
60
+ def authorize!(*args); end
61
+ end
62
+
63
+ end
64
+
65
+ def instrument_gem_ransack(controller_class, action)
66
+ if Object.lookup_const('RansackUI')
67
+ Object.lookup_const('RansackUI::ControllerHelpers').class_exec do
68
+ def load_ransack_search(*args); end
69
+ end
70
+ end
71
+
72
+ if Object.lookup_const('Ransack')
73
+ ActiveRecord::Base.class_exec do
74
+ def search(*args); where; end
75
+ end
76
+ end
77
+ end
78
+
79
+ def instrument_gem_authlogic(controller_class, action)
80
+ return unless Object.lookup_const('Authlogic')
81
+
82
+ # only instrument this if there is a class called User; assume that a user is the logged in entity
83
+ return unless Object.lookup_const('User')
84
+
85
+ Object.lookup_const('Authlogic::Session::Base').class_exec do
86
+ def self.find(*args)
87
+ ADSL::Extract::Rails::PartiallyUnknownHash.new(
88
+ :record => User.find
89
+ )
90
+ end
91
+ end
92
+ end
93
+
94
+ def instrument_gem_devise(controller_class, action)
95
+ return unless Object.lookup_const('Devise')
96
+
97
+ Devise.mappings.values.each do |mapping|
98
+ role = mapping.singular
99
+ role_class = mapping.class_name
100
+ controller_class.class_eval <<-ruby
101
+ def authenticate_#{role}!(*args); end
102
+ def #{role}_signed_in?(*args); true; end
103
+ def current_#{role}(*args); #{role_class}.find(-1); end
104
+ def #{role}_session(*args); ::ADSL::Extract::MetaUnknown.new; end
105
+ def only_render_implemented_actions(*args); end
106
+ ruby
107
+ end
108
+ end
109
+
110
+ def instrument_gems(controller_class, action)
111
+ instrument_gem_cancan controller_class, action
112
+ instrument_gem_ransack controller_class, action
113
+ instrument_gem_authlogic controller_class, action
114
+ instrument_gem_devise controller_class, action
115
+ end
116
+
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,140 @@
1
+ require 'adsl/util/general'
2
+ require 'active_record'
3
+ require 'action_controller'
4
+ require 'action_view'
5
+
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+
9
+ ENV["RAILS_ENV"] ||= 'test'
10
+
11
+ def initialize_test_context
12
+ Object.lookup_or_create_class('::Asd', ActiveRecord::Base).class_exec do
13
+ has_many :blahs, :class_name => 'Mod::Blah'
14
+ has_many :kmes, :through => :blahs, :source => :kme12, :dependent => :destroy
15
+ end
16
+
17
+ Object.lookup_or_create_class('::Kme', Asd).class_exec do
18
+ belongs_to :blah, :class_name => 'Mod::Blah', :dependent => :delete
19
+ end
20
+
21
+ Object.lookup_or_create_class('::Mod::Blah', ActiveRecord::Base).class_exec do
22
+ belongs_to :asd
23
+ has_one :kme12, :class_name => 'Kme', :dependent => :delete
24
+ end
25
+
26
+ Object.lookup_or_create_class('::ApplicationController', ActionController::Base).class_exec do
27
+ def respond_to
28
+ # allow for empty render statements, for testing purposes only
29
+ if block_given?
30
+ super
31
+ else
32
+ render :nothing => true
33
+ end
34
+ end
35
+
36
+ def render(options = {}, extra_options = nil, &block)
37
+ options ||= {}
38
+ options[:nothing] = true
39
+ super
40
+ end
41
+
42
+ # no templates exist and we do not care
43
+ rescue_from ActionView::MissingTemplate do; end
44
+ end
45
+
46
+ Object.lookup_or_create_class('::AsdsController', ApplicationController).class_exec do
47
+ def index; respond_to; end
48
+ def show; respond_to; end
49
+ def new; respond_to; end
50
+
51
+ def create
52
+ a = Asd.new
53
+ a.save!
54
+ respond_to
55
+ end
56
+
57
+ def edit; respond_to; end
58
+ def update; respond_to; end
59
+ def destroy; respond_to; end
60
+ def nothing; respond_to; end
61
+
62
+ before_filter :before, :only => :before_filter_action
63
+ before_filter :before2, :only => :before_filter_action
64
+ after_filter :after, :only => :after_filter_action
65
+
66
+ before_filter :before_nothing, :only => :nothing
67
+ after_filter :after_nothing, :only => :nothing
68
+
69
+ def before_filter_action; respond_to; end
70
+ def after_filter_action; respond_to; end
71
+
72
+ def before; end
73
+ def before2; end
74
+ def after; end
75
+
76
+ def before_nothing; end
77
+ def after_nothing; end
78
+ end
79
+ end
80
+
81
+ # Only the parts of rails we want to use
82
+ # if you want everything, use "rails/all"
83
+ require "action_controller/railtie"
84
+
85
+ if ENV["RAILS_ENV"] == 'test'
86
+
87
+ # Define the application and configuration
88
+ class ADSLRailsTestApplication < ::Rails::Application
89
+ # configuration here if needed
90
+ config.assets.enabled = false
91
+ config.active_support.deprecation = :stderr
92
+ config.secret_token = 'RandomTextRequiredByARecentVersionOfRakeOrWhateverWhoCaresThisIsUsedForGemTestingOnly'
93
+ config.action_dispatch.show_exceptions = false
94
+ end
95
+
96
+ logger = Logger.new(STDOUT)
97
+ logger.level = Logger::ERROR
98
+ # silence is getting deprecated in Rails 4.0
99
+ # not sure why, the alternatives are not nearly as convenient
100
+ def logger.adsl_silence
101
+ old_level = self.level
102
+ self.level = 6
103
+ yield
104
+ ensure
105
+ self.level = old_level
106
+ end
107
+
108
+ Rails.logger = logger
109
+
110
+ ActiveRecord::Base.establish_connection(
111
+ :adapter => 'sqlite3',
112
+ :database => ':memory:',
113
+ :verbosity => 'quiet'
114
+ )
115
+
116
+ # Initialize the application
117
+ ADSLRailsTestApplication.initialize!
118
+ ADSLRailsTestApplication.routes.draw do
119
+ resources :asds do
120
+ collection do
121
+ get :nothing
122
+ get :before_filter_action
123
+ get :after_filter_action
124
+ get :around_filter_action
125
+ end
126
+ end
127
+ end
128
+
129
+ # Initialize the test activerecord schema
130
+ ActiveRecord::Migration.verbose = false
131
+ ActiveRecord::Schema.define do
132
+ create_table :asds do |t|
133
+ t.string :type
134
+ end
135
+ create_table :blahs do |t|
136
+ t.integer :asd_id
137
+ end
138
+ end
139
+ end
140
+
@@ -0,0 +1,54 @@
1
+ require 'sexp_processor'
2
+
3
+ class Sexp
4
+ def block_replace(*search_types, &block)
5
+ options = search_types.last.is_a?(Hash) ? search_types.pop : {}
6
+
7
+ propagate_to_children = true
8
+ if options.include?(:unless_in)
9
+ options[:unless_in] = Array.wrap options[:unless_in]
10
+ propagate_to_children = false if options[:unless_in].include?(self.sexp_type)
11
+ end
12
+
13
+ new = self.map do |element|
14
+ if propagate_to_children && element.is_a?(Sexp)
15
+ result = element.block_replace *search_types, options, &block
16
+ result = [result] unless result.class == Array
17
+ result
18
+ else
19
+ [element]
20
+ end
21
+ end
22
+ result = Sexp.from_array new.flatten(1)
23
+ result = block[result] if search_types.include? result.sexp_type
24
+ return result
25
+ end
26
+
27
+ def find_shallowest(search_type)
28
+ return [self] if sexp_type == search_type
29
+ self.inject([]) do |collection, subsexp|
30
+ collection << subsexp.find_shallowest(search_type) if subsexp.is_a?(Sexp)
31
+ collection
32
+ end.flatten(1)
33
+ end
34
+
35
+ def may_return_or_raise?
36
+ return true if sexp_type == :return
37
+ return true if sexp_type == :call && self[2] == :raise
38
+ sexp_body.each do |subsexp|
39
+ return true if subsexp.is_a?(Sexp) && subsexp.may_return_or_raise?
40
+ end
41
+ false
42
+ end
43
+ end
44
+
45
+ class Module
46
+ def to_sexp
47
+ parts = self.name.split('::')
48
+ sexp = s(:colon3, parts.shift.to_sym)
49
+ parts.each do |part|
50
+ sexp = s(:colon2, sexp, part.to_sym)
51
+ end
52
+ sexp
53
+ end
54
+ end
@@ -0,0 +1,261 @@
1
+ require 'adsl/util/general'
2
+ require 'active_support/all'
3
+
4
+ class String
5
+ def resolve_spass
6
+ self
7
+ end
8
+
9
+ def split_by_zero_level_comma
10
+ parts = []
11
+ sequence_beginning_index = 0
12
+ index = 0
13
+ paren_level = 0
14
+ while index < length
15
+ if self[index, 1] == '('
16
+ paren_level += 1
17
+ elsif self[index, 1] == ')'
18
+ paren_level -= 1
19
+ raise ArgumentError, 'Unmatching parenthesis' if paren_level < 0
20
+ elsif self[index, 1] == ',' and paren_level == 0
21
+ parts << self[sequence_beginning_index, index - sequence_beginning_index].strip
22
+ sequence_beginning_index = index + 1
23
+ end
24
+ index += 1
25
+ end
26
+ parts << self[sequence_beginning_index, length - sequence_beginning_index].strip
27
+ parts
28
+ end
29
+ end
30
+
31
+ class Symbol
32
+ def resolve_spass
33
+ to_s
34
+ end
35
+ end
36
+
37
+ class TrueClass
38
+ def resolve_spass
39
+ "true"
40
+ end
41
+ end
42
+
43
+ class FalseClass
44
+ def resolve_spass
45
+ "false"
46
+ end
47
+ end
48
+
49
+ module ADSL
50
+ module FOL
51
+ class Not
52
+ def initialize(*formulae)
53
+ @formulae = formulae.flatten
54
+ raise ArgumentError, "At least one subformula required" if @formulae.empty?
55
+ end
56
+
57
+ def resolve_spass
58
+ children = @formulae.map{ |obj| obj.resolve_spass }
59
+ children.delete_if{ |a| a == 'false' }
60
+ return 'false' if children.include? 'true'
61
+ return And.new(children.map{ |child| child.match('\Anot\((.*)\)\z') ? $1 : "not(#{child})" }).resolve_spass
62
+ end
63
+ end
64
+
65
+ class And
66
+ attr_reader :objs
67
+
68
+ def initialize(*objs)
69
+ @objs = objs.flatten
70
+ end
71
+
72
+ def resolve_spass
73
+ children = @objs.map{ |child| child.resolve_spass }
74
+ children = children.map{ |child| child.match('\Aand\((.*)\)\z') ? $1.split_by_zero_level_comma : child }.flatten
75
+ children.delete_if{ |a| a == 'true' }
76
+ return 'false' if children.include? 'false'
77
+ return 'true' if children.empty?
78
+ return children.first if children.length == 1
79
+ return "and(#{children.join(', ')})"
80
+ end
81
+ end
82
+
83
+ class Or
84
+ attr_reader :objs
85
+
86
+ def initialize(*objs)
87
+ @objs = objs.flatten
88
+ end
89
+
90
+ def resolve_spass
91
+ children = @objs.map{ |child| child.resolve_spass }
92
+ children = children.map{ |child| child.match('\Aor\((.*)\)\z') ? $1.split_by_zero_level_comma : child }.flatten
93
+ children.delete_if{ |a| a == 'false' }
94
+ return 'true' if children.include? 'true'
95
+ return 'false' if children.empty?
96
+ return children.first if children.length == 1
97
+ return "or(#{children.join(', ')})"
98
+ end
99
+ end
100
+
101
+ class ForAll
102
+ def initialize(*params)
103
+ params = params.flatten
104
+ raise ArgumentError, "At least a formula required" if params.length < 1
105
+ @args = params.first(params.length - 1)
106
+ @formula = params.last
107
+ end
108
+
109
+ def resolve_spass
110
+ args = @args.map{ |obj| obj.resolve_spass }
111
+ formula = @formula.resolve_spass
112
+ return formula if args.empty?
113
+ return 'true' if formula == 'true'
114
+ return 'false' if formula == 'false'
115
+ "forall( [#{args.join(', ')}], #{formula})"
116
+ end
117
+ end
118
+
119
+ class Exists
120
+ def initialize(*params)
121
+ params = params.flatten
122
+ raise ArgumentError, "At least a formula required" if params.length < 1
123
+ @args = params.first(params.length - 1)
124
+ @formula = params.last
125
+ end
126
+
127
+ def resolve_spass
128
+ args = @args.map{ |obj| obj.resolve_spass }
129
+ formula = @formula.resolve_spass
130
+ return formula if args.empty?
131
+ return 'true' if formula == 'true'
132
+ return 'false' if formula == 'false'
133
+ "exists( [#{args.join(', ')}], #{formula})"
134
+ end
135
+ end
136
+
137
+ class Equal
138
+ def initialize(*subformulae)
139
+ @subformulae = subformulae.flatten
140
+ raise ArgumentError, "At least two subformulae required" if @subformulae.length < 2
141
+ end
142
+
143
+ def resolve_spass
144
+ return @subformulae.first.resolve_spass if @subformulae.length == 1
145
+ combinations = []
146
+ (@subformulae.length-1).times do |index|
147
+ combinations << "equal(#{@subformulae[index].resolve_spass}, #{@subformulae[index+1].resolve_spass})"
148
+ end
149
+ return And.new(combinations).resolve_spass
150
+ end
151
+ end
152
+
153
+ class Equiv
154
+ def initialize(*subformulae)
155
+ @subformulae = subformulae.flatten
156
+ raise ArgumentError, "At least two subformulae required" if @subformulae.length < 2
157
+ end
158
+
159
+ def resolve_spass
160
+ subformulae = @subformulae.map{ |sub| sub.resolve_spass }
161
+ return subformulae.first if subformulae.length == 1
162
+ return And.new(subformulae).resolve_spass if subformulae.include? 'true'
163
+ return Not.new(subformulae).resolve_spass if subformulae.include? 'false'
164
+ combinations = []
165
+ (subformulae.length-1).times do |index|
166
+ combinations << "equiv(#{subformulae[index]}, #{subformulae[index+1]})"
167
+ end
168
+ return And.new(combinations).resolve_spass
169
+ end
170
+ end
171
+
172
+ class Implies
173
+ def initialize(from, to)
174
+ @from = from
175
+ @to = to
176
+ end
177
+
178
+ def resolve_spass
179
+ from = @from.resolve_spass
180
+ to = @to.resolve_spass
181
+ return to if from == 'true'
182
+ return 'true' if from == 'false'
183
+ return Not.new(from).resolve_spass if to == 'false'
184
+ return 'true' if to == 'true'
185
+ return "implies(#{from}, #{to})"
186
+ end
187
+ end
188
+
189
+ class OneOf
190
+ def initialize(*formulae)
191
+ @formulae = formulae.flatten
192
+ end
193
+
194
+ def resolve_spass
195
+ return 'false' if @formulae.empty?
196
+ return @formulae.first.resolve_spass if @formulae.length == 1
197
+ return Equiv.new(Not.new(@formulae.first), @formulae.last).resolve_spass if @formulae.length == 2
198
+
199
+ substatements = []
200
+ @formulae.length.times do |i|
201
+ formulae_without_i = @formulae.first(i) + @formulae.last(@formulae.length - 1 - i)
202
+ substatements << Implies.new(@formulae[i], Not.new(formulae_without_i))
203
+ end
204
+ And.new(Or.new(@formulae), substatements).resolve_spass
205
+ end
206
+ end
207
+
208
+ class IfThenElse
209
+ def initialize(iif, tthen, eelse)
210
+ @iif = iif
211
+ @tthen = tthen
212
+ @eelse = eelse
213
+ end
214
+
215
+ def resolve_spass
216
+ And.new(Implies.new(@iif, @tthen), Implies.new(Not.new(@iif), @eelse)).resolve_spass
217
+ end
218
+ end
219
+
220
+ class IfThenElseEq
221
+ def initialize(iif, tthen, eelse)
222
+ @iif = iif
223
+ @tthen = tthen
224
+ @eelse = eelse
225
+ end
226
+
227
+ def resolve_spass
228
+ And.new(Equiv.new(@iif, @tthen), Equiv.new(Not.new(@iif), @eelse)).resolve_spass
229
+ end
230
+ end
231
+
232
+ class PairwiseEqual
233
+ def initialize(*list)
234
+ list = list.flatten
235
+ @list1 = list.first((list.length/2.0).ceil)
236
+ @list2 = list.last((list.length/2.0).floor)
237
+ raise ArgumentError, "Lists not of equal length: [#{@list1.join(", ")}], [#{@list2.join(", ")}]" if @list1.length != @list2.length
238
+ end
239
+
240
+ def resolve_spass
241
+ equalities = []
242
+ @list1.length.times do |i|
243
+ equalities << Equal.new(@list1[i], @list2[i])
244
+ end
245
+ return And.new(equalities).resolve_spass
246
+ end
247
+ end
248
+
249
+ # define a method for each of the above classes, starting with underscore and underscored* afterwards
250
+ # *see: http://api.rubyonrails.org/v2.3.8/classes/ActiveSupport/CoreExtensions/String/Inflections.html
251
+ self.constants.each do |klass_name|
252
+ instance_eval do
253
+ klass = FOL.const_get klass_name
254
+ send :define_method, "_#{klass_name.to_s.underscore}".to_sym do |*args|
255
+ klass.new(*args).resolve_spass
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+