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.
- 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
|
+
|