adsl 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+