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,206 @@
|
|
|
1
|
+
require 'backports'
|
|
2
|
+
require 'sexp_processor'
|
|
3
|
+
require 'ruby_parser'
|
|
4
|
+
require 'method_source'
|
|
5
|
+
require 'ruby2ruby'
|
|
6
|
+
require 'adsl/extract/sexp_utils'
|
|
7
|
+
require 'adsl/extract/meta'
|
|
8
|
+
|
|
9
|
+
module Kernel
|
|
10
|
+
def ins_call(object, method_name, *args, &block)
|
|
11
|
+
::ADSL::Extract::Instrumenter.get_instance.execute_instrumented object, method_name, *args, &block
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module ADSL
|
|
16
|
+
module Extract
|
|
17
|
+
class Instrumenter
|
|
18
|
+
|
|
19
|
+
attr_reader :stack_depth, :method_locals_stack
|
|
20
|
+
attr_accessor :instrumentation_filters
|
|
21
|
+
|
|
22
|
+
@instance = nil
|
|
23
|
+
@method_locals_stack = []
|
|
24
|
+
|
|
25
|
+
def self.get_instance()
|
|
26
|
+
@instance
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ruby_parser
|
|
30
|
+
RUBY_VERSION >= '2' ? Ruby19Parser.new : RubyParser.for_current_ruby
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.instrumented()
|
|
34
|
+
# a dummy method injected into the AST
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def method_locals
|
|
38
|
+
@method_locals_stack.last
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def previous_locals
|
|
42
|
+
@method_locals_stack[-2]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def root_locals
|
|
46
|
+
@method_locals_stack.first
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def exec_within
|
|
50
|
+
Instrumenter.instance_variable_set(:@instance, self) if @stack_depth == 0
|
|
51
|
+
@stack_depth += 1
|
|
52
|
+
@method_locals_stack << create_locals if respond_to? :create_locals
|
|
53
|
+
|
|
54
|
+
return yield(self)
|
|
55
|
+
ensure
|
|
56
|
+
@stack_depth -= 1
|
|
57
|
+
@method_locals_stack.pop
|
|
58
|
+
Instrumenter.instance_variable_set(:@instance, nil) if @stack_depth == 0
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def mark_sexp_instrumented(sexp)
|
|
62
|
+
raise 'Already instrumented' if sexp_instrumented? sexp
|
|
63
|
+
|
|
64
|
+
first_stmt = sexp[3]
|
|
65
|
+
|
|
66
|
+
if first_stmt[0] != :call or
|
|
67
|
+
first_stmt[1] != Instrumenter.to_sexp or
|
|
68
|
+
first_stmt[2] != :instrumented
|
|
69
|
+
new_stmt = s(:call, Instrumenter.to_sexp, :instrumented)
|
|
70
|
+
sexp.insert 3, new_stmt
|
|
71
|
+
end
|
|
72
|
+
sexp
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def sexp_instrumented?(sexp)
|
|
76
|
+
first_stmt = sexp[3]
|
|
77
|
+
return (first_stmt[0] == :call and
|
|
78
|
+
first_stmt[1] == Instrumenter.to_sexp and
|
|
79
|
+
first_stmt[2] == :instrumented)
|
|
80
|
+
rescue MethodSource::SourceNotFoundError
|
|
81
|
+
return
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def initialize(instrument_domain = Dir.pwd)
|
|
85
|
+
@instrument_domain = instrument_domain
|
|
86
|
+
@replacers = []
|
|
87
|
+
@stack_depth = 0
|
|
88
|
+
@method_locals_stack = []
|
|
89
|
+
|
|
90
|
+
# mark the instrumentation
|
|
91
|
+
replace :defn, :defs do |sexp|
|
|
92
|
+
mark_sexp_instrumented sexp
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# make sure the instrumentation propagates through calls
|
|
96
|
+
replace :call do |sexp|
|
|
97
|
+
# expected format: s(:call, object, method_name, *args)
|
|
98
|
+
# replaced with Extract::Instrumenter.e(instrumenter_id, object, method_name, *args)
|
|
99
|
+
original_object = sexp.sexp_body[0] || s(:self)
|
|
100
|
+
original_method_name = sexp.sexp_body[1]
|
|
101
|
+
original_args = sexp.sexp_body[2..-1]
|
|
102
|
+
|
|
103
|
+
next sexp if [s(:self), nil].include? original_object and Kernel.respond_to? original_method_name
|
|
104
|
+
|
|
105
|
+
s(:call, nil, :ins_call, original_object, s(:lit, original_method_name), *original_args)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def should_instrument?(object, method_name)
|
|
110
|
+
return false if object.is_a?(Fixnum) or object.is_a?(Symbol)
|
|
111
|
+
|
|
112
|
+
method = object.singleton_class.instance_method method_name
|
|
113
|
+
|
|
114
|
+
return false if method.source_location.nil?
|
|
115
|
+
return false if method.owner == Kernel
|
|
116
|
+
return false if @instrument_domain && !(method.source_location.first =~ /^#{@instrument_domain}.*$/)
|
|
117
|
+
|
|
118
|
+
(instrumentation_filters || []).each do |filter|
|
|
119
|
+
return false unless filter.allow_instrumentation? object, method_name
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
source = method.source
|
|
123
|
+
sexp = ruby_parser.process source
|
|
124
|
+
!sexp_instrumented? sexp
|
|
125
|
+
rescue MethodSource::SourceNotFoundError
|
|
126
|
+
# sometimes this happens because the method_source gem bugs out with evals etc
|
|
127
|
+
return false
|
|
128
|
+
rescue NameError => e
|
|
129
|
+
# ghost method with no available source
|
|
130
|
+
return false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def replace(*types, &block)
|
|
134
|
+
options = types.last.is_a?(Hash) ? types.pop : {}
|
|
135
|
+
@replacers << [types, block, options]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def with_replace(*types, replacer)
|
|
139
|
+
replace *types, replacer
|
|
140
|
+
yield
|
|
141
|
+
ensure
|
|
142
|
+
@replacers.pop
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def execute_instrumented(object, method_name, *args, &block)
|
|
146
|
+
self.exec_within do
|
|
147
|
+
instrument object, method_name
|
|
148
|
+
return object.send method_name, *args, &block
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def convert_root_defs_into_defn(sexp)
|
|
153
|
+
sexp.sexp_type == :defs ? s(:defn, *sexp[2..-1]) : sexp
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def instrument_string(source)
|
|
157
|
+
sexp = ruby_parser.process source
|
|
158
|
+
unless sexp.nil?
|
|
159
|
+
instrumented_sexp = instrument_sexp sexp
|
|
160
|
+
new_code = Ruby2Ruby.new.process instrumented_sexp
|
|
161
|
+
else
|
|
162
|
+
source
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def instrument(object, method_name)
|
|
167
|
+
if should_instrument? object, method_name
|
|
168
|
+
begin
|
|
169
|
+
# this is complex because I want to avoid using .method on non-class objects
|
|
170
|
+
# because they might implement method themselves
|
|
171
|
+
method = object.singleton_class.instance_method method_name
|
|
172
|
+
|
|
173
|
+
source = method.source
|
|
174
|
+
|
|
175
|
+
# Ruby 2.0.0 support is in development as of writing this
|
|
176
|
+
sexp = ruby_parser.process source
|
|
177
|
+
|
|
178
|
+
unless sexp.nil?
|
|
179
|
+
sexp = convert_root_defs_into_defn sexp
|
|
180
|
+
|
|
181
|
+
instrumented_sexp = instrument_sexp sexp
|
|
182
|
+
|
|
183
|
+
new_code = Ruby2Ruby.new.process instrumented_sexp
|
|
184
|
+
|
|
185
|
+
object.replace_method method_name, new_code
|
|
186
|
+
|
|
187
|
+
new_code
|
|
188
|
+
else
|
|
189
|
+
source
|
|
190
|
+
end
|
|
191
|
+
rescue MethodSource::SourceNotFoundError
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def instrument_sexp(sexp)
|
|
197
|
+
return nil if sexp.nil?
|
|
198
|
+
@replacers.reverse_each do |types, block, options|
|
|
199
|
+
sexp = sexp.block_replace *types, options, &block
|
|
200
|
+
end
|
|
201
|
+
sexp
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'backports'
|
|
3
|
+
|
|
4
|
+
class Object
|
|
5
|
+
def replace_method(method_name, source = nil, &block)
|
|
6
|
+
raise "Object #{self} of class #{self.class} does not respond to #{method_name}" unless self.respond_to? method_name, true
|
|
7
|
+
|
|
8
|
+
im = self.singleton_class.instance_method(method_name)
|
|
9
|
+
|
|
10
|
+
aliases = []
|
|
11
|
+
self.singleton_class.instance_methods.each do |other_name|
|
|
12
|
+
next if other_name == method_name
|
|
13
|
+
other = self.singleton_class.instance_method other_name
|
|
14
|
+
aliases << [other_name, other] if other == im
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
owner = im.owner
|
|
18
|
+
|
|
19
|
+
unless source.nil?
|
|
20
|
+
owner.class_eval source
|
|
21
|
+
else
|
|
22
|
+
owner.send :define_method, method_name, &block
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
aliases.each do |other_name, other|
|
|
26
|
+
other.owner.class_exec do
|
|
27
|
+
alias_method other_name, method_name
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
require 'adsl/extract/rails/other_meta'
|
|
2
|
+
require 'adsl/parser/ast_nodes'
|
|
3
|
+
|
|
4
|
+
module ADSL
|
|
5
|
+
module Extract
|
|
6
|
+
module Rails
|
|
7
|
+
|
|
8
|
+
class ActionBlockBuilder
|
|
9
|
+
|
|
10
|
+
attr_accessor :root_paths, :stmt_frames, :branch_choices
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@root_paths = []
|
|
14
|
+
@stmt_frames = [[]]
|
|
15
|
+
@branch_choices = []
|
|
16
|
+
@return_values = []
|
|
17
|
+
@has_returned_or_raised = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def push_frame; @stmt_frames << []; end
|
|
21
|
+
def pop_frame; @stmt_frames.pop; end
|
|
22
|
+
|
|
23
|
+
def in_stmt_frame(*args)
|
|
24
|
+
push_frame
|
|
25
|
+
yield *args
|
|
26
|
+
ensure
|
|
27
|
+
return pop_frame
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def included_already?(where, what)
|
|
31
|
+
return where.map{ |e| e.equal? what }.include?(true) ||
|
|
32
|
+
(
|
|
33
|
+
where.last.is_a?(ADSL::Parser::ASTObjsetStmt) &&
|
|
34
|
+
what.is_a?(ADSL::Parser::ASTObjsetStmt) &&
|
|
35
|
+
where.last.objset == what.objset
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def append_stmt(stmt, options = {})
|
|
40
|
+
return stmt if @has_returned_or_raised && !options[:ignore_has_returned]
|
|
41
|
+
return stmt if included_already? @stmt_frames.last, stmt
|
|
42
|
+
@stmt_frames.last << stmt
|
|
43
|
+
stmt
|
|
44
|
+
end
|
|
45
|
+
alias_method :<<, :append_stmt
|
|
46
|
+
|
|
47
|
+
def branch_choice(if_id)
|
|
48
|
+
@branch_choices.each do |iter_if_id, choice|
|
|
49
|
+
return choice if iter_if_id == if_id
|
|
50
|
+
end
|
|
51
|
+
@branch_choices << [if_id, true]
|
|
52
|
+
true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def has_more_executions?
|
|
56
|
+
@branch_choices.each do |if_id, choice|
|
|
57
|
+
return true if choice == true
|
|
58
|
+
end
|
|
59
|
+
false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def increment_branch_choice
|
|
63
|
+
@branch_choices.pop while !@branch_choices.empty? && @branch_choices.last[1] == false
|
|
64
|
+
@branch_choices.last[1] = false unless @branch_choices.empty?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def reset
|
|
68
|
+
unless has_more_executions?
|
|
69
|
+
@root_paths = []
|
|
70
|
+
@branch_choices = []
|
|
71
|
+
@return_values = []
|
|
72
|
+
end
|
|
73
|
+
@has_returned_or_raised = false
|
|
74
|
+
@stmt_frames = [[]]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def explore_all_choices
|
|
78
|
+
while true
|
|
79
|
+
begin
|
|
80
|
+
reset
|
|
81
|
+
increment_branch_choice
|
|
82
|
+
|
|
83
|
+
return_value = yield
|
|
84
|
+
|
|
85
|
+
do_return return_value unless @has_returned_or_raised
|
|
86
|
+
rescue Exception => e
|
|
87
|
+
#puts "Exception: #{e}"
|
|
88
|
+
#puts e.backtrace.first 20
|
|
89
|
+
#do_raise unless @has_returned_or_raised
|
|
90
|
+
ensure
|
|
91
|
+
return common_return_value unless has_more_executions?
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def common_supertype_of_objsets(values)
|
|
97
|
+
return false if values.empty?
|
|
98
|
+
values.each do |value|
|
|
99
|
+
return false if value.is_a?(MetaUnknown) || !value.respond_to?(:adsl_ast)
|
|
100
|
+
end
|
|
101
|
+
adsl_asts = values.reject{ |v| v.nil? }.map(&:adsl_ast)
|
|
102
|
+
adsl_asts = adsl_asts.map{ |v| v.is_a?(ADSL::Parser::ASTObjsetStmt) ? v.objset : v }
|
|
103
|
+
adsl_asts.each do |adsl_ast|
|
|
104
|
+
return false unless adsl_ast.class.is_objset?
|
|
105
|
+
# side effects should trigger only if the selection is chosen;
|
|
106
|
+
# but the translation does not do this
|
|
107
|
+
return false if adsl_ast.objset_has_side_effects?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
common_supertype = nil
|
|
111
|
+
values.each do |value|
|
|
112
|
+
next if value.nil?
|
|
113
|
+
if common_supertype.nil?
|
|
114
|
+
common_supertype = value.class
|
|
115
|
+
elsif value.class <= common_supertype
|
|
116
|
+
# all is fine
|
|
117
|
+
elsif common_supertype <= value.class
|
|
118
|
+
common_supertype = value.class
|
|
119
|
+
else
|
|
120
|
+
return false
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
common_supertype
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def common_supertype_of_objset_arrays(values)
|
|
128
|
+
return false if values.empty?
|
|
129
|
+
|
|
130
|
+
values.each do |value|
|
|
131
|
+
return false unless value.is_a? Array
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
return_value = []
|
|
135
|
+
highest_length = values.map(&:length).max
|
|
136
|
+
highest_length.times do |index|
|
|
137
|
+
ct = compatible_types(values.map{ |v| v[index] })
|
|
138
|
+
return false unless ct
|
|
139
|
+
return_value << ct
|
|
140
|
+
end
|
|
141
|
+
return_value
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def common_return_value
|
|
145
|
+
uniq = @return_values.dup
|
|
146
|
+
# avoid include? because it uses :== and metaobjects override the == operator
|
|
147
|
+
if uniq.map(&:nil?).include? true
|
|
148
|
+
uniq.delete_if{ |e| e.nil? }
|
|
149
|
+
uniq << nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
if uniq.length == 1
|
|
153
|
+
uniq.first
|
|
154
|
+
elsif ct = common_supertype_of_objsets(uniq)
|
|
155
|
+
objsets = uniq.map(&:adsl_ast).map{ |r| r.is_a?(ADSL::Parser::ASTObjsetStmt) ? r.objset : r }
|
|
156
|
+
ct.new(:adsl_ast => ADSL::Parser::ASTOneOfObjset.new(:objsets => objsets))
|
|
157
|
+
elsif ct = common_supertype_of_objset_arrays(uniq)
|
|
158
|
+
highest_length = uniq.map(&:length).max
|
|
159
|
+
combined_objsets = []
|
|
160
|
+
highest_length.times do |index|
|
|
161
|
+
objsets = uniq.map{|u| u[index]}.map(&:adsl_ast).map{ |r| r.is_a?(ADSL::Parser::ASTObjsetStmt) ? r.objset : r }
|
|
162
|
+
combined_objsets << ct[index].new(:objset => ADSL::Parser::ASTOneOfObjset.new(:objsets => objsets))
|
|
163
|
+
end
|
|
164
|
+
combined_objsets
|
|
165
|
+
else
|
|
166
|
+
# append all return values to root paths
|
|
167
|
+
# cause they won't be returned and handled by the caller
|
|
168
|
+
# if an array is returned, assume the 'return 1, 2, 3' syntax
|
|
169
|
+
|
|
170
|
+
@return_values.length.times do |index|
|
|
171
|
+
Array.wrap(@return_values[index]).flatten.each do |ret_value|
|
|
172
|
+
stmt = ADSL::Extract::Rails::ActionInstrumenter.extract_stmt_from_expr ret_value
|
|
173
|
+
@root_paths[index] << stmt unless (
|
|
174
|
+
stmt.nil? ||
|
|
175
|
+
!stmt.class.is_statement? ||
|
|
176
|
+
included_already?(@root_paths[index], stmt)
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
ADSL::Extract::Rails::MetaUnknown.new
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def all_stmts_so_far
|
|
186
|
+
stmts = []
|
|
187
|
+
@stmt_frames.each do |frame|
|
|
188
|
+
stmts += frame
|
|
189
|
+
end
|
|
190
|
+
stmts
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def do_return(return_value = nil)
|
|
194
|
+
unless @has_returned_or_raised
|
|
195
|
+
@root_paths << all_stmts_so_far
|
|
196
|
+
@return_values << return_value
|
|
197
|
+
@has_returned_or_raised = true
|
|
198
|
+
end
|
|
199
|
+
return_value
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def do_raise(*args)
|
|
203
|
+
unless @has_returned_or_raised
|
|
204
|
+
# appending nothing to root paths
|
|
205
|
+
@root_paths << [::ADSL::Parser::ASTDummyStmt.new(:type => :raise)]
|
|
206
|
+
@return_values << nil
|
|
207
|
+
@has_returned_or_raised = true
|
|
208
|
+
end
|
|
209
|
+
return *args
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def adsl_ast
|
|
213
|
+
blocks = @root_paths.map do |root_path|
|
|
214
|
+
::ADSL::Parser::ASTBlock.new :statements => root_path
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
if blocks.empty?
|
|
218
|
+
::ADSL::Parser::ASTBlock.new :statements => []
|
|
219
|
+
elsif blocks.length == 1
|
|
220
|
+
blocks.first
|
|
221
|
+
else
|
|
222
|
+
::ADSL::Parser::ASTBlock.new :statements => [::ADSL::Parser::ASTEither.new(:blocks => blocks)]
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def root_lvl_adsl_ast
|
|
227
|
+
::ADSL::Parser::ASTBlock.new :statements => @stmt_frames.first
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|