falluto 0.0.1 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,410 @@
1
+ require 'set'
2
+
3
+ require 'rubygems' # to load treetop
4
+ require 'treetop' # to build the parser
5
+
6
+ require 'falluto/grammar'
7
+ require 'falluto/nusmv'
8
+ require 'falluto/ruby_extensions'
9
+ require 'falluto/symboltable'
10
+
11
+ class UndeclaredFault < Exception ; end
12
+ class RedefinedFault < Exception ; end
13
+
14
+ class CompilerContext
15
+ attr_reader :current_module, :modules
16
+ attr_accessor :variable
17
+
18
+ def initialize
19
+ @modules = SymbolTable.new
20
+ end
21
+
22
+ def <<(m)
23
+ @modules << m
24
+ @current_module = m
25
+ end
26
+
27
+ def [](type)
28
+ @modules[type]
29
+ end
30
+
31
+ def main_module
32
+ @modules['main']
33
+ end
34
+
35
+ end
36
+
37
+
38
+ class Compiler
39
+
40
+ attr_reader :input_string, :compiled_string, :parsed_tree, :specs
41
+
42
+ def initialize
43
+ @parser = Falluto::GrammarParser.new
44
+ #@parser.root = "Choose your favorite rule"
45
+ @generator = Falluto::NuSMV::CodeGenerator.new
46
+ @context = CompilerContext.new
47
+ @specs = []
48
+ end
49
+
50
+ def run input
51
+ @input_string = input
52
+ @parsed_tree = @parser.parse @input_string
53
+ if @parsed_tree
54
+ @compiled_string = @parsed_tree.send :compile, self
55
+ end
56
+ end
57
+
58
+ def auxiliar_variables
59
+ variables = Set.new
60
+
61
+ each_module do |m|
62
+ m.each_fault do |f|
63
+ f.each_auxiliar_variable do |v|
64
+ variables << v.name
65
+ end
66
+ variables << f.precondition_variable
67
+ variables << f.restore_variable
68
+ variables << f.fairness_variable
69
+ end
70
+ end
71
+
72
+ variables.sort
73
+ end
74
+
75
+ def each_module &block
76
+ @context.modules.each{|m| yield m}
77
+ end
78
+
79
+ def compile node
80
+ method = nil
81
+ begin
82
+ method = "compile_#{node.class.human_name}"
83
+ #puts "compiling with #{method} '#{node.text}'"
84
+ send method, node
85
+ rescue => e
86
+ puts e.message
87
+ puts e.backtrace
88
+ raise "[ERROR] Don't know how to compile #{node.class} (#{node.text})"
89
+ end
90
+ end
91
+
92
+ # Recursively compile a syntax node descending into its elements.
93
+ def compile_treetop_runtime_syntax_node node
94
+ if node.elements
95
+ node.elements.map{|element|
96
+ # puts "Invoking on #{element.class}"
97
+ compile element}.to_s
98
+ else
99
+ node.text
100
+ end
101
+ end
102
+
103
+ def compile_falluto_module_declaration_node node
104
+ m = Falluto::NuSMV::Module.new node.name
105
+ @context << m
106
+
107
+ result = "MODULE " + node.signature + "\n"
108
+ result << compile(node.declarations)
109
+ if @context.current_module.has_faults?
110
+ result << dump_faulty_module_declarations
111
+ result << dump_system_effect_declaration
112
+ end
113
+ result
114
+ end
115
+
116
+ # Create a new fault object for this declaration.
117
+ # No code is output for this construct.
118
+ #
119
+ def compile_falluto_fault_declaration_node node
120
+ modname = @context.current_module.name
121
+ name = node.name
122
+ precondition = node.precondition
123
+ restores = node.restores
124
+ effect = node.effect
125
+
126
+ f = Falluto::NuSMV::Fault.new modname, name, precondition, restores, effect
127
+ raise RedefinedFault.new(f.name) if @context.current_module.is_defined? f
128
+ @context.current_module.add_fault f
129
+ ''
130
+ end
131
+
132
+ # Compile the following construct
133
+ #
134
+ # next(v) := val disabled_by {f,g};
135
+ #
136
+ # into
137
+ #
138
+ # next(v) :=
139
+ # case
140
+ # (1) & !f.active & !g.active : compile(val);
141
+ # 1 : v;
142
+ # esac;
143
+ #
144
+ def compile_falluto_fault_assignment_node node
145
+ variable = node.variable
146
+ value = node.value
147
+ faults = @context.current_module.get_faults *node.faults
148
+ raise UndeclaredFault.new fname if faults.any?{ |f| f.nil? }
149
+ @context.variable = variable
150
+
151
+ new_condition = build_condition_with_faults "1", faults
152
+
153
+ <<-END
154
+ next(#{@context.variable}) :=
155
+ case
156
+ #{new_condition} : #{compile value};
157
+ 1 : #{@context.variable};
158
+ esac;
159
+ END
160
+
161
+ end
162
+
163
+ # Compile the following construct
164
+ #
165
+ # next(v) := val;
166
+ #
167
+ # into
168
+ #
169
+ # next(v) := compile(val);
170
+ #
171
+ # so that we can propagate fault compilation into the value.
172
+ # (when val is a 'case' construct)
173
+ #
174
+ def compile_falluto_assignment_node node
175
+ variable = node.variable
176
+ value = node.value
177
+ @context.variable = variable
178
+ %Q| next(#{@context.variable}) := #{compile value};\n|
179
+ end
180
+
181
+ # Compile the following construct
182
+ #
183
+ # next(v) :=
184
+ # case
185
+ # c_1 : v_1 disabled_by {f, g};
186
+ # ...
187
+ # c_n : v_n;
188
+ # esac;
189
+ #
190
+ # into
191
+ #
192
+ # next(v) :=
193
+ # case
194
+ # (c_1) & !f.active & !g.active : compile(v1);
195
+ # ...
196
+ # c_n : v_n;
197
+ # 1 : v;
198
+ # esac;
199
+ #
200
+ def compile_falluto_case_node node
201
+ new_cases = []
202
+ has_faults = false
203
+ node.each_case do |c|
204
+ has_faults ||= c.has_faults?
205
+ new_cases << c.compile(self)
206
+ end
207
+ new_cases << " 1 : #{@context.variable};\n" if has_faults
208
+
209
+ "case\n" + new_cases.join('') + "esac"
210
+ end
211
+
212
+ # Compile the folllowing construct
213
+ #
214
+ # c : v disabled_by {f,g};
215
+ #
216
+ # into
217
+ #
218
+ # (c) & !f.active & !g.active : v;
219
+ #
220
+ # if the construct is not disabled by faults,
221
+ # then the input code is unchanged.
222
+ #
223
+ def compile_falluto_case_element_node node
224
+ condition = node.condition
225
+ value = node.value
226
+
227
+ if node.has_faults?
228
+ faults = @context.current_module.get_faults *node.faults
229
+ raise UndeclaredFault.new fname if faults.any?{ |f| f.nil? }
230
+ new_condition = build_condition_with_faults condition, faults
231
+ add_condition_to_faults condition, faults
232
+ "#{new_condition} : #{value};\n"
233
+ else
234
+ "#{condition} : #{value};\n"
235
+ end
236
+ end
237
+
238
+ # Compile a variable will output the original code.
239
+ # However, we also need to track variables and their types
240
+ # to modify the model specifications.
241
+ def compile_falluto_var_decl_node node
242
+ v = Falluto::NuSMV::Variable.new node.name, node.vartype
243
+ @context.current_module.add_variable v
244
+ compile_treetop_runtime_syntax_node node
245
+ end
246
+
247
+ # Compile the following construct
248
+ #
249
+ # LTLSPEC s
250
+ #
251
+ # into
252
+ #
253
+ # LTLSPEC ((faults and processes are fair) -> (s))
254
+ #
255
+ def compile_falluto_ltl_spec_node node
256
+ spec = node.specification
257
+ sf_list = ['1']
258
+
259
+ @context.main_module.each_variable do |variable|
260
+ type = @context[variable.type]
261
+ # NOTE nusmvmodule will be nil if type is undeclared
262
+ sf = dump_module_strong_fairness(type, variable.name) unless type.nil?
263
+ sf_list << sf if sf
264
+ end
265
+
266
+ @specs << spec
267
+
268
+ "LTLSPEC (( #{sf_list.join(" & ")} ) -> #{spec})"
269
+ end
270
+
271
+ def no_fault_active faults
272
+ faults.collect{|f| "! #{f.name}.#{f.active_variable}"}.join(' & ')
273
+ end
274
+
275
+ def build_condition_with_faults condition, faults
276
+ "(#{condition}) & #{no_fault_active faults}"
277
+ end
278
+
279
+ def add_condition_to_faults condition, faults
280
+ faults.each { |fault| fault.add_auxiliar_variable condition }
281
+ end
282
+
283
+ def dump_faulty_module_declarations
284
+ decls = ''
285
+
286
+ @context.current_module.each_fault do |fault|
287
+ decls << "-- Begin declarations for (#{fault.name})\n"
288
+ decls << declare_fault_precondition(fault)
289
+ decls << declare_fault_restore(fault)
290
+ decls << declare_fault_instance(fault)
291
+ decls << declare_auxiliar_variables(fault)
292
+ decls << declare_fault_fairness(fault)
293
+ decls << "-- End declarations for (#{fault.name})\n"
294
+ end
295
+
296
+ decls << "FAIRNESS running\n" if @context.current_module.has_faults?
297
+ decls
298
+ end
299
+
300
+ def dump_system_effect_declaration
301
+ decls = ''
302
+
303
+ @context.current_module.each_fault do |fault|
304
+ name = @context.current_module.name
305
+ decls << "-- Begin system effect for (#{name}, #{fault.name})\n"
306
+ decls << declare_sytem_effect(fault)
307
+ decls << "-- End system effect for (#{name}, #{fault.name})\n"
308
+ end
309
+
310
+ decls
311
+ end
312
+
313
+ # NOTE: Can we return true when the module has no faults?
314
+ def dump_module_strong_fairness m, inst
315
+ sf = []
316
+ m.each_fault{ |f| sf << f.strong_fairness(inst) }
317
+
318
+ if sf.empty?
319
+ nil
320
+ else
321
+ "(#{sf.join(" & ")})"
322
+ end
323
+ end
324
+
325
+ def declare_fault_precondition fault
326
+ %Q|DEFINE #{fault.precondition_variable} := (#{fault.precondition});\n|
327
+ end
328
+
329
+ def declare_fault_restore fault
330
+ %Q|DEFINE #{fault.restore_variable} := (#{fault.restore});\n|
331
+ end
332
+
333
+ def declare_fault_instance fault
334
+ %Q|VAR #{fault.name} : process #{fault.instance_signature};\n|
335
+ end
336
+
337
+ def declare_auxiliar_variables fault
338
+ str = ''
339
+ fault.each_auxiliar_variable do |v|
340
+ str << %Q|DEFINE #{v.name} := (#{v.condition});\n|
341
+ end
342
+ str
343
+ end
344
+
345
+ def declare_fault_fairness fault
346
+ fv = fault.fairness_variable
347
+ result =<<-END_DECL
348
+ VAR #{fv} : boolean;
349
+ ASSIGN
350
+ next(#{fv}) :=
351
+ case
352
+ #{fault.fairness_condition} & !#{fv} : 1;
353
+ 1 : 0;
354
+ esac;
355
+ END_DECL
356
+ end
357
+
358
+ def declare_sytem_effect fault
359
+ # The system effect is a module which takes all variables affected by
360
+ # faults as parameters and modifies them when the fault occurs.
361
+
362
+ declare_fault_module(fault) +
363
+ declare_affected_variables_modifications(fault) +
364
+ "FAIRNESS running\n"
365
+ end
366
+
367
+ def declare_fault_module fault
368
+ instance_signature = fault.instance_signature
369
+ active = fault.active_variable
370
+ pre = fault.precondition_variable
371
+ restore = fault.restore_variable
372
+
373
+ <<-END_DECL
374
+ MODULE #{instance_signature}
375
+ VAR #{active} : boolean;
376
+ ASSIGN
377
+ next(#{active}) :=
378
+ case
379
+ #{pre} & !#{active} : {0, 1};
380
+ #{active} & next(#{restore}) : 0;
381
+ 1 : #{active};
382
+ esac;
383
+ END_DECL
384
+ end
385
+
386
+ def declare_affected_variables_modifications fault
387
+ decls = ''
388
+
389
+ fault.each_affected_variable_with_effect do |v, effect|
390
+ decls << declare_affected_variable_modification(fault, v, effect)
391
+ end
392
+
393
+ decls
394
+ end
395
+
396
+ def declare_affected_variable_modification fault, variable, effect
397
+ active = fault.active_variable
398
+
399
+ <<-END_DECL
400
+ next(#{variable}) :=
401
+ case
402
+ !#{active} & next(#{active}) : #{effect};
403
+ 1 : #{variable};
404
+ esac;
405
+ END_DECL
406
+ end
407
+
408
+
409
+ end
410
+
@@ -0,0 +1,141 @@
1
+ class Treetop::Runtime::SyntaxNode
2
+ alias text text_value
3
+
4
+ def stripped
5
+ text_value.strip
6
+ end
7
+
8
+ def compile generator
9
+ generator.compile self
10
+ end
11
+ end
12
+
13
+ module Falluto
14
+ class ModuleDeclarationNode < Treetop::Runtime::SyntaxNode
15
+ def name
16
+ module_sign.name.stripped
17
+ end
18
+
19
+ def signature
20
+ module_sign.stripped
21
+ end
22
+
23
+ def declarations
24
+ decls
25
+ end
26
+
27
+ end
28
+
29
+ class FaultDeclarationNode < Treetop::Runtime::SyntaxNode
30
+ def name
31
+ id.stripped
32
+ end
33
+
34
+ def precondition
35
+ fault_pre.simple_expression.stripped
36
+ end
37
+
38
+ def restores
39
+ fault_restores.next_expression.stripped
40
+ end
41
+
42
+ def effect
43
+ result = Hash.new
44
+
45
+ each_effect do |effect_expression|
46
+ var = effect_expression.var_id.stripped
47
+ effect = effect_expression.simple_expression.stripped
48
+ result[var] = effect
49
+ end
50
+
51
+ result
52
+ end
53
+
54
+ def each_effect
55
+ first = fault_effect.list.first
56
+ yield first unless first.text.empty?
57
+ fault_effect.list.rest.elements.each {|e| yield e.fault_effect_expression}
58
+ end
59
+ end
60
+
61
+ class FaultAssignmentNode < Treetop::Runtime::SyntaxNode
62
+ def variable
63
+ var_id.stripped
64
+ end
65
+
66
+ def value
67
+ basic_expr
68
+ end
69
+
70
+ def faults
71
+ [list.first.stripped] + list.rest.elements.map{|e| e.fault.stripped}
72
+ end
73
+ end
74
+
75
+ class AssignmentNode < Treetop::Runtime::SyntaxNode
76
+ def variable
77
+ var_id.stripped
78
+ end
79
+
80
+ def value
81
+ basic_expr
82
+ end
83
+ end
84
+
85
+ class CaseNode < Treetop::Runtime::SyntaxNode
86
+ def each_case &block
87
+ cases.elements.each &block
88
+ end
89
+ end
90
+
91
+ class CaseElementNode < Treetop::Runtime::SyntaxNode
92
+ def condition
93
+ left.stripped
94
+ end
95
+
96
+ def value
97
+ right.stripped
98
+ end
99
+
100
+ def faults
101
+ result = []
102
+
103
+ if has_faults?
104
+ list = disabled_by.list
105
+ result << list.first.stripped
106
+ list.rest.elements.inject(result) do |acc, node|
107
+ acc << node.fault.stripped
108
+ end
109
+ end
110
+
111
+ result
112
+ end
113
+
114
+ def has_faults?
115
+ not disabled_by.elements.nil?
116
+ end
117
+
118
+ end
119
+
120
+ class LtlSpecNode < Treetop::Runtime::SyntaxNode
121
+ def specification
122
+ spec.stripped
123
+ end
124
+ end
125
+
126
+ class VarDeclNode < Treetop::Runtime::SyntaxNode
127
+ def name
128
+ decl_var_id.stripped
129
+ end
130
+
131
+ def vartype
132
+ begin
133
+ t = type.module_type.name.stripped
134
+ rescue NoMethodError
135
+ t = type.stripped
136
+ end
137
+ end
138
+
139
+ end
140
+ end
141
+