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,1410 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'active_support'
|
|
3
|
+
require 'pp'
|
|
4
|
+
require 'set'
|
|
5
|
+
require 'adsl/ds/data_store_spec'
|
|
6
|
+
require 'adsl/util/general'
|
|
7
|
+
|
|
8
|
+
class Array
|
|
9
|
+
def optimize
|
|
10
|
+
map do |e|
|
|
11
|
+
e.respond_to?(:optimize) ? e.optimize : e
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module ADSL
|
|
17
|
+
module Parser
|
|
18
|
+
|
|
19
|
+
class ASTNode
|
|
20
|
+
def self.is_statement?
|
|
21
|
+
@is_statement
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.is_objset?
|
|
25
|
+
@is_objset
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.is_formula?
|
|
29
|
+
@is_formula
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def objset_has_side_effects?
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.node_type(*fields)
|
|
37
|
+
options = {}
|
|
38
|
+
if fields.last.is_a? Hash
|
|
39
|
+
options.merge! fields.pop
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if options.include?(:type) and !options.include?(:types)
|
|
43
|
+
options[:types] = [options[:type]]
|
|
44
|
+
end
|
|
45
|
+
if options.include?(:types)
|
|
46
|
+
@is_statement = options[:types].include? :statement
|
|
47
|
+
@is_objset = options[:types].include? :objset
|
|
48
|
+
@is_formula = options[:types].include? :formula
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
container_for *fields
|
|
52
|
+
container_for :lineno
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def adsl_ast
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def optimize
|
|
60
|
+
copy = self.dup
|
|
61
|
+
children = self.class.container_for_fields.map{ |field_name| [field_name, copy.send(field_name)] }
|
|
62
|
+
until children.empty?
|
|
63
|
+
child_name, child = children.pop
|
|
64
|
+
new_value = child.respond_to?(:optimize) ? child.optimize : child.dup
|
|
65
|
+
copy.send "#{child_name}=", new_value unless new_value.equal? child
|
|
66
|
+
end
|
|
67
|
+
copy
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def dup
|
|
71
|
+
new_values = {}
|
|
72
|
+
self.class.container_for_fields.each do |field_name|
|
|
73
|
+
value = send field_name
|
|
74
|
+
new_values[field_name] = if value.is_a?(Symbol) || value.nil?
|
|
75
|
+
value
|
|
76
|
+
else
|
|
77
|
+
value.dup
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
self.class.new new_values
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def block_replace(&block)
|
|
84
|
+
children = self.class.container_for_fields.map{ |field_name| [field_name, send(field_name)] }
|
|
85
|
+
children.each do |name, value|
|
|
86
|
+
new_value = if value.is_a? Array
|
|
87
|
+
value.map do |e|
|
|
88
|
+
new_e = e.block_replace(&block)
|
|
89
|
+
new_e.nil? ? e.dup : new_e
|
|
90
|
+
end
|
|
91
|
+
elsif value.is_a? ASTNode
|
|
92
|
+
new_value = value.block_replace(&block)
|
|
93
|
+
new_value.nil? ? value.dup : new_value
|
|
94
|
+
elsif value.is_a?(Symbol) || value.nil?
|
|
95
|
+
value
|
|
96
|
+
else
|
|
97
|
+
value.dup
|
|
98
|
+
end
|
|
99
|
+
send("#{name}=", new_value) if new_value != value
|
|
100
|
+
end
|
|
101
|
+
new_value = block[self]
|
|
102
|
+
new_value.nil? ? self.dup : new_value
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def ==(other)
|
|
106
|
+
return false unless self.class == other.class
|
|
107
|
+
self.class.container_for_fields.each do |field_name|
|
|
108
|
+
child1 = self.send field_name
|
|
109
|
+
child2 = other.send field_name
|
|
110
|
+
return false unless child1 == child2
|
|
111
|
+
end
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
alias_method :eql?, :==
|
|
115
|
+
|
|
116
|
+
def hash
|
|
117
|
+
[self.class, *self.class.container_for_fields.map{ |field_name| send field_name }].hash
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def preorder_traverse(&block)
|
|
121
|
+
self.class.container_for_fields.each do |field_name|
|
|
122
|
+
field = send field_name
|
|
123
|
+
if field.is_a? Array
|
|
124
|
+
field.flatten.each do |subfield|
|
|
125
|
+
subfield.preorder_traverse &block if subfield.respond_to? :preorder_traverse
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
field.preorder_traverse &block if field.respond_to? :preorder_traverse
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
block[self]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# used for statistics
|
|
135
|
+
def adsl_ast_size
|
|
136
|
+
sum = 1
|
|
137
|
+
self.class.container_for_fields.each do |field_name|
|
|
138
|
+
field = send field_name
|
|
139
|
+
if field.is_a? Array
|
|
140
|
+
field.flatten.each do |subfield|
|
|
141
|
+
sum += subfield.adsl_ast_size if subfield.respond_to? :adsl_ast_size
|
|
142
|
+
end
|
|
143
|
+
else
|
|
144
|
+
sum += field.adsl_ast_size if field.respond_to? :adsl_ast_size
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
sum
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class ADSLError < StandardError; end
|
|
152
|
+
|
|
153
|
+
class ASTDummyObjset < ASTNode
|
|
154
|
+
node_type :type, :type => :objset
|
|
155
|
+
|
|
156
|
+
def typecheck_and_resolve(context)
|
|
157
|
+
self
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def to_adsl
|
|
161
|
+
"DummyObjset(#{ @type })"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class ASTDummyStmt < ASTNode
|
|
166
|
+
node_type :type, :type => :statement
|
|
167
|
+
|
|
168
|
+
def typecheck_and_resolve(context)
|
|
169
|
+
self
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def to_adsl
|
|
173
|
+
"DummyStmt(#{ @type })\n"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
class ASTSpec < ASTNode
|
|
178
|
+
node_type :classes, :actions, :invariants
|
|
179
|
+
|
|
180
|
+
def typecheck_and_resolve
|
|
181
|
+
context = ASTTypecheckResolveContext.new
|
|
182
|
+
|
|
183
|
+
# make sure class names are unique
|
|
184
|
+
@classes.each do |class_node|
|
|
185
|
+
if context.classes.include? class_node.name.text
|
|
186
|
+
raise ADSLError, "Duplicate class name '#{class_node.name.text}' on line #{class_node.name.lineno} (first definition on line #{context.classes[class_node.name.text][0].name.lineno}"
|
|
187
|
+
end
|
|
188
|
+
klass = ADSL::DS::DSClass.new :name => class_node.name.text
|
|
189
|
+
context.classes[klass.name] = [class_node, klass]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# make sure the parent classes are declared properly and that the inheritance graph is non-cyclic
|
|
193
|
+
parents = Hash.new{}
|
|
194
|
+
context.classes.values.select{ |v| v[0].parent_name }.each do |class_node, klass|
|
|
195
|
+
parent_node, parent = context.classes[class_node.parent_name.text]
|
|
196
|
+
raise ADSLError, "Unknown parent class name #{class_node.parent_name.text} for class #{class_node.name.text} on line #{class_node.parent_name}" if parent.nil?
|
|
197
|
+
klass.parent = parent
|
|
198
|
+
|
|
199
|
+
parents[klass] = parent
|
|
200
|
+
parent_chain = [klass]
|
|
201
|
+
while parent != nil do
|
|
202
|
+
if parent_chain.include? parent
|
|
203
|
+
cyclic_chain = parent_chain.slice(parent_chain.index(parent), parent_chain.length) + [parent]
|
|
204
|
+
raise ADSLError, "Cyclic inheritance detected: #{cyclic_chain.map{ |c| c.name }.join ' -> '}"
|
|
205
|
+
end
|
|
206
|
+
parent_chain << parent
|
|
207
|
+
parent = parents[parent]
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# make sure relations are valid and refer to existing classes
|
|
212
|
+
context.classes.values.each do |class_node, klass|
|
|
213
|
+
class_node.relations.each do |rel_node|
|
|
214
|
+
iter = klass
|
|
215
|
+
while iter != nil
|
|
216
|
+
if context.relations[iter.name].include? rel_node.name.text
|
|
217
|
+
raise ADSLError, "Duplicate relation name '#{class_node.name.text}' under class '#{klass.name}' on line #{rel_node.lineno} (first definition on line #{context.relations[iter.name][rel_node.name.text][0].lineno}"
|
|
218
|
+
end
|
|
219
|
+
iter = iter.parent
|
|
220
|
+
end
|
|
221
|
+
rel = ADSL::DS::DSRelation.new :name => rel_node.name.text, :from_class => klass
|
|
222
|
+
context.relations[klass.name][rel.name] = [rel_node, rel]
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# now that classes and rels are initialized, check them
|
|
227
|
+
@classes.each do |class_node|
|
|
228
|
+
class_node.typecheck_and_resolve context
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
@actions.each do |action_node|
|
|
232
|
+
action_node.typecheck_and_resolve context
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# make sure invariants have unique names; add names to unnamed invariants
|
|
236
|
+
names = Set.new
|
|
237
|
+
@invariants.each do |invariant_node|
|
|
238
|
+
invariant = invariant_node.typecheck_and_resolve context
|
|
239
|
+
if invariant.name && names.include?(invariant.name)
|
|
240
|
+
raise ADSLError, "Duplicate invariant name #{invariant.name} on line #{invariant_node.lineno}"
|
|
241
|
+
end
|
|
242
|
+
name = invariant.name || "unnamed_line_#{invariant_node.lineno}"
|
|
243
|
+
while names.include? name
|
|
244
|
+
name = name.increment_suffix
|
|
245
|
+
end
|
|
246
|
+
invariant.name = name
|
|
247
|
+
context.invariants << invariant
|
|
248
|
+
names << name
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
@invariants.each do |invariant_node|
|
|
252
|
+
invariant = invariant_node.typecheck_and_resolve context
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
ADSL::DS::DSSpec.new(
|
|
256
|
+
:classes => context.classes.map{ |a, b| b[1] },
|
|
257
|
+
:actions => context.actions.map{ |a, b| b[1] },
|
|
258
|
+
:invariants => context.invariants.dup
|
|
259
|
+
)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def to_adsl
|
|
263
|
+
"#{ @classes.map(&:to_adsl).join }\n#{ @actions.map(&:to_adsl).join }\n#{ @invariants.map(&:to_adsl).join }"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def adsl_ast_size(options = {})
|
|
267
|
+
sum = 1
|
|
268
|
+
@classes.each do |c|
|
|
269
|
+
sum += c.adsl_ast_size
|
|
270
|
+
end
|
|
271
|
+
actions = options[:action_name].nil? ? @actions : @actions.select{ |a| a.name.text == options[:action_name] }
|
|
272
|
+
actions.each do |a|
|
|
273
|
+
sum += options[:pre_optimize] ? a.pre_optimize_adsl_ast_size : a.adsl_ast_size
|
|
274
|
+
end
|
|
275
|
+
invs = options[:invariant_name].nil? ? @invariants : @invariants.select{ |a| a.name.text == options[:invariant_name] }
|
|
276
|
+
invs.each do |i|
|
|
277
|
+
sum += i.adsl_ast_size
|
|
278
|
+
end
|
|
279
|
+
sum
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
class ASTClass < ASTNode
|
|
284
|
+
node_type :name, :parent_name, :relations
|
|
285
|
+
|
|
286
|
+
def typecheck_and_resolve(context)
|
|
287
|
+
klass = context.classes[@name.text][1]
|
|
288
|
+
@relations.each do |rel_node|
|
|
289
|
+
rel = context.relations[@name.text][rel_node.name.text][1]
|
|
290
|
+
klass.relations << rel
|
|
291
|
+
|
|
292
|
+
if rel_node.cardinality[0] > rel_node.cardinality[1]
|
|
293
|
+
raise ADSLError, "Invalid cardinality of relation #{klass.name}.#{rel_node.name.text} on line #{rel_node.cardinality[2]}: minimum cardinality #{rel_node.cardinality[0]} must not be greater than the maximum cardinality #{rel_node.cardinality[1]}"
|
|
294
|
+
end
|
|
295
|
+
if rel_node.cardinality[1] == 0
|
|
296
|
+
raise ADSLError, "Invalid cardinality of relation #{klass.name}.#{rel_node.name.text} on line #{rel_node.cardinality[2]}: maximum cardinality #{rel_node.cardinality[1]} must be positive"
|
|
297
|
+
end
|
|
298
|
+
unless context.classes.include? rel_node.to_class_name.text
|
|
299
|
+
raise ADSLError, "Unknown class name #{rel_node.to_class_name.text} in relation #{klass.name}.#{rel_node.name.text} on line #{rel_node.to_class_name.lineno}"
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
rel.to_class = context.classes[rel_node.to_class_name.text][1]
|
|
303
|
+
rel.cardinality = rel_node.cardinality
|
|
304
|
+
if rel_node.inverse_of_name
|
|
305
|
+
target_class = context.classes[rel.to_class.name][1]
|
|
306
|
+
inverse_of_node, inverse_of = context.relations[target_class.name][rel_node.inverse_of_name.text]
|
|
307
|
+
|
|
308
|
+
while inverse_of_node.nil?
|
|
309
|
+
inverse_of_node, inverse_of = context.relations[target_class.name][rel_node.inverse_of_name.text]
|
|
310
|
+
target_class = target_class.parent
|
|
311
|
+
raise ADSLError, "Unknown relation to which #{rel.from_class.name}.#{rel.name} relation is inverse to: #{rel.to_class.name}.#{rel_node.inverse_of_name.text} on line #{rel_node.inverse_of_name.lineno}" if target_class.nil?
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
if inverse_of_node.inverse_of_name
|
|
315
|
+
raise ADSLError, "Relation #{rel.from_class.name}.#{rel.name} cannot be inverse to an inverse relation #{rel.to_class.name}.#{rel_node.inverse_of_name.text} on line #{rel_node.inverse_of_name.lineno}"
|
|
316
|
+
end
|
|
317
|
+
rel.inverse_of = inverse_of
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def to_adsl
|
|
323
|
+
par_name = @parent_name.nil? ? "" : "extends #{@parent_name.text} "
|
|
324
|
+
"class #{ @name.text } #{ par_name }{\n#{ @relations.map(&:to_adsl).adsl_indent }}\n"
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
class ASTRelation < ASTNode
|
|
329
|
+
node_type :cardinality, :to_class_name, :name, :inverse_of_name
|
|
330
|
+
|
|
331
|
+
def to_adsl
|
|
332
|
+
card_str = cardinality[1] == Float::INFINITY ? "#{cardinality[0]}+" : "#{cardinality[0]}..#{cardinality[1]}"
|
|
333
|
+
inv_str = inverse_of_name.nil? ? "" : " inverseof #{inverse_of_name.text}"
|
|
334
|
+
"#{ card_str } #{ @to_class_name.text } #{ @name.text }#{ inv_str }\n"
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
class ASTIdent < ASTNode
|
|
339
|
+
node_type :text
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
class ASTTypecheckResolveContext
|
|
343
|
+
attr_accessor :classes, :relations, :actions, :invariants, :var_stack, :pre_stmts
|
|
344
|
+
|
|
345
|
+
class ASTStackFrame < ActiveSupport::OrderedHash
|
|
346
|
+
attr_accessor :var_write_listeners
|
|
347
|
+
attr_accessor :var_read_listeners
|
|
348
|
+
|
|
349
|
+
def initialize
|
|
350
|
+
super
|
|
351
|
+
@var_write_listeners = []
|
|
352
|
+
@var_read_listeners = []
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def on_var_read(&block)
|
|
356
|
+
listeners = @var_read_listeners
|
|
357
|
+
listeners.push block
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def on_var_write(&block)
|
|
361
|
+
listeners = @var_write_listeners
|
|
362
|
+
listeners.push block
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def fire_write_event(name)
|
|
366
|
+
listeners = @var_write_listeners
|
|
367
|
+
listeners.each do |listener|
|
|
368
|
+
listener.call name
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def fire_read_event(name)
|
|
373
|
+
listeners = @var_read_listeners
|
|
374
|
+
listeners.each do |listener|
|
|
375
|
+
listener.call name
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def dup
|
|
380
|
+
other = ASTStackFrame.new
|
|
381
|
+
self.each do |key, val|
|
|
382
|
+
other[key] = val.dup
|
|
383
|
+
end
|
|
384
|
+
other.var_write_listeners = @var_write_listeners.dup
|
|
385
|
+
other.var_read_listeners = @var_read_listeners.dup
|
|
386
|
+
other
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def clone
|
|
390
|
+
dup
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def initialize
|
|
395
|
+
# name => [astnode, dsobj]
|
|
396
|
+
@classes = ActiveSupport::OrderedHash.new
|
|
397
|
+
|
|
398
|
+
# classname => name => [astnode, dsobj]
|
|
399
|
+
@relations = ActiveSupport::OrderedHash.new{ |hash, key| hash[key] = ActiveSupport::OrderedHash.new }
|
|
400
|
+
|
|
401
|
+
# stack of name => [astnode, dsobj]
|
|
402
|
+
@actions = ActiveSupport::OrderedHash.new
|
|
403
|
+
|
|
404
|
+
@invariants = []
|
|
405
|
+
@var_stack = []
|
|
406
|
+
@pre_stmts = []
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def initialize_copy(source)
|
|
410
|
+
super
|
|
411
|
+
source.classes.each do |name, value|
|
|
412
|
+
@classes[name] = value.dup
|
|
413
|
+
end
|
|
414
|
+
source.relations.each do |class_name, class_entry|
|
|
415
|
+
entries = @relations[class_name]
|
|
416
|
+
class_entry.each do |name, value|
|
|
417
|
+
entries[name] = value.dup
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
@actions = source.actions.dup
|
|
421
|
+
@invariants = source.invariants.dup
|
|
422
|
+
@var_stack = source.var_stack.map{ |frame| frame.dup }
|
|
423
|
+
@pre_stmts = source.pre_stmts.map{ |stmt| stmt.dup }
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def on_var_write(&block)
|
|
427
|
+
@var_stack.last.on_var_write(&block)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def on_var_read(&block)
|
|
431
|
+
@var_stack.last.on_var_read(&block)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def in_stack_frame
|
|
435
|
+
push_frame
|
|
436
|
+
yield
|
|
437
|
+
ensure
|
|
438
|
+
pop_frame
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def push_frame
|
|
442
|
+
@var_stack.push ASTStackFrame.new
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def pop_frame
|
|
446
|
+
@var_stack.pop
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def define_var(var, node)
|
|
450
|
+
raise ADSLError, "Defining variables on a stack with no stack frames" if @var_stack.empty?
|
|
451
|
+
prev_var_node, prev_var = lookup_var var.name
|
|
452
|
+
raise ADSLError, "Duplicate identifier '#{var.name}' on line #{node.lineno}; previous definition on line #{prev_var_node.lineno}" unless prev_var.nil?
|
|
453
|
+
@var_stack.last[var.name] = [node, var]
|
|
454
|
+
@var_stack.last.fire_write_event var.name
|
|
455
|
+
return var
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def redefine_var(var, node)
|
|
459
|
+
@var_stack.length.times do |frame_index|
|
|
460
|
+
frame = @var_stack[frame_index]
|
|
461
|
+
next unless frame.include? var.name
|
|
462
|
+
old_var = frame[var.name][1]
|
|
463
|
+
|
|
464
|
+
if old_var.type.nil?
|
|
465
|
+
# nothing?
|
|
466
|
+
elsif var.type.nil?
|
|
467
|
+
var.type = old_var.type
|
|
468
|
+
elsif var.type != old_var.type
|
|
469
|
+
raise ADSLError, "Unmatched type '#{var.type.name}' for variable '#{var.name}' on line #{node.lineno}"
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
frame[var.name][1] = var
|
|
473
|
+
|
|
474
|
+
@var_stack[frame_index..-1].reverse.each do |subframe|
|
|
475
|
+
subframe.fire_write_event var.name
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
return var
|
|
479
|
+
end
|
|
480
|
+
return define_var var, node
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def lookup_var(name, fire_read_event=true)
|
|
484
|
+
@var_stack.length.times do |index|
|
|
485
|
+
frame = @var_stack[index]
|
|
486
|
+
next if frame[name].nil?
|
|
487
|
+
var = frame[name]
|
|
488
|
+
|
|
489
|
+
if fire_read_event
|
|
490
|
+
@var_stack[index..-1].reverse.each do |subframe|
|
|
491
|
+
subframe.fire_read_event name
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# handle events here, none defined atm
|
|
496
|
+
return var
|
|
497
|
+
end
|
|
498
|
+
nil
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def find_relation(from_type, rel_name, lineno, to_type=nil)
|
|
502
|
+
iter = from_type
|
|
503
|
+
relation_node, relation = @relations[iter.name][rel_name]
|
|
504
|
+
while relation.nil?
|
|
505
|
+
iter = iter.parent
|
|
506
|
+
raise ADSLError, "Unknown relation #{from_type.name}.#{rel_name} on line #{lineno}" if iter.nil?
|
|
507
|
+
relation_node, relation = @relations[iter.name][rel_name]
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
unless to_type.nil?
|
|
511
|
+
raise ADSLError, "Mismatched right-hand-side type for relation #{from_type.name}.#{rel_name} on line #{lineno}. Expected #{relation.to_class.name} but was #{to_type.name}" unless relation.to_class.superclass_of? to_type
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
relation
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def self.context_vars_that_differ(*contexts)
|
|
518
|
+
vars_per_context = []
|
|
519
|
+
contexts.each do |context|
|
|
520
|
+
vars_per_context << context.var_stack.inject(ActiveSupport::OrderedHash.new) { |so_far, frame| so_far.merge! frame }
|
|
521
|
+
end
|
|
522
|
+
all_vars = vars_per_context.map{ |c| c.keys }.flatten.uniq
|
|
523
|
+
packed = ActiveSupport::OrderedHash.new
|
|
524
|
+
all_vars.each do |v|
|
|
525
|
+
packed[v] = vars_per_context.map{ |vpc| vpc[v][1] }
|
|
526
|
+
end
|
|
527
|
+
packed.delete_if { |v, vars| vars.uniq.length == 1 }
|
|
528
|
+
packed
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
class ASTAction < ASTNode
|
|
533
|
+
node_type :name, :arg_cardinalities, :arg_names, :arg_types, :block
|
|
534
|
+
|
|
535
|
+
def typecheck_and_resolve(context)
|
|
536
|
+
old_action_node, old_action = context.actions[@name.text]
|
|
537
|
+
raise ADSLError, "Duplicate action name #{@name.text} on line #{@name.lineno}; first definition on line #{old_action_node.name.lineno}" unless old_action.nil?
|
|
538
|
+
arguments = []
|
|
539
|
+
cardinalities = []
|
|
540
|
+
block = nil
|
|
541
|
+
context.in_stack_frame do
|
|
542
|
+
@arg_names.length.times do |i|
|
|
543
|
+
cardinality = @arg_cardinalities[i]
|
|
544
|
+
if cardinality[0] > cardinality[1]
|
|
545
|
+
raise ADSLError, "Invalid cardinality of argument #{@arg_names[i].text} of action #{@name.text} on line #{cardinality[2]}: minimum cardinality #{cardinality[0]} must not be greater than the maximum cardinality #{cardinality[1]}"
|
|
546
|
+
end
|
|
547
|
+
if cardinality[1] == 0
|
|
548
|
+
raise ADSLError, "Invalid cardinality of relation #{@arg_names[i].text} of action #{@name.text} on line #{cardinality[2]}: maximum cardinality #{cardinality[1]} must be positive"
|
|
549
|
+
end
|
|
550
|
+
cardinality = cardinality.first 2
|
|
551
|
+
|
|
552
|
+
klass_node, klass = context.classes[@arg_types[i].text]
|
|
553
|
+
raise ADSLError, "Unknown class #{@arg_types[i].text} on line #{@arg_types[i].lineno}" if klass.nil?
|
|
554
|
+
var = ADSL::DS::DSVariable.new :name => @arg_names[i].text, :type => klass
|
|
555
|
+
context.define_var var, @arg_types[i]
|
|
556
|
+
arguments << var
|
|
557
|
+
cardinalities << cardinality
|
|
558
|
+
end
|
|
559
|
+
block = @block.typecheck_and_resolve context, false
|
|
560
|
+
end
|
|
561
|
+
action = ADSL::DS::DSAction.new :name => @name.text, :args => arguments, :cardinalities => cardinalities, :block => block
|
|
562
|
+
context.actions[action.name] = [self, action]
|
|
563
|
+
return action
|
|
564
|
+
rescue Exception => e
|
|
565
|
+
#pp @block
|
|
566
|
+
new_ex = e.exception("#{e.message} in action #{@name.text}")
|
|
567
|
+
new_ex.set_backtrace e.backtrace
|
|
568
|
+
raise new_ex
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
def optimize
|
|
572
|
+
copy = dup
|
|
573
|
+
copy.instance_variable_set :@pre_optimize_adsl_ast_size, copy.adsl_ast_size
|
|
574
|
+
|
|
575
|
+
copy.block = until_no_change(copy.block) do |block|
|
|
576
|
+
block = block.optimize(true)
|
|
577
|
+
|
|
578
|
+
variables_read = []
|
|
579
|
+
block.preorder_traverse do |node|
|
|
580
|
+
next unless node.is_a? ASTVariable
|
|
581
|
+
variables_read << node.var_name.text
|
|
582
|
+
end
|
|
583
|
+
block.block_replace do |node|
|
|
584
|
+
next unless node.is_a? ASTAssignment
|
|
585
|
+
next if node.var_name.nil? || variables_read.include?(node.var_name.text)
|
|
586
|
+
ASTObjsetStmt.new :objset => node.objset
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
next block if block.statements.length != 1
|
|
590
|
+
|
|
591
|
+
if block.statements.first.is_a? ASTEither
|
|
592
|
+
either = block.statements.first
|
|
593
|
+
either = ASTEither.new(:blocks => either.blocks.reject{ |subblock| subblock.statements.empty? })
|
|
594
|
+
if either.blocks.length == 0
|
|
595
|
+
ASTBlock.new(:statements => [])
|
|
596
|
+
elsif either.blocks.length == 1
|
|
597
|
+
either.blocks.first
|
|
598
|
+
else
|
|
599
|
+
ASTBlock.new(:statements => [either])
|
|
600
|
+
end
|
|
601
|
+
else
|
|
602
|
+
block
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
copy
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def pre_optimize_adsl_ast_size
|
|
610
|
+
@pre_optimize_adsl_ast_size || adsl_ast_size
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def prepend_global_variables_by_signatures(*regexes)
|
|
614
|
+
variable_names = []
|
|
615
|
+
preorder_traverse do |node|
|
|
616
|
+
next unless node.is_a? ASTVariable
|
|
617
|
+
name = node.var_name.text
|
|
618
|
+
variable_names << name if regexes.map{ |r| r =~ name ? true : false }.include? true
|
|
619
|
+
end
|
|
620
|
+
variable_names.each do |name|
|
|
621
|
+
@block.statements.unshift ASTAssignment.new(
|
|
622
|
+
:var_name => ASTIdent.new(:text => name),
|
|
623
|
+
:objset => ASTEmptyObjset.new
|
|
624
|
+
)
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def to_adsl
|
|
629
|
+
args = []
|
|
630
|
+
@arg_cardinalities.length.times do |index|
|
|
631
|
+
card = @arg_cardinalities[index]
|
|
632
|
+
type = @arg_types[index].text
|
|
633
|
+
name = @arg_names[index].text
|
|
634
|
+
|
|
635
|
+
card_str = card[1] == Float::INFINITY ? "#{card[0]}+" : "#{card[0]}..#{card[1]}"
|
|
636
|
+
args << "#{card_str} #{type} #{name}"
|
|
637
|
+
end
|
|
638
|
+
"action #{@name.text}(#{ args.join ', ' }) {\n#{ @block.to_adsl.adsl_indent }}\n"
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
class ASTBlock < ASTNode
|
|
643
|
+
node_type :statements, :type => :statement
|
|
644
|
+
|
|
645
|
+
def typecheck_and_resolve(context, open_subcontext=true)
|
|
646
|
+
context.push_frame if open_subcontext
|
|
647
|
+
stmts = []
|
|
648
|
+
@statements.each do |node|
|
|
649
|
+
main_stmt = node.typecheck_and_resolve context
|
|
650
|
+
stmts += context.pre_stmts
|
|
651
|
+
stmts << main_stmt unless main_stmt.nil?
|
|
652
|
+
context.pre_stmts = []
|
|
653
|
+
end
|
|
654
|
+
return ADSL::DS::DSBlock.new :statements => stmts.flatten
|
|
655
|
+
ensure
|
|
656
|
+
context.pop_frame if open_subcontext
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
def optimize(last_stmt = false)
|
|
660
|
+
until_no_change super() do |block|
|
|
661
|
+
next block if block.statements.empty?
|
|
662
|
+
|
|
663
|
+
statements = block.statements.map(&:optimize).map{ |stmt|
|
|
664
|
+
stmt.is_a?(ASTBlock) ? stmt.statements : [stmt]
|
|
665
|
+
}.flatten(1).reject{ |stmt|
|
|
666
|
+
stmt.is_a?(ASTDummyStmt)
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if last_stmt
|
|
670
|
+
if statements.last.is_a?(ASTAssignment)
|
|
671
|
+
if statements.last.objset.objset_has_side_effects?
|
|
672
|
+
statements[-1] = ASTObjsetStmt.new(:objset => statements.last.objset)
|
|
673
|
+
else
|
|
674
|
+
statements.pop
|
|
675
|
+
end
|
|
676
|
+
elsif statements.last.is_a?(ASTEither)
|
|
677
|
+
statements.last.blocks.map!{ |b| b.optimize true }
|
|
678
|
+
statements[-1] = statements.last.optimize
|
|
679
|
+
elsif statements.last.is_a?(ASTBlock)
|
|
680
|
+
last = statements.pop.optimize true
|
|
681
|
+
statements += last.statements
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
ASTBlock.new(:statements => statements)
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def to_adsl
|
|
690
|
+
@statements.map(&:to_adsl).join
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
class ASTAssignment < ASTNode
|
|
695
|
+
node_type :var_name, :objset, :type => :statement
|
|
696
|
+
|
|
697
|
+
def typecheck_and_resolve(context)
|
|
698
|
+
objset = @objset.typecheck_and_resolve context
|
|
699
|
+
@var = ADSL::DS::DSVariable.new :name => @var_name.text, :type => objset.type
|
|
700
|
+
context.redefine_var @var, @var_name
|
|
701
|
+
return ADSL::DS::DSAssignment.new :var => @var, :objset => objset
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def to_adsl
|
|
705
|
+
"#{ @var_name.text } = #{ @objset.to_adsl }\n"
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
class ASTDeclareVar < ASTNode
|
|
710
|
+
node_type :var_name, :type => :statement
|
|
711
|
+
|
|
712
|
+
def typecheck_and_resolve(context)
|
|
713
|
+
var = context.lookup_var @var_name.text, false
|
|
714
|
+
if var.nil?
|
|
715
|
+
return ASTAssignment.new(:var_name => @var_name.dup, :objset => ASTEmptyObjset.new).typecheck_and_resolve(context)
|
|
716
|
+
else
|
|
717
|
+
[]
|
|
718
|
+
end
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
def to_adsl
|
|
722
|
+
"declare #{ @var_name.text }\n"
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
class ASTObjsetStmt < ASTNode
|
|
727
|
+
node_type :objset, :type => :statement
|
|
728
|
+
|
|
729
|
+
def typecheck_and_resolve(context)
|
|
730
|
+
@objset.typecheck_and_resolve(context)
|
|
731
|
+
return nil
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def optimize(last_stmt = false)
|
|
735
|
+
@objset.objset_has_side_effects? ? self : ASTDummyStmt.new
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
def to_adsl
|
|
739
|
+
"#{ @objset.to_adsl }\n"
|
|
740
|
+
end
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
class ASTCreateObjset < ASTNode
|
|
744
|
+
node_type :class_name, :type => :objset
|
|
745
|
+
|
|
746
|
+
def objset_has_side_effects?; true; end
|
|
747
|
+
|
|
748
|
+
def typecheck_and_resolve(context)
|
|
749
|
+
klass_node, klass = context.classes[@class_name.text]
|
|
750
|
+
raise ADSLError, "Undefined class #{@class_name.text} referred to at line #{@class_name.lineno}" if klass.nil?
|
|
751
|
+
if @create_obj.nil?
|
|
752
|
+
@create_obj = ADSL::DS::DSCreateObj.new :klass => klass
|
|
753
|
+
context.pre_stmts << @create_obj
|
|
754
|
+
end
|
|
755
|
+
ADSL::DS::DSCreateObjset.new :createobj => @create_obj
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
def to_adsl
|
|
759
|
+
"create(#{ @class_name.text })"
|
|
760
|
+
end
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
class ASTForEach < ASTNode
|
|
764
|
+
node_type :var_name, :objset, :block, :type => :statement
|
|
765
|
+
|
|
766
|
+
def typecheck_and_resolve(context)
|
|
767
|
+
before_context = context.dup
|
|
768
|
+
objset = @objset.typecheck_and_resolve context
|
|
769
|
+
|
|
770
|
+
temp_iterator_objset = ASTDummyObjset.new :type => objset.type
|
|
771
|
+
assignment = ASTAssignment.new :lineno => @lineno, :var_name => @var_name, :objset => temp_iterator_objset
|
|
772
|
+
@block.statements = [assignment, @block.statements].flatten
|
|
773
|
+
|
|
774
|
+
vars_written_to = Set[]
|
|
775
|
+
vars_read = Set[]
|
|
776
|
+
vars_read_before_being_written_to = Set[]
|
|
777
|
+
context.on_var_write do |name|
|
|
778
|
+
vars_written_to << name
|
|
779
|
+
end
|
|
780
|
+
context.on_var_read do |name|
|
|
781
|
+
var_node, var = context.lookup_var name, false
|
|
782
|
+
vars_read_before_being_written_to << name unless
|
|
783
|
+
vars_written_to.include?(name) or vars_read_before_being_written_to.include? name
|
|
784
|
+
vars_read << name unless vars_read.include? name
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
context.push_frame
|
|
788
|
+
block = @block.typecheck_and_resolve context
|
|
789
|
+
context.pop_frame
|
|
790
|
+
|
|
791
|
+
vars_read_before_being_written_to.each do |var_name|
|
|
792
|
+
vars_read_before_being_written_to.delete var_name unless vars_written_to.include? var_name
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
flat = true
|
|
796
|
+
# flat = false unless vars_read_before_being_written_to.empty?
|
|
797
|
+
|
|
798
|
+
if flat
|
|
799
|
+
for_each = ADSL::DS::DSFlatForEach.new :objset => objset, :block => block
|
|
800
|
+
else
|
|
801
|
+
for_each = ADSL::DS::DSForEach.new :objset => objset, :block => block
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
vars_read_before_being_written_to.each do |var_name|
|
|
805
|
+
before_var_node, before_var = before_context.lookup_var var_name, false
|
|
806
|
+
inside_var_node, inside_var = context.lookup_var var_name, false
|
|
807
|
+
lambda_objset = ADSL::DS::DSForEachPreLambdaObjset.new :for_each => for_each, :before_var => before_var, :inside_var => inside_var
|
|
808
|
+
var = ADSL::DS::DSVariable.new :name => var_name, :type => before_var.type
|
|
809
|
+
assignment = ADSL::DS::DSAssignment.new :var => var, :objset => lambda_objset
|
|
810
|
+
block.replace before_var, var
|
|
811
|
+
block.statements.unshift assignment
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
iterator_objset = ADSL::DS::DSForEachIteratorObjset.new :for_each => for_each
|
|
815
|
+
block.replace temp_iterator_objset, iterator_objset
|
|
816
|
+
return for_each
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
def list_creations
|
|
820
|
+
@block.list_creations
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
def optimize
|
|
824
|
+
optimized = super
|
|
825
|
+
if optimized.block.statements.empty?
|
|
826
|
+
return ASTObjsetStmt.new(:objset => optimized.objset).optimize
|
|
827
|
+
end
|
|
828
|
+
optimized
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
def to_adsl
|
|
832
|
+
"foreach #{ @var_name.text } : #{ @objset.to_adsl } {\n#{ @block.to_adsl.adsl_indent }}\n"
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
class ASTEither < ASTNode
|
|
837
|
+
node_type :blocks, :type => :statement
|
|
838
|
+
|
|
839
|
+
def typecheck_and_resolve(context)
|
|
840
|
+
context.push_frame
|
|
841
|
+
|
|
842
|
+
contexts = [context]
|
|
843
|
+
(@blocks.length-1).times do
|
|
844
|
+
contexts << context.dup
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
blocks = []
|
|
848
|
+
@blocks.length.times do |i|
|
|
849
|
+
blocks << @blocks[i].typecheck_and_resolve(contexts[i])
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
contexts.each do |c|
|
|
853
|
+
c.pop_frame
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
either = ADSL::DS::DSEither.new :blocks => blocks
|
|
857
|
+
|
|
858
|
+
lambdas = []
|
|
859
|
+
|
|
860
|
+
ASTTypecheckResolveContext::context_vars_that_differ(*contexts).each do |var_name, vars|
|
|
861
|
+
common_type = ADSL::DS::DSClass.common_supertype(vars.map(&:type))
|
|
862
|
+
var = ADSL::DS::DSVariable.new :name => var_name, :type => common_type
|
|
863
|
+
objset = ADSL::DS::DSEitherLambdaObjset.new :either => either, :vars => vars
|
|
864
|
+
assignment = ADSL::DS::DSAssignment.new :var => var, :objset => objset
|
|
865
|
+
context.redefine_var var, nil
|
|
866
|
+
lambdas << assignment
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
return [ either, lambdas ]
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
def list_entity_classes_written_to
|
|
873
|
+
@blocks.map{ block.list_entity_classes_written_to }.flatten
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
def optimize
|
|
877
|
+
until_no_change super do |either|
|
|
878
|
+
next either.optimize unless either.is_a?(ASTEither)
|
|
879
|
+
next ASTDummyStmt.new if either.blocks.empty?
|
|
880
|
+
next either.blocks.first if either.blocks.length == 1
|
|
881
|
+
ASTEither.new(:blocks => either.blocks.map{ |block|
|
|
882
|
+
if block.statements.length == 1 && block.statements.first.is_a?(ASTEither)
|
|
883
|
+
block.statements.first.blocks
|
|
884
|
+
else
|
|
885
|
+
[block]
|
|
886
|
+
end
|
|
887
|
+
}.flatten(1).uniq)
|
|
888
|
+
end
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
def to_adsl
|
|
892
|
+
"either #{ @blocks.map{ |b| "{\n#{ b.to_adsl.adsl_indent }}" }.join " or " }\n"
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
class ASTDeleteObj < ASTNode
|
|
897
|
+
node_type :objset, :type => :statement
|
|
898
|
+
|
|
899
|
+
def typecheck_and_resolve(context)
|
|
900
|
+
objset = @objset.typecheck_and_resolve context
|
|
901
|
+
return [] if objset.type.nil?
|
|
902
|
+
return ADSL::DS::DSDeleteObj.new :objset => objset
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
def to_adsl
|
|
906
|
+
"delete #{ @objset.to_adsl }\n"
|
|
907
|
+
end
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
class ASTCreateTup < ASTNode
|
|
911
|
+
node_type :objset1, :rel_name, :objset2, :type => :statement
|
|
912
|
+
|
|
913
|
+
def typecheck_and_resolve(context)
|
|
914
|
+
objset1 = @objset1.typecheck_and_resolve context
|
|
915
|
+
objset2 = @objset2.typecheck_and_resolve context
|
|
916
|
+
raise ADSLError, "Ambiguous type on the left hand side on line #{@objset1.lineno}" if objset1.type.nil?
|
|
917
|
+
return [] if objset2.type.nil?
|
|
918
|
+
relation = context.find_relation objset1.type, @rel_name.text, @rel_name.lineno, objset2.type
|
|
919
|
+
return ADSL::DS::DSCreateTup.new :objset1 => objset1, :relation => relation, :objset2 => objset2
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
def to_adsl
|
|
923
|
+
"#{ @objset1.to_adsl }.#{ @rel_name.text } += #{ @objset2.to_adsl }\n"
|
|
924
|
+
end
|
|
925
|
+
end
|
|
926
|
+
|
|
927
|
+
class ASTDeleteTup < ASTNode
|
|
928
|
+
node_type :objset1, :rel_name, :objset2, :type => :statement
|
|
929
|
+
|
|
930
|
+
def typecheck_and_resolve(context)
|
|
931
|
+
objset1 = @objset1.typecheck_and_resolve context
|
|
932
|
+
objset2 = @objset2.typecheck_and_resolve context
|
|
933
|
+
raise ADSLError, "Ambiguous type on the left hand side on line #{@objset1.lineno}" if objset1.type.nil?
|
|
934
|
+
return [] if objset2.type.nil?
|
|
935
|
+
relation = context.find_relation objset1.type, @rel_name.text, @rel_name.lineno, objset2.type
|
|
936
|
+
return ADSL::DS::DSDeleteTup.new :objset1 => objset1, :relation => relation, :objset2 => objset2
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
def to_adsl
|
|
940
|
+
"#{ @objset1.to_adsl }.#{ @rel_name.text } -= #{ @objset2.to_adsl }\n"
|
|
941
|
+
end
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
class ASTSetTup < ASTNode
|
|
945
|
+
node_type :objset1, :rel_name, :objset2, :type => :statement
|
|
946
|
+
|
|
947
|
+
def typecheck_and_resolve(context)
|
|
948
|
+
objset1 = @objset1.typecheck_and_resolve context
|
|
949
|
+
objset2 = @objset2.typecheck_and_resolve context
|
|
950
|
+
raise ADSLError, "Ambiguous type on the left hand side on line #{@objset1.lineno}" if objset1.type.nil?
|
|
951
|
+
return [] if objset2.type.nil?
|
|
952
|
+
relation = context.find_relation objset1.type, @rel_name.text, @rel_name.lineno, objset2.type
|
|
953
|
+
return [
|
|
954
|
+
ADSL::DS::DSDeleteTup.new(:objset1 => objset1, :relation => relation, :objset2 => ADSL::DS::DSAllOf.new(:klass => relation.to_class)),
|
|
955
|
+
ADSL::DS::DSCreateTup.new(:objset1 => objset1, :relation => relation, :objset2 => objset2)
|
|
956
|
+
]
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
def to_adsl
|
|
960
|
+
"#{ @objset1.to_adsl }.#{ @rel_name.text } = #{ @objset2.to_adsl }\n"
|
|
961
|
+
end
|
|
962
|
+
end
|
|
963
|
+
|
|
964
|
+
class ASTAllOf < ASTNode
|
|
965
|
+
node_type :class_name, :type => :objset
|
|
966
|
+
|
|
967
|
+
def typecheck_and_resolve(context)
|
|
968
|
+
klass_node, klass = context.classes[@class_name.text]
|
|
969
|
+
raise ADSLError, "Unknown class name #{@class_name.text} on line #{@class_name.lineno}" if klass.nil?
|
|
970
|
+
return ADSL::DS::DSAllOf.new :klass => klass
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
def list_entity_classes_read
|
|
974
|
+
Set[context.classes[@class_name.text]]
|
|
975
|
+
end
|
|
976
|
+
|
|
977
|
+
def to_adsl
|
|
978
|
+
"allof(#{@class_name.text})"
|
|
979
|
+
end
|
|
980
|
+
end
|
|
981
|
+
|
|
982
|
+
class ASTSubset < ASTNode
|
|
983
|
+
node_type :objset, :type => :objset
|
|
984
|
+
|
|
985
|
+
def objset_has_side_effects?
|
|
986
|
+
@objset.nil? ? false : @objset.objset_has_side_effects?
|
|
987
|
+
end
|
|
988
|
+
|
|
989
|
+
def typecheck_and_resolve(context)
|
|
990
|
+
objset = @objset.typecheck_and_resolve context
|
|
991
|
+
return ADSL::DS::DSEmptyObjset.new if objset.type.nil?
|
|
992
|
+
return ADSL::DS::DSSubset.new :objset => objset
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
def optimize
|
|
996
|
+
until_no_change super do |subset|
|
|
997
|
+
subset.objset.is_a?(ASTSubset) ? subset.objset : subset
|
|
998
|
+
end
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
def to_adsl
|
|
1002
|
+
"subset(#{ @objset.to_adsl })"
|
|
1003
|
+
end
|
|
1004
|
+
end
|
|
1005
|
+
|
|
1006
|
+
class ASTOneOf < ASTNode
|
|
1007
|
+
node_type :objset, :type => :objset
|
|
1008
|
+
|
|
1009
|
+
def objset_has_side_effects?
|
|
1010
|
+
@objset.nil? ? false : @objset.objset_has_side_effects?
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
def typecheck_and_resolve(context)
|
|
1014
|
+
objset = @objset.typecheck_and_resolve context
|
|
1015
|
+
return ADSL::DS::DSEmptyObjset.new if objset.type.nil?
|
|
1016
|
+
return ADSL::DS::DSOneOf.new :objset => objset
|
|
1017
|
+
end
|
|
1018
|
+
|
|
1019
|
+
def optimize
|
|
1020
|
+
until_no_change super do |oneof|
|
|
1021
|
+
oneof.objset.is_a?(ASTOneOf) ? oneof.objset : oneof
|
|
1022
|
+
end
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
def to_adsl
|
|
1026
|
+
"oneof(#{ @objset.to_adsl })"
|
|
1027
|
+
end
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
class ASTUnion < ASTNode
|
|
1031
|
+
node_type :objsets, :type => :objset
|
|
1032
|
+
|
|
1033
|
+
def objset_has_side_effects?
|
|
1034
|
+
@objsets.nil? ? false : @objsets.map{ |o| o.objset_has_side_effects? }.include?(true)
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
def typecheck_and_resolve(context)
|
|
1038
|
+
objsets = @objsets.map{ |o| o.typecheck_and_resolve context }
|
|
1039
|
+
objsets.reject!{ |o| o.type.nil? }
|
|
1040
|
+
|
|
1041
|
+
return ADSL::DS::DSEmptyObjset.new if objsets.length == 0
|
|
1042
|
+
return objsets.first if objsets.length == 1
|
|
1043
|
+
|
|
1044
|
+
types = objsets.map{ |o| o.type }
|
|
1045
|
+
# will raise an error if no single common supertype exists
|
|
1046
|
+
ADSL::DS::DSClass.common_supertype(types)
|
|
1047
|
+
|
|
1048
|
+
return ADSL::DS::DSUnion.new :objsets => objsets
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
def optimize
|
|
1052
|
+
until_no_change super do |union|
|
|
1053
|
+
next ASTEmptyObjset.new if union.objsets.empty?
|
|
1054
|
+
next union.objsets.first if union.objsets.length == 1
|
|
1055
|
+
ASTUnion.new(:objsets => union.objsets.map{ |objset|
|
|
1056
|
+
objset.is_a?(ASTUnion) ? objset.objsets : [objset]
|
|
1057
|
+
}.flatten(1).reject{ |o| o.is_a? ASTEmptyObjset })
|
|
1058
|
+
end
|
|
1059
|
+
end
|
|
1060
|
+
|
|
1061
|
+
def to_adsl
|
|
1062
|
+
"union(#{ @objsets.map(&:to_adsl).join(', ') })"
|
|
1063
|
+
end
|
|
1064
|
+
end
|
|
1065
|
+
|
|
1066
|
+
class ASTOneOfObjset < ASTNode
|
|
1067
|
+
node_type :objsets, :type => :objset
|
|
1068
|
+
|
|
1069
|
+
def objset_has_side_effects?
|
|
1070
|
+
@objsets.nil? ? false : @objsets.map{ |o| o.objset_has_side_effects? }.include?(true)
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1073
|
+
def typecheck_and_resolve(context)
|
|
1074
|
+
objsets = @objsets.map{ |o| o.typecheck_and_resolve context }
|
|
1075
|
+
common_type = ADSL::DS::DSClass.common_supertype objsets.reject{ |o| o.type.nil? }
|
|
1076
|
+
if objsets.length == 0
|
|
1077
|
+
ADSL::DS::DSEmptyObjset.new
|
|
1078
|
+
elsif objsets.length == 1
|
|
1079
|
+
objsets.first
|
|
1080
|
+
else
|
|
1081
|
+
ADSL::DS::DSOneOfObjset.new :objsets => objsets
|
|
1082
|
+
end
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1085
|
+
def optimize
|
|
1086
|
+
until_no_change super do |o|
|
|
1087
|
+
if !o.is_a?(ASTOneOfObjset)
|
|
1088
|
+
o
|
|
1089
|
+
elsif o.objsets.empty?
|
|
1090
|
+
ASTEmptyObjset.new
|
|
1091
|
+
elsif o.objsets.length == 1
|
|
1092
|
+
o.objsets.first
|
|
1093
|
+
else
|
|
1094
|
+
ASTOneOfObjset.new(:objsets => o.objsets.uniq)
|
|
1095
|
+
end
|
|
1096
|
+
end
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1099
|
+
def to_adsl
|
|
1100
|
+
"any_of(#{ @objsets.map(&:to_adsl).join ', ' })"
|
|
1101
|
+
end
|
|
1102
|
+
end
|
|
1103
|
+
|
|
1104
|
+
class ASTVariable < ASTNode
|
|
1105
|
+
node_type :var_name, :type => :objset
|
|
1106
|
+
|
|
1107
|
+
def typecheck_and_resolve(context)
|
|
1108
|
+
var_node, var = context.lookup_var @var_name.text
|
|
1109
|
+
raise ADSLError, "Undefined variable #{@var_name.text} on line #{@var_name.lineno}" if var.nil?
|
|
1110
|
+
return var
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
def to_adsl
|
|
1114
|
+
@var_name.text
|
|
1115
|
+
end
|
|
1116
|
+
end
|
|
1117
|
+
|
|
1118
|
+
class ASTDereference < ASTNode
|
|
1119
|
+
node_type :objset, :rel_name, :type => :objset
|
|
1120
|
+
|
|
1121
|
+
def objset_has_side_effects?
|
|
1122
|
+
@objset.nil? ? false : @objset.objset_has_side_effects?
|
|
1123
|
+
end
|
|
1124
|
+
|
|
1125
|
+
def typecheck_and_resolve(context)
|
|
1126
|
+
objset = @objset.typecheck_and_resolve context
|
|
1127
|
+
klass = objset.type
|
|
1128
|
+
raise ADSLError, 'Empty objset dereference' if klass.nil?
|
|
1129
|
+
relation = context.find_relation objset.type, @rel_name.text, @rel_name.lineno
|
|
1130
|
+
return ADSL::DS::DSDereference.new :objset => objset, :relation => relation
|
|
1131
|
+
end
|
|
1132
|
+
|
|
1133
|
+
def to_adsl
|
|
1134
|
+
"#{ @objset.to_adsl }.#{ rel_name.text }"
|
|
1135
|
+
end
|
|
1136
|
+
end
|
|
1137
|
+
|
|
1138
|
+
class ASTDereferenceCreate < ASTNode
|
|
1139
|
+
node_type :objset, :rel_name, :empty_first, :type => :objset
|
|
1140
|
+
|
|
1141
|
+
def objset_has_side_effects?; true; end
|
|
1142
|
+
|
|
1143
|
+
def typecheck_and_resolve(context)
|
|
1144
|
+
objset = @objset.typecheck_and_resolve context
|
|
1145
|
+
klass = objset.type
|
|
1146
|
+
raise ADSLError, 'Cannot create an object on an empty objset' if klass.nil?
|
|
1147
|
+
relation = context.find_relation klass, @rel_name.text, @rel_name.lineno
|
|
1148
|
+
|
|
1149
|
+
create_objset = ASTCreateObjset.new(
|
|
1150
|
+
:class_name => ASTIdent.new(:text => relation.to_class.name)
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
assoc_builder = (@empty_first ? ASTSetTup : ASTCreateTup).new(
|
|
1154
|
+
:objset1 => @objset,
|
|
1155
|
+
:rel_name => @rel_name,
|
|
1156
|
+
:objset2 => create_objset
|
|
1157
|
+
)
|
|
1158
|
+
context.pre_stmts << assoc_builder.typecheck_and_resolve(context)
|
|
1159
|
+
|
|
1160
|
+
create_objset.typecheck_and_resolve(context)
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
def to_adsl
|
|
1164
|
+
"derefcreate(#{@objset.to_adsl}.#{@rel_name.text})"
|
|
1165
|
+
end
|
|
1166
|
+
end
|
|
1167
|
+
|
|
1168
|
+
class ASTEmptyObjset < ASTNode
|
|
1169
|
+
node_type :type => :objset
|
|
1170
|
+
|
|
1171
|
+
def typecheck_and_resolve(context)
|
|
1172
|
+
return ADSL::DS::DSEmptyObjset.new
|
|
1173
|
+
end
|
|
1174
|
+
|
|
1175
|
+
def to_adsl
|
|
1176
|
+
"empty"
|
|
1177
|
+
end
|
|
1178
|
+
end
|
|
1179
|
+
|
|
1180
|
+
class ASTInvariant < ASTNode
|
|
1181
|
+
node_type :name, :formula, :type => :formula
|
|
1182
|
+
|
|
1183
|
+
def typecheck_and_resolve(context)
|
|
1184
|
+
formula = @formula.typecheck_and_resolve context
|
|
1185
|
+
name = @name.nil? ? nil : @name.text
|
|
1186
|
+
return ADSL::DS::DSInvariant.new :name => name, :formula => formula
|
|
1187
|
+
end
|
|
1188
|
+
|
|
1189
|
+
def to_adsl
|
|
1190
|
+
n = (@name.nil? || @name.text.nil?) ? "" : "#{ @name.text.gsub(/\s/, '_') }: "
|
|
1191
|
+
"invariant #{n}#{ @formula.to_adsl }\n"
|
|
1192
|
+
end
|
|
1193
|
+
end
|
|
1194
|
+
|
|
1195
|
+
class ASTBoolean < ASTNode
|
|
1196
|
+
node_type :bool_value, :type => :formula
|
|
1197
|
+
|
|
1198
|
+
def typecheck_and_resolve(context)
|
|
1199
|
+
return ADSL::DS::DSBoolean::TRUE if @bool_value
|
|
1200
|
+
return ADSL::DS::DSBoolean::FALSE
|
|
1201
|
+
end
|
|
1202
|
+
|
|
1203
|
+
def to_adsl
|
|
1204
|
+
"#{ @bool_value }"
|
|
1205
|
+
end
|
|
1206
|
+
end
|
|
1207
|
+
|
|
1208
|
+
class ASTForAll < ASTNode
|
|
1209
|
+
node_type :vars, :subformula, :type => :formula
|
|
1210
|
+
|
|
1211
|
+
def typecheck_and_resolve(context)
|
|
1212
|
+
context.in_stack_frame do
|
|
1213
|
+
vars = []
|
|
1214
|
+
objsets = []
|
|
1215
|
+
@vars.each do |var_node, objset_node|
|
|
1216
|
+
objset = objset_node.typecheck_and_resolve context
|
|
1217
|
+
|
|
1218
|
+
var = ADSL::DS::DSVariable.new :name => var_node.text, :type => objset.type
|
|
1219
|
+
context.define_var var, var_node
|
|
1220
|
+
|
|
1221
|
+
vars << var
|
|
1222
|
+
objsets << objset
|
|
1223
|
+
end
|
|
1224
|
+
subformula = @subformula.typecheck_and_resolve context
|
|
1225
|
+
return ADSL::DS::DSForAll.new :vars => vars, :objsets => objsets, :subformula => subformula
|
|
1226
|
+
end
|
|
1227
|
+
end
|
|
1228
|
+
|
|
1229
|
+
def to_adsl
|
|
1230
|
+
v = @vars.map{ |var, objset| "#{ var.text } in #{ objset.to_adsl }" }.join ", "
|
|
1231
|
+
"forall(#{v}: #{ @subformula.to_adsl })"
|
|
1232
|
+
end
|
|
1233
|
+
end
|
|
1234
|
+
|
|
1235
|
+
class ASTExists < ASTNode
|
|
1236
|
+
node_type :vars, :subformula, :type => :formula
|
|
1237
|
+
|
|
1238
|
+
def typecheck_and_resolve(context)
|
|
1239
|
+
context.in_stack_frame do
|
|
1240
|
+
vars = []
|
|
1241
|
+
objsets = []
|
|
1242
|
+
@vars.each do |var_node, objset_node|
|
|
1243
|
+
objset = objset_node.typecheck_and_resolve context
|
|
1244
|
+
|
|
1245
|
+
var = ADSL::DS::DSVariable.new :name => var_node.text, :type => objset.type
|
|
1246
|
+
context.define_var var, var_node
|
|
1247
|
+
|
|
1248
|
+
vars << var
|
|
1249
|
+
objsets << objset
|
|
1250
|
+
end
|
|
1251
|
+
subformula = @subformula.nil? ? nil : @subformula.typecheck_and_resolve(context)
|
|
1252
|
+
return ADSL::DS::DSExists.new :vars => vars, :objsets => objsets, :subformula => subformula
|
|
1253
|
+
end
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
def to_adsl
|
|
1257
|
+
v = @vars.map{ |var, objset| "#{ var.text } in #{ objset.to_adsl }" }.join ", "
|
|
1258
|
+
"exists(#{v}: #{ @subformula.to_adsl })"
|
|
1259
|
+
end
|
|
1260
|
+
end
|
|
1261
|
+
|
|
1262
|
+
class ASTNot < ASTNode
|
|
1263
|
+
node_type :subformula, :type => :formula
|
|
1264
|
+
|
|
1265
|
+
def typecheck_and_resolve(context)
|
|
1266
|
+
subformula = @subformula.typecheck_and_resolve context
|
|
1267
|
+
raise "Substatement not a formula on line #{@subformula.lineno}" unless subformula.type == :formula
|
|
1268
|
+
return subformula.subformula if subformula.is_a? ADSL::DS::DSNot
|
|
1269
|
+
return ADSL::DS::DSNot.new :subformula => subformula
|
|
1270
|
+
end
|
|
1271
|
+
|
|
1272
|
+
def to_adsl
|
|
1273
|
+
"not(#{ @subformula.to_adsl })"
|
|
1274
|
+
end
|
|
1275
|
+
end
|
|
1276
|
+
|
|
1277
|
+
class ASTAnd < ASTNode
|
|
1278
|
+
node_type :subformulae, :type => :formula
|
|
1279
|
+
|
|
1280
|
+
def typecheck_and_resolve(context)
|
|
1281
|
+
subformulae = @subformulae.map{ |o| o.typecheck_and_resolve context }
|
|
1282
|
+
subformulae.each do |subformula|
|
|
1283
|
+
raise "Substatement not a formula on line #{subformula.lineno}" unless subformula.type == :formula
|
|
1284
|
+
end
|
|
1285
|
+
flattened_subformulae = []
|
|
1286
|
+
subformulae.each do |subformula|
|
|
1287
|
+
if subformula.is_a? ADSL::DS::DSAnd
|
|
1288
|
+
flattened_subformulae += subformula.subformulae
|
|
1289
|
+
else
|
|
1290
|
+
flattened_subformulae << subformula
|
|
1291
|
+
end
|
|
1292
|
+
end
|
|
1293
|
+
return ADSL::DS::DSAnd.new :subformulae => flattened_subformulae
|
|
1294
|
+
end
|
|
1295
|
+
|
|
1296
|
+
def to_adsl
|
|
1297
|
+
"and(#{ @subformulae.map(&:to_adsl).join ", " })"
|
|
1298
|
+
end
|
|
1299
|
+
end
|
|
1300
|
+
|
|
1301
|
+
class ASTOr < ASTNode
|
|
1302
|
+
node_type :subformulae, :type => :formula
|
|
1303
|
+
|
|
1304
|
+
def typecheck_and_resolve(context)
|
|
1305
|
+
subformulae = @subformulae.map{ |o| o.typecheck_and_resolve context }
|
|
1306
|
+
subformulae.each do |subformula|
|
|
1307
|
+
raise "Substatement not a formula on line #{subformula.lineno}" unless subformula.type == :formula
|
|
1308
|
+
end
|
|
1309
|
+
flattened_subformulae = []
|
|
1310
|
+
subformulae.each do |subformula|
|
|
1311
|
+
if subformula.is_a? ADSL::DS::DSOr
|
|
1312
|
+
flattened_subformulae += subformula.subformulae
|
|
1313
|
+
else
|
|
1314
|
+
flattened_subformulae << subformula
|
|
1315
|
+
end
|
|
1316
|
+
end
|
|
1317
|
+
return ADSL::DS::DSOr.new :subformulae => flattened_subformulae
|
|
1318
|
+
end
|
|
1319
|
+
|
|
1320
|
+
def to_adsl
|
|
1321
|
+
"or(#{ @subformulae.map(&:to_adsl).join ", " })"
|
|
1322
|
+
end
|
|
1323
|
+
end
|
|
1324
|
+
|
|
1325
|
+
class ASTEquiv < ASTNode
|
|
1326
|
+
node_type :subformulae, :type => :formula
|
|
1327
|
+
|
|
1328
|
+
def typecheck_and_resolve(context)
|
|
1329
|
+
subformulae = @subformulae.map{ |o| o.typecheck_and_resolve context }
|
|
1330
|
+
subformulae.each do |subformula|
|
|
1331
|
+
raise "Substatement not a formula on line #{subformula.lineno}" unless subformula.type == :formula
|
|
1332
|
+
end
|
|
1333
|
+
return ADSL::DS::DSEquiv.new :subformulae => subformulae
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1336
|
+
def to_adsl
|
|
1337
|
+
"equiv(#{ @subformulae.map(&:to_adsl).join ", " })"
|
|
1338
|
+
end
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
class ASTImplies < ASTNode
|
|
1342
|
+
node_type :subformula1, :subformula2, :type => :formula
|
|
1343
|
+
|
|
1344
|
+
def typecheck_and_resolve(context)
|
|
1345
|
+
subformula1 = @subformula1.typecheck_and_resolve context
|
|
1346
|
+
subformula2 = @subformula2.typecheck_and_resolve context
|
|
1347
|
+
|
|
1348
|
+
[subformula1, subformula2].each do |subformula|
|
|
1349
|
+
raise "Substatement not a formula on line #{subformula.lineno}" unless subformula.type == :formula
|
|
1350
|
+
end
|
|
1351
|
+
return ADSL::DS::DSImplies.new :subformula1 => subformula1, :subformula2 => subformula2
|
|
1352
|
+
end
|
|
1353
|
+
|
|
1354
|
+
def to_adsl
|
|
1355
|
+
"implies(#{ @subformula1.to_adsl }, #{ @subformula2.to_adsl })"
|
|
1356
|
+
end
|
|
1357
|
+
end
|
|
1358
|
+
|
|
1359
|
+
class ASTEqual < ASTNode
|
|
1360
|
+
node_type :objsets, :type => :formula
|
|
1361
|
+
|
|
1362
|
+
def typecheck_and_resolve(context)
|
|
1363
|
+
objsets = @objsets.map{ |o| o.typecheck_and_resolve context }
|
|
1364
|
+
|
|
1365
|
+
types = objsets.map{ |o| o.type }.select{ |o| not o.nil? }
|
|
1366
|
+
# will raise an error if no single common supertype exists
|
|
1367
|
+
ADSL::DS::DSClass.common_supertype(types)
|
|
1368
|
+
|
|
1369
|
+
return ADSL::DS::DSEqual.new :objsets => objsets
|
|
1370
|
+
end
|
|
1371
|
+
|
|
1372
|
+
def to_adsl
|
|
1373
|
+
"equal(#{ @objsets.map(&:to_adsl).join ", " })"
|
|
1374
|
+
end
|
|
1375
|
+
end
|
|
1376
|
+
|
|
1377
|
+
class ASTIn < ASTNode
|
|
1378
|
+
node_type :objset1, :objset2, :type => :formula
|
|
1379
|
+
|
|
1380
|
+
def typecheck_and_resolve(context)
|
|
1381
|
+
objset1 = @objset1.typecheck_and_resolve context
|
|
1382
|
+
objset2 = @objset2.typecheck_and_resolve context
|
|
1383
|
+
|
|
1384
|
+
return ADSL::DS::Boolean::TRUE if objset1.type.nil?
|
|
1385
|
+
return ADSL::DS::DSEmpty.new :objset => objset1 if objset2.type.nil?
|
|
1386
|
+
|
|
1387
|
+
raise ADSLError, "Object sets are not of compatible types: #{objset1.type.name}, #{objset2.type.name}" unless objset2.type.superclass_of? objset1.type
|
|
1388
|
+
return ADSL::DS::DSIn.new :objset1 => objset1, :objset2 => objset2
|
|
1389
|
+
end
|
|
1390
|
+
|
|
1391
|
+
def to_adsl
|
|
1392
|
+
"#{ @objset1.to_adsl } in #{ @objset2.to_adsl }"
|
|
1393
|
+
end
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
class ASTEmpty < ASTNode
|
|
1397
|
+
node_type :objset, :type => :formula
|
|
1398
|
+
|
|
1399
|
+
def typecheck_and_resolve(context)
|
|
1400
|
+
objset = @objset.typecheck_and_resolve context
|
|
1401
|
+
return ADSL::DS::Boolean::TRUE if objset.type.nil?
|
|
1402
|
+
return ADSL::DS::DSEmpty.new :objset => objset
|
|
1403
|
+
end
|
|
1404
|
+
|
|
1405
|
+
def to_adsl
|
|
1406
|
+
"empty(#{ @objset.to_adsl })"
|
|
1407
|
+
end
|
|
1408
|
+
end
|
|
1409
|
+
end
|
|
1410
|
+
end
|