adsl 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -20
- data/README.md +14 -21
- data/bin/adsl-verify +8 -8
- data/lib/adsl.rb +3 -0
- data/lib/adsl/adsl.rb +3 -0
- data/lib/adsl/ds/data_store_spec.rb +339 -0
- data/lib/adsl/extract/instrumenter.rb +206 -0
- data/lib/adsl/extract/meta.rb +33 -0
- data/lib/adsl/extract/rails/action_block_builder.rb +233 -0
- data/lib/adsl/extract/rails/action_instrumenter.rb +400 -0
- data/lib/adsl/extract/rails/action_runner.rb +57 -0
- data/lib/adsl/extract/rails/active_record_metaclass_generator.rb +555 -0
- data/lib/adsl/extract/rails/callback_chain_simulator.rb +135 -0
- data/lib/adsl/extract/rails/invariant_extractor.rb +48 -0
- data/lib/adsl/extract/rails/invariant_instrumenter.rb +70 -0
- data/lib/adsl/extract/rails/other_meta.rb +57 -0
- data/lib/adsl/extract/rails/rails_extractor.rb +211 -0
- data/lib/adsl/extract/rails/rails_instrumentation_test_case.rb +34 -0
- data/lib/adsl/extract/rails/rails_special_gem_instrumentation.rb +120 -0
- data/lib/adsl/extract/rails/rails_test_helper.rb +140 -0
- data/lib/adsl/extract/sexp_utils.rb +54 -0
- data/lib/adsl/fol/first_order_logic.rb +261 -0
- data/lib/adsl/parser/adsl_parser.racc +159 -0
- data/lib/{parser → adsl/parser}/adsl_parser.rex +4 -4
- data/lib/{parser → adsl/parser}/adsl_parser.rex.rb +6 -6
- data/lib/adsl/parser/adsl_parser.tab.rb +1031 -0
- data/lib/adsl/parser/ast_nodes.rb +1410 -0
- data/lib/adsl/railtie.rb +67 -0
- data/lib/adsl/spass/bin.rb +230 -0
- data/lib/{spass → adsl/spass}/ruby_extensions.rb +0 -0
- data/lib/adsl/spass/spass_ds_extensions.rb +931 -0
- data/lib/adsl/spass/spass_translator.rb +393 -0
- data/lib/adsl/spass/util.rb +13 -0
- data/lib/adsl/util/csv_hash_formatter.rb +94 -0
- data/lib/adsl/util/general.rb +228 -0
- data/lib/adsl/util/test_helper.rb +71 -0
- data/lib/adsl/verification/formula_generators.rb +231 -0
- data/lib/adsl/verification/instrumentation_filter.rb +50 -0
- data/lib/adsl/verification/invariant.rb +19 -0
- data/lib/adsl/verification/rails_verification.rb +33 -0
- data/lib/adsl/verification/utils.rb +20 -0
- data/lib/adsl/verification/verification_case.rb +13 -0
- data/test/integration/rails/rails_branch_verification_test.rb +112 -0
- data/test/integration/rails/rails_verification_test.rb +253 -0
- data/test/integration/spass/basic_translation_test.rb +563 -0
- data/test/integration/spass/control_flow_translation_test.rb +421 -0
- data/test/unit/adsl/ds/data_store_spec_test.rb +54 -0
- data/test/unit/adsl/extract/instrumenter_test.rb +103 -0
- data/test/unit/adsl/extract/meta_test.rb +142 -0
- data/test/unit/adsl/extract/rails/action_block_builder_test.rb +178 -0
- data/test/unit/adsl/extract/rails/action_instrumenter_test.rb +68 -0
- data/test/unit/adsl/extract/rails/active_record_metaclass_generator_test.rb +336 -0
- data/test/unit/adsl/extract/rails/callback_chain_simulator_test.rb +76 -0
- data/test/unit/adsl/extract/rails/invariant_extractor_test.rb +92 -0
- data/test/unit/adsl/extract/rails/rails_extractor_test.rb +1380 -0
- data/test/unit/adsl/extract/rails/rails_test_helper_test.rb +25 -0
- data/test/unit/adsl/extract/sexp_utils_test.rb +100 -0
- data/test/unit/adsl/fol/first_order_logic_test.rb +227 -0
- data/test/unit/adsl/parser/action_parser_test.rb +1040 -0
- data/test/unit/adsl/parser/ast_nodes_test.rb +359 -0
- data/test/unit/adsl/parser/class_parser_test.rb +288 -0
- data/test/unit/adsl/parser/general_parser_test.rb +67 -0
- data/test/unit/adsl/parser/invariant_parser_test.rb +432 -0
- data/test/unit/adsl/parser/parser_util_test.rb +126 -0
- data/test/unit/adsl/spass/bin_test.rb +65 -0
- data/test/unit/adsl/spass/ruby_extensions_test.rb +39 -0
- data/test/unit/adsl/spass/spass_ds_extensions_test.rb +7 -0
- data/test/unit/adsl/spass/spass_translator_test.rb +342 -0
- data/test/unit/adsl/util/csv_hash_formatter_test.rb +68 -0
- data/test/unit/adsl/util/general_test.rb +303 -0
- data/test/unit/adsl/util/test_helper_test.rb +120 -0
- data/test/unit/adsl/verification/formula_generators_test.rb +200 -0
- data/test/unit/adsl/verification/instrumentation_filter_test.rb +39 -0
- data/test/unit/adsl/verification/utils_test.rb +39 -0
- data/test/unit/adsl/verification/verification_case_test.rb +8 -0
- metadata +229 -29
- data/lib/ds/data_store_spec.rb +0 -292
- data/lib/fol/first_order_logic.rb +0 -260
- data/lib/parser/adsl_ast.rb +0 -779
- data/lib/parser/adsl_parser.racc +0 -151
- data/lib/parser/adsl_parser.tab.rb +0 -976
- data/lib/parser/dsdl_parser.rex.rb +0 -196
- data/lib/parser/dsdl_parser.tab.rb +0 -976
- data/lib/spass/bin.rb +0 -164
- data/lib/spass/spass_ds_extensions.rb +0 -870
- data/lib/spass/spass_translator.rb +0 -388
- data/lib/spass/util.rb +0 -11
- data/lib/util/csv_hash_formatter.rb +0 -47
- data/lib/util/test_helper.rb +0 -33
- 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
|
+
|