adsl 0.0.2
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.
- data/Gemfile +17 -0
- data/LICENSE +165 -0
- data/README.md +45 -0
- data/bin/adsl-verify +111 -0
- data/lib/ds/data_store_spec.rb +292 -0
- data/lib/fol/first_order_logic.rb +261 -0
- data/lib/parser/adsl_ast.rb +780 -0
- data/lib/parser/adsl_parser.racc +151 -0
- data/lib/parser/adsl_parser.rex +48 -0
- data/lib/parser/adsl_parser.rex.rb +196 -0
- data/lib/parser/adsl_parser.tab.rb +976 -0
- data/lib/parser/dsdl_parser.rex.rb +196 -0
- data/lib/parser/dsdl_parser.tab.rb +976 -0
- data/lib/spass/bin.rb +164 -0
- data/lib/spass/ruby_extensions.rb +25 -0
- data/lib/spass/spass_ds_extensions.rb +870 -0
- data/lib/spass/spass_translator.rb +378 -0
- data/lib/spass/util.rb +11 -0
- data/lib/util/csv_hash_formatter.rb +39 -0
- data/lib/util/test_helper.rb +33 -0
- data/lib/util/util.rb +110 -0
- metadata +185 -0
@@ -0,0 +1,261 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support/all'
|
3
|
+
require 'util/util'
|
4
|
+
|
5
|
+
class String
|
6
|
+
def resolve_spass
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
def split_by_zero_level_comma
|
11
|
+
parts = []
|
12
|
+
sequence_beginning_index = 0
|
13
|
+
index = 0
|
14
|
+
paren_level = 0
|
15
|
+
while index < length
|
16
|
+
if self[index, 1] == '('
|
17
|
+
paren_level += 1
|
18
|
+
elsif self[index, 1] == ')'
|
19
|
+
paren_level -= 1
|
20
|
+
raise ArgumentError, 'Unmatching parenthesis' if paren_level < 0
|
21
|
+
elsif self[index, 1] == ',' and paren_level == 0
|
22
|
+
parts << self[sequence_beginning_index, index - sequence_beginning_index].strip
|
23
|
+
sequence_beginning_index = index + 1
|
24
|
+
end
|
25
|
+
index += 1
|
26
|
+
end
|
27
|
+
parts << self[sequence_beginning_index, length - sequence_beginning_index].strip
|
28
|
+
parts
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Symbol
|
33
|
+
def resolve_spass
|
34
|
+
to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class TrueClass
|
39
|
+
def resolve_spass
|
40
|
+
"true"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class FalseClass
|
45
|
+
def resolve_spass
|
46
|
+
"false"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module FOL
|
51
|
+
class Not
|
52
|
+
def initialize(*formulae)
|
53
|
+
@formulae = formulae.flatten
|
54
|
+
raise ArgumentError, "At least one subformula required" if @formulae.empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
def resolve_spass
|
58
|
+
children = @formulae.map{ |obj| obj.resolve_spass }
|
59
|
+
children.delete_if{ |a| a == 'false' }
|
60
|
+
return 'false' if children.include? 'true'
|
61
|
+
return And.new(children.map{ |child| child.match('\Anot\((.*)\)\z') ? $1 : "not(#{child})" }).resolve_spass
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
class And
|
67
|
+
attr_reader :objs
|
68
|
+
|
69
|
+
def initialize(*objs)
|
70
|
+
@objs = objs.flatten
|
71
|
+
end
|
72
|
+
|
73
|
+
def resolve_spass
|
74
|
+
children = @objs.map{ |child| child.resolve_spass }
|
75
|
+
children = children.map{ |child| child.match('\Aand\((.*)\)\z') ? $1.split_by_zero_level_comma : child }.flatten
|
76
|
+
children.delete_if{ |a| a == 'true' }
|
77
|
+
return 'false' if children.include? 'false'
|
78
|
+
return 'true' if children.empty?
|
79
|
+
return children.first if children.length == 1
|
80
|
+
return "and(#{children.join(', ')})"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Or
|
85
|
+
attr_reader :objs
|
86
|
+
|
87
|
+
def initialize(*objs)
|
88
|
+
@objs = objs.flatten
|
89
|
+
end
|
90
|
+
|
91
|
+
def resolve_spass
|
92
|
+
children = @objs.map{ |child| child.resolve_spass }
|
93
|
+
children = children.map{ |child| child.match('\Aor\((.*)\)\z') ? $1.split_by_zero_level_comma : child }.flatten
|
94
|
+
children.delete_if{ |a| a == 'false' }
|
95
|
+
return 'true' if children.include? 'true'
|
96
|
+
return 'false' if children.empty?
|
97
|
+
return children.first if children.length == 1
|
98
|
+
return "or(#{children.join(', ')})"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class ForAll
|
103
|
+
def initialize(*params)
|
104
|
+
params = params.flatten
|
105
|
+
raise ArgumentError, "At least a formula required" if params.length < 1
|
106
|
+
@args = params.first(params.length - 1)
|
107
|
+
@formula = params.last
|
108
|
+
end
|
109
|
+
|
110
|
+
def resolve_spass
|
111
|
+
args = @args.map{ |obj| obj.resolve_spass }
|
112
|
+
formula = @formula.resolve_spass
|
113
|
+
return formula if args.empty?
|
114
|
+
return 'true' if formula == 'true'
|
115
|
+
return 'false' if formula == 'false'
|
116
|
+
"forall( [#{args.join(', ')}], #{formula})"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Exists
|
121
|
+
def initialize(*params)
|
122
|
+
params = params.flatten
|
123
|
+
raise ArgumentError, "At least a formula required" if params.length < 1
|
124
|
+
@args = params.first(params.length - 1)
|
125
|
+
@formula = params.last
|
126
|
+
end
|
127
|
+
|
128
|
+
def resolve_spass
|
129
|
+
args = @args.map{ |obj| obj.resolve_spass }
|
130
|
+
formula = @formula.resolve_spass
|
131
|
+
return formula if args.empty?
|
132
|
+
return 'true' if formula == 'true'
|
133
|
+
return 'false' if formula == 'false'
|
134
|
+
"exists( [#{args.join(', ')}], #{formula})"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class Equal
|
139
|
+
def initialize(*subformulae)
|
140
|
+
@subformulae = subformulae.flatten
|
141
|
+
raise ArgumentError, "At least two subformulae required" if @subformulae.length < 2
|
142
|
+
end
|
143
|
+
|
144
|
+
def resolve_spass
|
145
|
+
return @subformulae.first.resolve_spass if @subformulae.length == 1
|
146
|
+
combinations = []
|
147
|
+
(@subformulae.length-1).times do |index|
|
148
|
+
combinations << "equal(#{@subformulae[index].resolve_spass}, #{@subformulae[index+1].resolve_spass})"
|
149
|
+
end
|
150
|
+
return And.new(combinations).resolve_spass
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class Equiv
|
155
|
+
def initialize(*subformulae)
|
156
|
+
@subformulae = subformulae.flatten
|
157
|
+
raise ArgumentError, "At least two subformulae required" if @subformulae.length < 2
|
158
|
+
end
|
159
|
+
|
160
|
+
def resolve_spass
|
161
|
+
subformulae = @subformulae.map{ |sub| sub.resolve_spass }
|
162
|
+
return subformulae.first if subformulae.length == 1
|
163
|
+
return And.new(subformulae).resolve_spass if subformulae.include? 'true'
|
164
|
+
return Not.new(subformulae).resolve_spass if subformulae.include? 'false'
|
165
|
+
combinations = []
|
166
|
+
(subformulae.length-1).times do |index|
|
167
|
+
combinations << "equiv(#{subformulae[index]}, #{subformulae[index+1]})"
|
168
|
+
end
|
169
|
+
return And.new(combinations).resolve_spass
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class Implies
|
174
|
+
def initialize(from, to)
|
175
|
+
@from = from
|
176
|
+
@to = to
|
177
|
+
end
|
178
|
+
|
179
|
+
def resolve_spass
|
180
|
+
from = @from.resolve_spass
|
181
|
+
to = @to.resolve_spass
|
182
|
+
return to if from == 'true'
|
183
|
+
return 'true' if from == 'false'
|
184
|
+
return Not.new(from).resolve_spass if to == 'false'
|
185
|
+
return 'true' if to == 'true'
|
186
|
+
return "implies(#{from}, #{to})"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class OneOf
|
191
|
+
def initialize(*formulae)
|
192
|
+
@formulae = formulae.flatten
|
193
|
+
end
|
194
|
+
|
195
|
+
def resolve_spass
|
196
|
+
return 'false' if @formulae.empty?
|
197
|
+
return @formulae.first.resolve_spass if @formulae.length == 1
|
198
|
+
return Equiv.new(Not.new(@formulae.first), @formulae.last).resolve_spass if @formulae.length == 2
|
199
|
+
|
200
|
+
substatements = []
|
201
|
+
@formulae.length.times do |i|
|
202
|
+
formulae_without_i = @formulae.first(i) + @formulae.last(@formulae.length - 1 - i)
|
203
|
+
substatements << Implies.new(@formulae[i], Not.new(formulae_without_i))
|
204
|
+
end
|
205
|
+
And.new(Or.new(@formulae), substatements).resolve_spass
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
class IfThenElse
|
210
|
+
def initialize(iif, tthen, eelse)
|
211
|
+
@iif = iif
|
212
|
+
@tthen = tthen
|
213
|
+
@eelse = eelse
|
214
|
+
end
|
215
|
+
|
216
|
+
def resolve_spass
|
217
|
+
And.new(Implies.new(@iif, @tthen), Implies.new(Not.new(@iif), @eelse)).resolve_spass
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
class IfThenElseEq
|
222
|
+
def initialize(iif, tthen, eelse)
|
223
|
+
@iif = iif
|
224
|
+
@tthen = tthen
|
225
|
+
@eelse = eelse
|
226
|
+
end
|
227
|
+
|
228
|
+
def resolve_spass
|
229
|
+
And.new(Equiv.new(@iif, @tthen), Equiv.new(Not.new(@iif), @eelse)).resolve_spass
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
class PairwiseEqual
|
234
|
+
def initialize(*list)
|
235
|
+
list = list.flatten
|
236
|
+
@list1 = list.first((list.length/2.0).ceil)
|
237
|
+
@list2 = list.last((list.length/2.0).floor)
|
238
|
+
raise ArgumentError, "Lists not of equal length: [#{@list1.join(", ")}], [#{@list2.join(", ")}]" if @list1.length != @list2.length
|
239
|
+
end
|
240
|
+
|
241
|
+
def resolve_spass
|
242
|
+
equalities = []
|
243
|
+
@list1.length.times do |i|
|
244
|
+
equalities << Equal.new(@list1[i], @list2[i])
|
245
|
+
end
|
246
|
+
return And.new(equalities).resolve_spass
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# define a function for each of the above classes, starting with underscore and underscored* afterwards
|
251
|
+
# *see: http://api.rubyonrails.org/v2.3.8/classes/ActiveSupport/CoreExtensions/String/Inflections.html
|
252
|
+
self.constants.each do |klass_name|
|
253
|
+
instance_eval do
|
254
|
+
klass = FOL.const_get(klass_name)
|
255
|
+
self.send :define_method, "_#{klass_name.underscore}".to_sym do |*args|
|
256
|
+
klass.new(*args).resolve_spass
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
@@ -0,0 +1,780 @@
|
|
1
|
+
require 'ds/data_store_spec'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_support'
|
4
|
+
require 'util/util'
|
5
|
+
require 'pp'
|
6
|
+
require 'set'
|
7
|
+
|
8
|
+
module ADSL
|
9
|
+
|
10
|
+
class ADSLNode
|
11
|
+
def self.node_type(*fields)
|
12
|
+
container_for *fields
|
13
|
+
container_for :lineno
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ADSLError < StandardError; end
|
18
|
+
|
19
|
+
class ADSLDummyObjset < ADSLNode
|
20
|
+
node_type :type
|
21
|
+
|
22
|
+
def typecheck_and_resolve(context)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ADSLSpec < ADSLNode
|
28
|
+
node_type :classes, :actions, :invariants
|
29
|
+
|
30
|
+
def typecheck_and_resolve
|
31
|
+
context = ADSLTypecheckResolveContext.new
|
32
|
+
|
33
|
+
# make sure class names are unique
|
34
|
+
@classes.each do |class_node|
|
35
|
+
if context.classes.include? class_node.name.text
|
36
|
+
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}"
|
37
|
+
end
|
38
|
+
klass = DS::DSClass.new :name => class_node.name.text
|
39
|
+
context.classes[klass.name] = [class_node, klass]
|
40
|
+
end
|
41
|
+
|
42
|
+
# make sure the parent classes are declared properly and that the inheritance graph is non-cyclic
|
43
|
+
parents = Hash.new{}
|
44
|
+
context.classes.values.select{ |v| v[0].parent_name }.each do |class_node, klass|
|
45
|
+
parent_node, parent = context.classes[class_node.parent_name.text]
|
46
|
+
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?
|
47
|
+
klass.parent = parent
|
48
|
+
|
49
|
+
parents[klass] = parent
|
50
|
+
parent_chain = [klass]
|
51
|
+
while parent != nil do
|
52
|
+
if parent_chain.include? parent
|
53
|
+
cyclic_chain = parent_chain.slice(parent_chain.index(parent), parent_chain.length) + [parent]
|
54
|
+
raise ADSLError, "Cyclic inheritance detected: #{cyclic_chain.map{ |c| c.name }.join ' -> '}"
|
55
|
+
end
|
56
|
+
parent_chain << parent
|
57
|
+
parent = parents[parent]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# make sure relations are valid and refer to existing classes
|
62
|
+
context.classes.values.each do |class_node, klass|
|
63
|
+
class_node.relations.each do |rel_node|
|
64
|
+
iter = klass
|
65
|
+
while iter != nil
|
66
|
+
if context.relations[iter.name].include? rel_node.name.text
|
67
|
+
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}"
|
68
|
+
end
|
69
|
+
iter = iter.parent
|
70
|
+
end
|
71
|
+
rel = DS::DSRelation.new :name => rel_node.name.text, :from_class => klass
|
72
|
+
context.relations[klass.name][rel.name] = [rel_node, rel]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# now that classes and rels are initialized, check them
|
77
|
+
@classes.each do |class_node|
|
78
|
+
class_node.typecheck_and_resolve context
|
79
|
+
end
|
80
|
+
|
81
|
+
@actions.each do |action_node|
|
82
|
+
action_node.typecheck_and_resolve context
|
83
|
+
end
|
84
|
+
|
85
|
+
# make sure invariants have unique names; add names to unnamed invariants
|
86
|
+
names = Set.new
|
87
|
+
@invariants.each do |invariant_node|
|
88
|
+
invariant = invariant_node.typecheck_and_resolve context
|
89
|
+
if invariant.name && names.include?(invariant.name)
|
90
|
+
raise ADSLError, "Duplicate invariant name #{invariant.name} on line #{invariant_node.lineno}"
|
91
|
+
end
|
92
|
+
name = invariant.name || "unnamed_line_#{invariant_node.lineno}"
|
93
|
+
while names.include? name
|
94
|
+
name = name.increment_suffix
|
95
|
+
end
|
96
|
+
invariant.name = name
|
97
|
+
context.invariants << invariant
|
98
|
+
names << name
|
99
|
+
end
|
100
|
+
|
101
|
+
@invariants.each do |invariant_node|
|
102
|
+
invariant = invariant_node.typecheck_and_resolve context
|
103
|
+
end
|
104
|
+
|
105
|
+
DS::DSSpec.new(
|
106
|
+
:classes => context.classes.map{ |a, b| b[1] },
|
107
|
+
:actions => context.actions.map{ |a, b| b[1] },
|
108
|
+
:invariants => context.invariants.dup
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class ADSLClass < ADSLNode
|
114
|
+
node_type :name, :parent_name, :relations
|
115
|
+
|
116
|
+
def typecheck_and_resolve(context)
|
117
|
+
klass = context.classes[@name.text][1]
|
118
|
+
@relations.each do |rel_node|
|
119
|
+
rel = context.relations[@name.text][rel_node.name.text][1]
|
120
|
+
klass.relations << rel
|
121
|
+
|
122
|
+
if rel_node.cardinality[0] > rel_node.cardinality[1]
|
123
|
+
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]}"
|
124
|
+
end
|
125
|
+
if rel_node.cardinality[1] == 0
|
126
|
+
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"
|
127
|
+
end
|
128
|
+
unless context.classes.include? rel_node.to_class_name.text
|
129
|
+
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}"
|
130
|
+
end
|
131
|
+
|
132
|
+
rel.to_class = context.classes[rel_node.to_class_name.text][1]
|
133
|
+
rel.cardinality = rel_node.cardinality
|
134
|
+
if rel_node.inverse_of_name
|
135
|
+
target_class = context.classes[rel.to_class.name][1]
|
136
|
+
inverse_of_node, inverse_of = context.relations[target_class.name][rel_node.inverse_of_name.text]
|
137
|
+
|
138
|
+
while inverse_of_node.nil?
|
139
|
+
inverse_of_node, inverse_of = context.relations[target_class.name][rel_node.inverse_of_name.text]
|
140
|
+
target_class = target_class.parent
|
141
|
+
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?
|
142
|
+
end
|
143
|
+
|
144
|
+
if inverse_of_node.inverse_of_name
|
145
|
+
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}"
|
146
|
+
end
|
147
|
+
rel.inverse_of = inverse_of
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class ADSLRelation < ADSLNode
|
154
|
+
node_type :cardinality, :to_class_name, :name, :inverse_of_name
|
155
|
+
end
|
156
|
+
|
157
|
+
class ADSLIdent < ADSLNode
|
158
|
+
node_type :text
|
159
|
+
end
|
160
|
+
|
161
|
+
class ADSLTypecheckResolveContext
|
162
|
+
attr_accessor :classes, :relations, :actions, :invariants, :var_stack
|
163
|
+
|
164
|
+
class ADSLStackFrame < ActiveSupport::OrderedHash
|
165
|
+
attr_accessor :var_write_listeners
|
166
|
+
attr_accessor :var_read_listeners
|
167
|
+
|
168
|
+
def initialize
|
169
|
+
super
|
170
|
+
@var_write_listeners = []
|
171
|
+
@var_read_listeners = []
|
172
|
+
end
|
173
|
+
|
174
|
+
def on_var_read(&block)
|
175
|
+
listeners = @var_read_listeners
|
176
|
+
listeners.push block
|
177
|
+
end
|
178
|
+
|
179
|
+
def on_var_write(&block)
|
180
|
+
listeners = @var_write_listeners
|
181
|
+
listeners.push block
|
182
|
+
end
|
183
|
+
|
184
|
+
def fire_write_event(name)
|
185
|
+
listeners = @var_write_listeners
|
186
|
+
listeners.each do |listener|
|
187
|
+
listener.call name
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def fire_read_event(name)
|
192
|
+
listeners = @var_read_listeners
|
193
|
+
listeners.each do |listener|
|
194
|
+
listener.call name
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def dup
|
199
|
+
other = ADSLStackFrame.new
|
200
|
+
self.each do |key, val|
|
201
|
+
other[key] = val.dup
|
202
|
+
end
|
203
|
+
other.var_write_listeners = @var_write_listeners.dup
|
204
|
+
other.var_read_listeners = @var_read_listeners.dup
|
205
|
+
other
|
206
|
+
end
|
207
|
+
|
208
|
+
def clone
|
209
|
+
dup
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def initialize
|
214
|
+
# name => [astnode, dsobj]
|
215
|
+
@classes = ActiveSupport::OrderedHash.new
|
216
|
+
# classname => name => [astnode, dsobj]
|
217
|
+
@relations = ActiveSupport::OrderedHash.new { |hash, key| hash[key] = ActiveSupport::OrderedHash.new }
|
218
|
+
# stack of name => [astnode, dsobj]
|
219
|
+
@actions = ActiveSupport::OrderedHash.new
|
220
|
+
@invariants = []
|
221
|
+
@var_stack = []
|
222
|
+
end
|
223
|
+
|
224
|
+
def initialize_copy(source)
|
225
|
+
super
|
226
|
+
source.classes.each do |name, value|
|
227
|
+
@classes[name] = value.dup
|
228
|
+
end
|
229
|
+
source.relations.each do |class_name, class_entry|
|
230
|
+
entries = @relations[class_name]
|
231
|
+
class_entry.each do |name, value|
|
232
|
+
entries[name] = value.dup
|
233
|
+
end
|
234
|
+
end
|
235
|
+
@actions = source.actions.dup
|
236
|
+
@invariants = source.invariants.dup
|
237
|
+
@var_stack = []
|
238
|
+
source.var_stack.each do |frame|
|
239
|
+
@var_stack << frame.dup
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def on_var_write(&block)
|
244
|
+
@var_stack.last.on_var_write(&block)
|
245
|
+
end
|
246
|
+
|
247
|
+
def on_var_read(&block)
|
248
|
+
@var_stack.last.on_var_read(&block)
|
249
|
+
end
|
250
|
+
|
251
|
+
def in_stack_frame
|
252
|
+
push_frame
|
253
|
+
yield
|
254
|
+
ensure
|
255
|
+
pop_frame
|
256
|
+
end
|
257
|
+
|
258
|
+
def push_frame
|
259
|
+
@var_stack.push ADSLStackFrame.new
|
260
|
+
end
|
261
|
+
|
262
|
+
def pop_frame
|
263
|
+
@var_stack.pop
|
264
|
+
end
|
265
|
+
|
266
|
+
def define_var(var, node)
|
267
|
+
raise ADSLError, "Defining variables on a stack with no stack frames" if @var_stack.empty?
|
268
|
+
prev_var_node, prev_var = lookup_var var.name
|
269
|
+
raise ADSLError, "Duplicate identifier '#{var.name}' on line #{node.lineno}; previous definition on line #{prev_var_node.lineno}" unless prev_var.nil?
|
270
|
+
@var_stack.last[var.name] = [node, var]
|
271
|
+
@var_stack.last.fire_write_event var.name
|
272
|
+
return var
|
273
|
+
end
|
274
|
+
|
275
|
+
def redefine_var(var, node)
|
276
|
+
@var_stack.length.times do |frame_index|
|
277
|
+
frame = @var_stack[frame_index]
|
278
|
+
next unless frame.include? var.name
|
279
|
+
old_var = frame[var.name][1]
|
280
|
+
|
281
|
+
raise ADSL::ADSLError, "Unmatched type '#{var.type.name}' for variable '#{var.name}' on line #{node.lineno}" if var.type != old_var.type
|
282
|
+
|
283
|
+
frame[var.name][1] = var
|
284
|
+
|
285
|
+
@var_stack[frame_index..-1].reverse.each do |subframe|
|
286
|
+
subframe.fire_write_event var.name
|
287
|
+
end
|
288
|
+
|
289
|
+
return var
|
290
|
+
end
|
291
|
+
return define_var var, node
|
292
|
+
end
|
293
|
+
|
294
|
+
def lookup_var(name, fire_read_event=true)
|
295
|
+
@var_stack.length.times do |index|
|
296
|
+
frame = @var_stack[index]
|
297
|
+
next if frame[name].nil?
|
298
|
+
var = frame[name]
|
299
|
+
|
300
|
+
if fire_read_event
|
301
|
+
@var_stack[index..-1].reverse.each do |subframe|
|
302
|
+
subframe.fire_read_event name
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# handle events here, none defined atm
|
307
|
+
return var
|
308
|
+
end
|
309
|
+
nil
|
310
|
+
end
|
311
|
+
|
312
|
+
def self.context_vars_that_differ(*contexts)
|
313
|
+
vars_per_context = []
|
314
|
+
contexts.each do |context|
|
315
|
+
vars_per_context << context.var_stack.inject({}) { |so_far, frame| so_far.merge! frame }
|
316
|
+
end
|
317
|
+
all_vars = vars_per_context.map{ |c| c.keys }.flatten.uniq
|
318
|
+
packed = {}
|
319
|
+
all_vars.each do |v|
|
320
|
+
packed[v] = vars_per_context.map{ |vpc| vpc[v][1] }
|
321
|
+
end
|
322
|
+
packed.delete_if { |v, vars| vars.uniq.length == 1 }
|
323
|
+
packed
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
class ADSLAction < ADSLNode
|
328
|
+
node_type :name, :arg_cardinalities, :arg_names, :arg_types, :block
|
329
|
+
|
330
|
+
def typecheck_and_resolve(context)
|
331
|
+
old_action_node, old_action = context.actions[@name.text]
|
332
|
+
raise ADSLError, "Duplicate action name #{@name.text} on line #{@name.lineno}; first definition on line #{old_action_node.name.lineno}" unless old_action.nil?
|
333
|
+
arguments = []
|
334
|
+
cardinalities = []
|
335
|
+
block = nil
|
336
|
+
context.in_stack_frame do
|
337
|
+
@arg_names.length.times do |i|
|
338
|
+
cardinality = @arg_cardinalities[i]
|
339
|
+
if cardinality[0] > cardinality[1]
|
340
|
+
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]}"
|
341
|
+
end
|
342
|
+
if cardinality[1] == 0
|
343
|
+
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"
|
344
|
+
end
|
345
|
+
cardinality = cardinality.first 2
|
346
|
+
|
347
|
+
klass_node, klass = context.classes[@arg_types[i].text]
|
348
|
+
raise ADSLError, "Unknown class #{@arg_types[i].text} on line #{@arg_types[i].lineno}" if klass.nil?
|
349
|
+
var = DS::DSVariable.new :name => @arg_names[i].text, :type => klass
|
350
|
+
context.define_var var, @arg_types[i]
|
351
|
+
arguments << var
|
352
|
+
cardinalities << cardinality
|
353
|
+
end
|
354
|
+
block = @block.typecheck_and_resolve context, false
|
355
|
+
end
|
356
|
+
action = DS::DSAction.new :name => @name.text, :args => arguments, :cardinalities => cardinalities, :block => block
|
357
|
+
context.actions[action.name] = [self, action]
|
358
|
+
return action
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
class ADSLBlock < ADSLNode
|
363
|
+
node_type :statements
|
364
|
+
|
365
|
+
def typecheck_and_resolve(context, open_subcontext=true)
|
366
|
+
context.push_frame if open_subcontext
|
367
|
+
stmts = @statements.map{ |node| node.typecheck_and_resolve context }.flatten
|
368
|
+
return DS::DSBlock.new :statements => stmts
|
369
|
+
ensure
|
370
|
+
context.pop_frame if open_subcontext
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
class ADSLAssignment < ADSLNode
|
375
|
+
node_type :var_name, :objset
|
376
|
+
|
377
|
+
def typecheck_and_resolve(context)
|
378
|
+
objset = @objset.typecheck_and_resolve context
|
379
|
+
@var = DS::DSVariable.new :name => @var_name.text, :type => objset.type
|
380
|
+
context.redefine_var @var, @var_name
|
381
|
+
return DS::DSAssignment.new :var => @var, :objset => objset
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
class ADSLCreateObj < ADSLNode
|
386
|
+
node_type :var_name, :class_name
|
387
|
+
|
388
|
+
def typecheck_and_resolve(context)
|
389
|
+
klass_node, klass = context.classes[@class_name.text]
|
390
|
+
raise ADSLError, "Undefined class #{@class_name.text} referred to at line #{@class_name.lineno}" if klass.nil?
|
391
|
+
create_obj = DS::DSCreateObj.new :klass => klass
|
392
|
+
if @var_name
|
393
|
+
var = DS::DSVariable.new :name => @var_name.text, :type => klass
|
394
|
+
objset = DS::DSCreateObjset.new :createobj => create_obj
|
395
|
+
assignment = DS::DSAssignment.new :var => var, :objset => objset
|
396
|
+
context.redefine_var var, @var_name
|
397
|
+
return [create_obj, assignment]
|
398
|
+
end
|
399
|
+
return create_obj
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
class ADSLForEach < ADSLNode
|
404
|
+
node_type :var_name, :objset, :block
|
405
|
+
|
406
|
+
def typecheck_and_resolve(context)
|
407
|
+
before_context = context.dup
|
408
|
+
objset = @objset.typecheck_and_resolve context
|
409
|
+
|
410
|
+
temp_iterator_objset = ADSL::ADSLDummyObjset.new :type => objset.type
|
411
|
+
assignment = ADSL::ADSLAssignment.new :lineno => @lineno, :var_name => @var_name, :objset => temp_iterator_objset
|
412
|
+
@block.statements = [assignment, @block.statements].flatten
|
413
|
+
|
414
|
+
vars_written_to = Set[]
|
415
|
+
vars_read = Set[]
|
416
|
+
vars_read_before_being_written_to = Set[]
|
417
|
+
context.on_var_write do |name|
|
418
|
+
vars_written_to << name
|
419
|
+
end
|
420
|
+
context.on_var_read do |name|
|
421
|
+
var_node, var = context.lookup_var name, false
|
422
|
+
vars_read_before_being_written_to << name unless
|
423
|
+
vars_written_to.include?(name) or vars_read_before_being_written_to.include? name
|
424
|
+
vars_read << name unless vars_read.include? name
|
425
|
+
end
|
426
|
+
|
427
|
+
context.push_frame
|
428
|
+
block = @block.typecheck_and_resolve context
|
429
|
+
context.pop_frame
|
430
|
+
|
431
|
+
vars_read_before_being_written_to.each do |var_name|
|
432
|
+
vars_read_before_being_written_to.delete var_name unless vars_written_to.include? var_name
|
433
|
+
end
|
434
|
+
|
435
|
+
flat = true
|
436
|
+
# flat = false unless vars_read_before_being_written_to.empty?
|
437
|
+
|
438
|
+
if flat
|
439
|
+
for_each = DS::DSFlatForEach.new :objset => objset, :block => block
|
440
|
+
else
|
441
|
+
for_each = DS::DSForEach.new :objset => objset, :block => block
|
442
|
+
end
|
443
|
+
|
444
|
+
vars_read_before_being_written_to.each do |var_name|
|
445
|
+
before_var_node, before_var = before_context.lookup_var var_name, false
|
446
|
+
inside_var_node, inside_var = context.lookup_var var_name, false
|
447
|
+
lambda_objset = DS::DSForEachPreLambdaObjset.new :for_each => for_each, :before_var => before_var, :inside_var => inside_var
|
448
|
+
var = DS::DSVariable.new :name => var_name, :type => before_var.type
|
449
|
+
assignment = DS::DSAssignment.new :var => var, :objset => lambda_objset
|
450
|
+
block.replace before_var, var
|
451
|
+
block.statements.unshift assignment
|
452
|
+
end
|
453
|
+
|
454
|
+
iterator_objset = DS::DSForEachIteratorObjset.new :for_each => for_each
|
455
|
+
block.replace temp_iterator_objset, iterator_objset
|
456
|
+
return for_each
|
457
|
+
end
|
458
|
+
|
459
|
+
def list_creations
|
460
|
+
@block.list_creations
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
class ADSLEither < ADSLNode
|
465
|
+
node_type :blocks
|
466
|
+
|
467
|
+
def typecheck_and_resolve(context)
|
468
|
+
context.push_frame
|
469
|
+
contexts = [context]
|
470
|
+
(1..@blocks.length-1).each do |i|
|
471
|
+
contexts << context.dup
|
472
|
+
end
|
473
|
+
|
474
|
+
blocks = []
|
475
|
+
@blocks.length.times do |i|
|
476
|
+
blocks << @blocks[i].typecheck_and_resolve(contexts[i])
|
477
|
+
end
|
478
|
+
|
479
|
+
contexts.each do |context|
|
480
|
+
context.pop_frame
|
481
|
+
end
|
482
|
+
|
483
|
+
either = DS::DSEither.new :blocks => blocks
|
484
|
+
|
485
|
+
lambdas = []
|
486
|
+
ADSLTypecheckResolveContext::context_vars_that_differ(*contexts).each do |var_name, vars|
|
487
|
+
var = DS::DSVariable.new :name => var_name, :type => vars.first.type
|
488
|
+
objset = DS::DSEitherLambdaObjset.new :either => either, :vars => vars
|
489
|
+
assignment = DS::DSAssignment.new :var => var, :objset => objset
|
490
|
+
context.redefine_var var, nil
|
491
|
+
lambdas << assignment
|
492
|
+
end
|
493
|
+
|
494
|
+
return [ either, lambdas ]
|
495
|
+
end
|
496
|
+
|
497
|
+
def list_entity_classes_written_to
|
498
|
+
@blocks.map{ block.list_entity_classes_written_to }.flatten
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
class ADSLDeleteObj < ADSLNode
|
503
|
+
node_type :objset
|
504
|
+
|
505
|
+
def typecheck_and_resolve(context)
|
506
|
+
objset = @objset.typecheck_and_resolve context
|
507
|
+
return DS::DSDeleteObj.new :objset => objset
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
def self.find_relation(context, from_type, rel_name, lineno, to_type=nil)
|
512
|
+
iter = from_type
|
513
|
+
relation_node, relation = context.relations[iter.name][rel_name]
|
514
|
+
while relation.nil?
|
515
|
+
iter = iter.parent
|
516
|
+
raise ADSLError, "Unknown relation #{from_type.name}.#{rel_name} on line #{lineno}" if iter.nil?
|
517
|
+
relation_node, relation = context.relations[iter.name][rel_name]
|
518
|
+
end
|
519
|
+
|
520
|
+
unless to_type.nil?
|
521
|
+
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
|
522
|
+
end
|
523
|
+
|
524
|
+
relation
|
525
|
+
end
|
526
|
+
|
527
|
+
class ADSLCreateTup < ADSLNode
|
528
|
+
node_type :objset1, :rel_name, :objset2
|
529
|
+
|
530
|
+
def typecheck_and_resolve(context)
|
531
|
+
objset1 = @objset1.typecheck_and_resolve context
|
532
|
+
objset2 = @objset2.typecheck_and_resolve context
|
533
|
+
relation = ADSL::find_relation context, objset1.type, @rel_name.text, @rel_name.lineno, objset2.type
|
534
|
+
return DS::DSCreateTup.new :objset1 => objset1, :relation => relation, :objset2 => objset2
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
class ADSLDeleteTup < ADSLNode
|
539
|
+
node_type :objset1, :rel_name, :objset2
|
540
|
+
|
541
|
+
def typecheck_and_resolve(context)
|
542
|
+
objset1 = @objset1.typecheck_and_resolve context
|
543
|
+
objset2 = @objset2.typecheck_and_resolve context
|
544
|
+
relation = ADSL::find_relation context, objset1.type, @rel_name.text, @rel_name.lineno, objset2.type
|
545
|
+
return DS::DSDeleteTup.new :objset1 => objset1, :relation => relation, :objset2 => objset2
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
class ADSLAllOf < ADSLNode
|
550
|
+
node_type :class_name
|
551
|
+
|
552
|
+
def typecheck_and_resolve(context)
|
553
|
+
klass_node, klass = context.classes[@class_name.text]
|
554
|
+
raise ADSLError, "Unknown class name #{@class_name.text} on line #{@class_name.lineno}" if klass.nil?
|
555
|
+
return DS::DSAllOf.new :klass => klass
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
class ADSLSubset < ADSLNode
|
560
|
+
node_type :objset
|
561
|
+
|
562
|
+
def typecheck_and_resolve(context)
|
563
|
+
objset = @objset.typecheck_and_resolve context
|
564
|
+
return DS::DSSubset.new :objset => objset
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
class ADSLOneOf < ADSLNode
|
569
|
+
node_type :objset
|
570
|
+
|
571
|
+
def typecheck_and_resolve(context)
|
572
|
+
objset = @objset.typecheck_and_resolve context
|
573
|
+
return DS::DSOneOf.new :objset => objset
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
class ADSLVariable < ADSLNode
|
578
|
+
node_type :var_name
|
579
|
+
|
580
|
+
def typecheck_and_resolve(context)
|
581
|
+
var_node, var = context.lookup_var @var_name.text
|
582
|
+
raise ADSLError, "Undefined variable #{@var_name.text} on line #{@var_name.lineno}" if var.nil?
|
583
|
+
return var
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
class ADSLDereference < ADSLNode
|
588
|
+
node_type :objset, :rel_name
|
589
|
+
|
590
|
+
def typecheck_and_resolve(context)
|
591
|
+
objset = @objset.typecheck_and_resolve context
|
592
|
+
klass = objset.type
|
593
|
+
relation = ADSL::find_relation context, objset.type, @rel_name.text, @rel_name.lineno
|
594
|
+
return DS::DSDereference.new :objset => objset, :relation => relation
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
class ADSLInvariant < ADSLNode
|
599
|
+
node_type :name, :formula
|
600
|
+
|
601
|
+
def typecheck_and_resolve(context)
|
602
|
+
formula = @formula.typecheck_and_resolve context
|
603
|
+
name = @name.nil? ? nil : @name.text
|
604
|
+
return DS::DSInvariant.new :name => name, :formula => formula
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
class ADSLBoolean < ADSLNode
|
609
|
+
node_type :bool_value
|
610
|
+
|
611
|
+
def typecheck_and_resolve(context)
|
612
|
+
return DS::DSBoolean::TRUE if @bool_value
|
613
|
+
return DS::DSBoolean::FALSE
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
class ADSLForAll < ADSLNode
|
618
|
+
node_type :vars, :subformula
|
619
|
+
|
620
|
+
def typecheck_and_resolve(context)
|
621
|
+
context.in_stack_frame do
|
622
|
+
vars = []
|
623
|
+
objsets = []
|
624
|
+
@vars.each do |var_node, objset_node|
|
625
|
+
objset = objset_node.typecheck_and_resolve context
|
626
|
+
|
627
|
+
var = DS::DSVariable.new :name => var_node.text, :type => objset.type
|
628
|
+
context.define_var var, var_node
|
629
|
+
|
630
|
+
vars << var
|
631
|
+
objsets << objset
|
632
|
+
end
|
633
|
+
subformula = @subformula.typecheck_and_resolve context
|
634
|
+
return DS::DSForAll.new :vars => vars, :objsets => objsets, :subformula => subformula
|
635
|
+
end
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
class ADSLExists < ADSLNode
|
640
|
+
node_type :vars, :subformula
|
641
|
+
|
642
|
+
def typecheck_and_resolve(context)
|
643
|
+
context.in_stack_frame do
|
644
|
+
vars = []
|
645
|
+
objsets = []
|
646
|
+
@vars.each do |var_node, objset_node|
|
647
|
+
objset = objset_node.typecheck_and_resolve context
|
648
|
+
|
649
|
+
var = DS::DSVariable.new :name => var_node.text, :type => objset.type
|
650
|
+
context.define_var var, var_node
|
651
|
+
|
652
|
+
vars << var
|
653
|
+
objsets << objset
|
654
|
+
end
|
655
|
+
subformula = @subformula.nil? ? nil : @subformula.typecheck_and_resolve(context)
|
656
|
+
return DS::DSExists.new :vars => vars, :objsets => objsets, :subformula => subformula
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
class ADSLNot < ADSLNode
|
662
|
+
node_type :subformula
|
663
|
+
|
664
|
+
def typecheck_and_resolve(context)
|
665
|
+
subformula = @subformula.typecheck_and_resolve context
|
666
|
+
raise "Substatement not a formula on line #{@subformula.lineno}" unless subformula.type == :formula
|
667
|
+
return subformula.subformula if subformula.is_a? DS::DSNot
|
668
|
+
return DS::DSNot.new :subformula => subformula
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
class ADSLAnd < ADSLNode
|
673
|
+
node_type :subformulae
|
674
|
+
|
675
|
+
def typecheck_and_resolve(context)
|
676
|
+
subformulae = @subformulae.map{ |o| o.typecheck_and_resolve context }
|
677
|
+
subformulae.each do |subformula|
|
678
|
+
raise "Substatement not a formula on line #{subformula.lineno}" unless subformula.type == :formula
|
679
|
+
end
|
680
|
+
flattened_subformulae = []
|
681
|
+
subformulae.each do |subformula|
|
682
|
+
if subformula.is_a? DS::DSAnd
|
683
|
+
flattened_subformulae += subformula.subformulae
|
684
|
+
else
|
685
|
+
flattened_subformulae << subformula
|
686
|
+
end
|
687
|
+
end
|
688
|
+
return DS::DSAnd.new :subformulae => flattened_subformulae
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
class ADSLOr < ADSLNode
|
693
|
+
node_type :subformulae
|
694
|
+
|
695
|
+
def typecheck_and_resolve(context)
|
696
|
+
subformulae = @subformulae.map{ |o| o.typecheck_and_resolve context }
|
697
|
+
subformulae.each do |subformula|
|
698
|
+
raise "Substatement not a formula on line #{subformula.lineno}" unless subformula.type == :formula
|
699
|
+
end
|
700
|
+
flattened_subformulae = []
|
701
|
+
subformulae.each do |subformula|
|
702
|
+
if subformula.is_a? DS::DSOr
|
703
|
+
flattened_subformulae += subformula.subformulae
|
704
|
+
else
|
705
|
+
flattened_subformulae << subformula
|
706
|
+
end
|
707
|
+
end
|
708
|
+
return DS::DSOr.new :subformulae => flattened_subformulae
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
class ADSLEquiv < ADSLNode
|
713
|
+
node_type :subformulae
|
714
|
+
|
715
|
+
def typecheck_and_resolve(context)
|
716
|
+
subformulae = @subformulae.map{ |o| o.typecheck_and_resolve context }
|
717
|
+
subformulae.each do |subformula|
|
718
|
+
raise "Substatement not a formula on line #{subformula.lineno}" unless subformula.type == :formula
|
719
|
+
end
|
720
|
+
return DS::DSEquiv.new :subformulae => subformulae
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
class ADSLImplies < ADSLNode
|
725
|
+
node_type :subformula1, :subformula2
|
726
|
+
|
727
|
+
def typecheck_and_resolve(context)
|
728
|
+
subformula1 = @subformula1.typecheck_and_resolve context
|
729
|
+
subformula2 = @subformula2.typecheck_and_resolve context
|
730
|
+
|
731
|
+
[subformula1, subformula2].each do |subformula|
|
732
|
+
raise "Substatement not a formula on line #{subformula.lineno}" unless subformula.type == :formula
|
733
|
+
end
|
734
|
+
return DS::DSImplies.new :subformula1 => subformula1, :subformula2 => subformula2
|
735
|
+
end
|
736
|
+
end
|
737
|
+
|
738
|
+
class ADSLEqual < ADSLNode
|
739
|
+
node_type :objsets
|
740
|
+
|
741
|
+
def typecheck_and_resolve(context)
|
742
|
+
objsets = @objsets.map{ |o| o.typecheck_and_resolve context }
|
743
|
+
|
744
|
+
types = objsets.map{ |o| o.type }.uniq
|
745
|
+
while types.length > 1
|
746
|
+
type1 = types.pop
|
747
|
+
type2 = types.pop
|
748
|
+
if type1.superclass_of? type2
|
749
|
+
types << type2
|
750
|
+
elsif type2.superclass_of? type1
|
751
|
+
types << type1
|
752
|
+
else
|
753
|
+
raise ADSLError, "Object sets are not of compatible types: #{objsets.map { |o| o.type.name }.join(", ")}"
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
return DS::DSEqual.new :objsets => objsets
|
758
|
+
end
|
759
|
+
end
|
760
|
+
|
761
|
+
class ADSLIn < ADSLNode
|
762
|
+
node_type :objset1, :objset2
|
763
|
+
|
764
|
+
def typecheck_and_resolve(context)
|
765
|
+
objset1 = @objset1.typecheck_and_resolve context
|
766
|
+
objset2 = @objset2.typecheck_and_resolve context
|
767
|
+
raise ADSLError, "Object sets are not of compatible types: #{objset1.type.name}, #{objset2.type.name}" unless objset2.type.superclass_of? objset1.type
|
768
|
+
return DS::DSIn.new :objset1 => objset1, :objset2 => objset2
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
772
|
+
class ADSLEmpty < ADSLNode
|
773
|
+
node_type :objset
|
774
|
+
|
775
|
+
def typecheck_and_resolve(context)
|
776
|
+
objset = @objset.typecheck_and_resolve context
|
777
|
+
return DS::DSEmpty.new :objset => objset
|
778
|
+
end
|
779
|
+
end
|
780
|
+
end
|