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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -20
  3. data/README.md +14 -21
  4. data/bin/adsl-verify +8 -8
  5. data/lib/adsl.rb +3 -0
  6. data/lib/adsl/adsl.rb +3 -0
  7. data/lib/adsl/ds/data_store_spec.rb +339 -0
  8. data/lib/adsl/extract/instrumenter.rb +206 -0
  9. data/lib/adsl/extract/meta.rb +33 -0
  10. data/lib/adsl/extract/rails/action_block_builder.rb +233 -0
  11. data/lib/adsl/extract/rails/action_instrumenter.rb +400 -0
  12. data/lib/adsl/extract/rails/action_runner.rb +57 -0
  13. data/lib/adsl/extract/rails/active_record_metaclass_generator.rb +555 -0
  14. data/lib/adsl/extract/rails/callback_chain_simulator.rb +135 -0
  15. data/lib/adsl/extract/rails/invariant_extractor.rb +48 -0
  16. data/lib/adsl/extract/rails/invariant_instrumenter.rb +70 -0
  17. data/lib/adsl/extract/rails/other_meta.rb +57 -0
  18. data/lib/adsl/extract/rails/rails_extractor.rb +211 -0
  19. data/lib/adsl/extract/rails/rails_instrumentation_test_case.rb +34 -0
  20. data/lib/adsl/extract/rails/rails_special_gem_instrumentation.rb +120 -0
  21. data/lib/adsl/extract/rails/rails_test_helper.rb +140 -0
  22. data/lib/adsl/extract/sexp_utils.rb +54 -0
  23. data/lib/adsl/fol/first_order_logic.rb +261 -0
  24. data/lib/adsl/parser/adsl_parser.racc +159 -0
  25. data/lib/{parser → adsl/parser}/adsl_parser.rex +4 -4
  26. data/lib/{parser → adsl/parser}/adsl_parser.rex.rb +6 -6
  27. data/lib/adsl/parser/adsl_parser.tab.rb +1031 -0
  28. data/lib/adsl/parser/ast_nodes.rb +1410 -0
  29. data/lib/adsl/railtie.rb +67 -0
  30. data/lib/adsl/spass/bin.rb +230 -0
  31. data/lib/{spass → adsl/spass}/ruby_extensions.rb +0 -0
  32. data/lib/adsl/spass/spass_ds_extensions.rb +931 -0
  33. data/lib/adsl/spass/spass_translator.rb +393 -0
  34. data/lib/adsl/spass/util.rb +13 -0
  35. data/lib/adsl/util/csv_hash_formatter.rb +94 -0
  36. data/lib/adsl/util/general.rb +228 -0
  37. data/lib/adsl/util/test_helper.rb +71 -0
  38. data/lib/adsl/verification/formula_generators.rb +231 -0
  39. data/lib/adsl/verification/instrumentation_filter.rb +50 -0
  40. data/lib/adsl/verification/invariant.rb +19 -0
  41. data/lib/adsl/verification/rails_verification.rb +33 -0
  42. data/lib/adsl/verification/utils.rb +20 -0
  43. data/lib/adsl/verification/verification_case.rb +13 -0
  44. data/test/integration/rails/rails_branch_verification_test.rb +112 -0
  45. data/test/integration/rails/rails_verification_test.rb +253 -0
  46. data/test/integration/spass/basic_translation_test.rb +563 -0
  47. data/test/integration/spass/control_flow_translation_test.rb +421 -0
  48. data/test/unit/adsl/ds/data_store_spec_test.rb +54 -0
  49. data/test/unit/adsl/extract/instrumenter_test.rb +103 -0
  50. data/test/unit/adsl/extract/meta_test.rb +142 -0
  51. data/test/unit/adsl/extract/rails/action_block_builder_test.rb +178 -0
  52. data/test/unit/adsl/extract/rails/action_instrumenter_test.rb +68 -0
  53. data/test/unit/adsl/extract/rails/active_record_metaclass_generator_test.rb +336 -0
  54. data/test/unit/adsl/extract/rails/callback_chain_simulator_test.rb +76 -0
  55. data/test/unit/adsl/extract/rails/invariant_extractor_test.rb +92 -0
  56. data/test/unit/adsl/extract/rails/rails_extractor_test.rb +1380 -0
  57. data/test/unit/adsl/extract/rails/rails_test_helper_test.rb +25 -0
  58. data/test/unit/adsl/extract/sexp_utils_test.rb +100 -0
  59. data/test/unit/adsl/fol/first_order_logic_test.rb +227 -0
  60. data/test/unit/adsl/parser/action_parser_test.rb +1040 -0
  61. data/test/unit/adsl/parser/ast_nodes_test.rb +359 -0
  62. data/test/unit/adsl/parser/class_parser_test.rb +288 -0
  63. data/test/unit/adsl/parser/general_parser_test.rb +67 -0
  64. data/test/unit/adsl/parser/invariant_parser_test.rb +432 -0
  65. data/test/unit/adsl/parser/parser_util_test.rb +126 -0
  66. data/test/unit/adsl/spass/bin_test.rb +65 -0
  67. data/test/unit/adsl/spass/ruby_extensions_test.rb +39 -0
  68. data/test/unit/adsl/spass/spass_ds_extensions_test.rb +7 -0
  69. data/test/unit/adsl/spass/spass_translator_test.rb +342 -0
  70. data/test/unit/adsl/util/csv_hash_formatter_test.rb +68 -0
  71. data/test/unit/adsl/util/general_test.rb +303 -0
  72. data/test/unit/adsl/util/test_helper_test.rb +120 -0
  73. data/test/unit/adsl/verification/formula_generators_test.rb +200 -0
  74. data/test/unit/adsl/verification/instrumentation_filter_test.rb +39 -0
  75. data/test/unit/adsl/verification/utils_test.rb +39 -0
  76. data/test/unit/adsl/verification/verification_case_test.rb +8 -0
  77. metadata +229 -29
  78. data/lib/ds/data_store_spec.rb +0 -292
  79. data/lib/fol/first_order_logic.rb +0 -260
  80. data/lib/parser/adsl_ast.rb +0 -779
  81. data/lib/parser/adsl_parser.racc +0 -151
  82. data/lib/parser/adsl_parser.tab.rb +0 -976
  83. data/lib/parser/dsdl_parser.rex.rb +0 -196
  84. data/lib/parser/dsdl_parser.tab.rb +0 -976
  85. data/lib/spass/bin.rb +0 -164
  86. data/lib/spass/spass_ds_extensions.rb +0 -870
  87. data/lib/spass/spass_translator.rb +0 -388
  88. data/lib/spass/util.rb +0 -11
  89. data/lib/util/csv_hash_formatter.rb +0 -47
  90. data/lib/util/test_helper.rb +0 -33
  91. data/lib/util/util.rb +0 -114
@@ -0,0 +1,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