falluto 0.0.1 → 0.0.9
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/bin/cleantrace.rb +97 -0
- data/bin/falluto.rb +92 -2
- data/bin/ftnusmv.rb +96 -0
- data/bin/viewtrace.rb +162 -0
- data/lib/falluto/compiler.rb +410 -0
- data/lib/falluto/grammar/nodes.rb +141 -0
- data/lib/falluto/grammar/rules.treetop +478 -0
- data/lib/falluto/grammar/tokens.treetop +145 -0
- data/lib/falluto/grammar.rb +7 -0
- data/lib/falluto/nusmv/codegen.rb +39 -0
- data/lib/falluto/nusmv/fault.rb +87 -0
- data/lib/falluto/nusmv/module.rb +45 -0
- data/lib/falluto/nusmv/variable.rb +13 -0
- data/lib/falluto/nusmv.rb +4 -0
- data/lib/falluto/ruby_extensions/class.rb +10 -0
- data/lib/falluto/ruby_extensions/file.rb +5 -0
- data/lib/falluto/ruby_extensions/hash.rb +3 -0
- data/lib/falluto/ruby_extensions/string.rb +5 -0
- data/lib/falluto/ruby_extensions.rb +4 -0
- data/lib/falluto/symboltable.rb +34 -0
- data/lib/falluto/version.rb +10 -0
- data/spec/grammar/parser_spec.rb +121 -0
- data/spec/nusmv/fault_spec.rb +17 -0
- data/spec/nusmv/generator_spec.rb +42 -0
- data/spec/nusmv/module_spec.rb +12 -0
- data/spec/nusmv/variable_spec.rb +32 -0
- data/spec/spec_helper.rb +4 -0
- metadata +35 -14
- data/.document +0 -5
- data/.gitignore +0 -21
- data/Rakefile +0 -56
- data/VERSION +0 -1
- data/test/helper.rb +0 -10
- data/test/test_falluto.rb +0 -7
@@ -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
|
+
|