stamina-core 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +78 -0
- data/LICENCE.md +22 -0
- data/lib/stamina-core/stamina-core.rb +1 -0
- data/lib/stamina-core/stamina/adl.rb +298 -0
- data/lib/stamina-core/stamina/automaton.rb +1300 -0
- data/lib/stamina-core/stamina/automaton/complement.rb +26 -0
- data/lib/stamina-core/stamina/automaton/complete.rb +36 -0
- data/lib/stamina-core/stamina/automaton/compose.rb +111 -0
- data/lib/stamina-core/stamina/automaton/determinize.rb +104 -0
- data/lib/stamina-core/stamina/automaton/equivalence.rb +57 -0
- data/lib/stamina-core/stamina/automaton/hide.rb +41 -0
- data/lib/stamina-core/stamina/automaton/metrics.rb +77 -0
- data/lib/stamina-core/stamina/automaton/minimize.rb +23 -0
- data/lib/stamina-core/stamina/automaton/minimize/hopcroft.rb +118 -0
- data/lib/stamina-core/stamina/automaton/minimize/pitchies.rb +130 -0
- data/lib/stamina-core/stamina/automaton/strip.rb +16 -0
- data/lib/stamina-core/stamina/automaton/walking.rb +361 -0
- data/lib/stamina-core/stamina/command.rb +38 -0
- data/lib/stamina-core/stamina/command/adl2dot.rb +82 -0
- data/lib/stamina-core/stamina/command/help.rb +23 -0
- data/lib/stamina-core/stamina/command/robustness.rb +21 -0
- data/lib/stamina-core/stamina/command/run.rb +84 -0
- data/lib/stamina-core/stamina/core.rb +11 -0
- data/lib/stamina-core/stamina/dsl.rb +6 -0
- data/lib/stamina-core/stamina/dsl/automata.rb +23 -0
- data/lib/stamina-core/stamina/dsl/core.rb +14 -0
- data/lib/stamina-core/stamina/engine.rb +32 -0
- data/lib/stamina-core/stamina/engine/context.rb +35 -0
- data/lib/stamina-core/stamina/errors.rb +26 -0
- data/lib/stamina-core/stamina/ext/math.rb +19 -0
- data/lib/stamina-core/stamina/loader.rb +3 -0
- data/lib/stamina-core/stamina/markable.rb +42 -0
- data/lib/stamina-core/stamina/utils.rb +1 -0
- data/lib/stamina-core/stamina/utils/decorate.rb +81 -0
- data/lib/stamina-core/stamina/version.rb +14 -0
- metadata +93 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
module Stamina
|
2
|
+
#
|
3
|
+
# Stamina - A Ruby Automaton & Induction Toolkit
|
4
|
+
#
|
5
|
+
# SYNOPSIS
|
6
|
+
# #{program_name} [--version] [--help] COMMAND [cmd opts] ARGS...
|
7
|
+
#
|
8
|
+
# OPTIONS
|
9
|
+
# #{summarized_options}
|
10
|
+
#
|
11
|
+
# COMMANDS
|
12
|
+
# #{summarized_subcommands}
|
13
|
+
#
|
14
|
+
# See '#{program_name} help COMMAND' for more information on a specific command.
|
15
|
+
#
|
16
|
+
class Command < ::Quickl::Delegator(__FILE__, __LINE__)
|
17
|
+
|
18
|
+
# Install options
|
19
|
+
options do |opt|
|
20
|
+
|
21
|
+
# Show the help and exit
|
22
|
+
opt.on_tail("--help", "Show help") do
|
23
|
+
raise Quickl::Help
|
24
|
+
end
|
25
|
+
|
26
|
+
# Show version and exit
|
27
|
+
opt.on_tail("--version", "Show version") do
|
28
|
+
raise Quickl::Exit, "#{program_name} #{VERSION} (c) 2010-2011, Bernard Lambeau"
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end # class Command
|
34
|
+
end # module Stamina
|
35
|
+
require_relative 'command/robustness'
|
36
|
+
require_relative 'command/help'
|
37
|
+
require_relative 'command/adl2dot'
|
38
|
+
require_relative 'command/run'
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Stamina
|
2
|
+
class Command
|
3
|
+
#
|
4
|
+
# Prints an automaton expressed in ADL in dot (or gif) format
|
5
|
+
#
|
6
|
+
# SYNOPSIS
|
7
|
+
# #{program_name} #{command_name} automaton.adl
|
8
|
+
#
|
9
|
+
# OPTIONS
|
10
|
+
# #{summarized_options}
|
11
|
+
#
|
12
|
+
class Adl2dot < Quickl::Command(__FILE__, __LINE__)
|
13
|
+
include Robustness
|
14
|
+
|
15
|
+
attr_reader :output_format
|
16
|
+
|
17
|
+
# Install options
|
18
|
+
options do |opt|
|
19
|
+
|
20
|
+
@output_file = nil
|
21
|
+
opt.on("-o", "--output=OUTPUT",
|
22
|
+
"Flush result output file") do |value|
|
23
|
+
@output_file = assert_writable_file(value)
|
24
|
+
end
|
25
|
+
|
26
|
+
@output_format = "dot"
|
27
|
+
opt.on("-g", "--gif",
|
28
|
+
"Generates a gif file instead of a dot one") do
|
29
|
+
@output_format = "gif"
|
30
|
+
end
|
31
|
+
|
32
|
+
opt.on("--png",
|
33
|
+
"Generates a png file instead of a dot one") do
|
34
|
+
@output_format = "png"
|
35
|
+
end
|
36
|
+
|
37
|
+
end # options
|
38
|
+
|
39
|
+
def output_file(infile)
|
40
|
+
@output_file || "#{File.basename(infile || 'stdin.adl', '.adl')}.#{output_format}"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Command execution
|
44
|
+
def execute(args)
|
45
|
+
raise Quickl::Help unless args.size <= 1
|
46
|
+
|
47
|
+
# Loads the target automaton
|
48
|
+
input = if args.size == 1
|
49
|
+
File.read assert_readable_file(args.first)
|
50
|
+
else
|
51
|
+
$stdin.readlines.join("\n")
|
52
|
+
end
|
53
|
+
|
54
|
+
begin
|
55
|
+
automaton = Stamina::ADL::parse_automaton(input)
|
56
|
+
rescue ADL::ParseError
|
57
|
+
sample = Stamina::ADL::parse_sample(input)
|
58
|
+
automaton = sample.to_pta
|
59
|
+
end
|
60
|
+
|
61
|
+
# create a file for the dot output
|
62
|
+
if output_format == 'dot'
|
63
|
+
dotfile = output_file(args.first)
|
64
|
+
else
|
65
|
+
require 'tempfile'
|
66
|
+
dotfile = Tempfile.new("stamina").path
|
67
|
+
end
|
68
|
+
|
69
|
+
# Flush automaton inside it
|
70
|
+
File.open(dotfile, 'w') do |f|
|
71
|
+
f << automaton.to_dot
|
72
|
+
end
|
73
|
+
|
74
|
+
# if gif output, use dot to convert it
|
75
|
+
unless output_format == 'dot'
|
76
|
+
`dot -T#{output_format} -o #{output_file(args.first)} #{dotfile}`
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end # class Adl2dot
|
81
|
+
end # class Command
|
82
|
+
end # module Stamina
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Stamina
|
2
|
+
class Command
|
3
|
+
#
|
4
|
+
# Show help about a specific command
|
5
|
+
#
|
6
|
+
# SYNOPSIS
|
7
|
+
# #{program_name} #{command_name} COMMAND
|
8
|
+
#
|
9
|
+
class Help < Quickl::Command(__FILE__, __LINE__)
|
10
|
+
|
11
|
+
# Let NoSuchCommandError be passed to higher stage
|
12
|
+
no_react_to Quickl::NoSuchCommand
|
13
|
+
|
14
|
+
# Command execution
|
15
|
+
def execute(args)
|
16
|
+
sup = Quickl.super_command(self)
|
17
|
+
sub = (args.size != 1) ? sup : Quickl.sub_command!(sup, args.first)
|
18
|
+
puts Quickl.help(sub)
|
19
|
+
end
|
20
|
+
|
21
|
+
end # class Help
|
22
|
+
end # class Command
|
23
|
+
end # module Stamina
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Stamina
|
2
|
+
class Command
|
3
|
+
module Robustness
|
4
|
+
|
5
|
+
# Checks that a given file is readable or raises a Quickl::IOAccessError
|
6
|
+
def assert_readable_file(file)
|
7
|
+
raise Quickl::IOAccessError, "File #{file} does not exists" unless File.exists?(file)
|
8
|
+
raise Quickl::IOAccessError, "File #{file} cannot be read" unless File.readable?(file)
|
9
|
+
file
|
10
|
+
end
|
11
|
+
|
12
|
+
# Checks that a given file is writable or raises a Quickl::IOAccessError
|
13
|
+
def assert_writable_file(file)
|
14
|
+
raise Quickl::IOAccessError, "File #{file} cannot be written" \
|
15
|
+
unless not(File.exists?(file)) or File.writable?(file)
|
16
|
+
file
|
17
|
+
end
|
18
|
+
|
19
|
+
end # module Robustness
|
20
|
+
end # class Command
|
21
|
+
end # module Stamina
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Stamina
|
2
|
+
class Command
|
3
|
+
#
|
4
|
+
# Run a file with the Stamina Engine
|
5
|
+
#
|
6
|
+
# SYNOPSIS
|
7
|
+
# stamina #{command_name} FILE
|
8
|
+
#
|
9
|
+
# OPTIONS
|
10
|
+
# #{summarized_options}
|
11
|
+
#
|
12
|
+
class Run < Quickl::Command(__FILE__, __LINE__)
|
13
|
+
include Robustness
|
14
|
+
|
15
|
+
attr_accessor :output
|
16
|
+
attr_accessor :images
|
17
|
+
|
18
|
+
# Install options
|
19
|
+
options do |opt|
|
20
|
+
|
21
|
+
@output = [:main]
|
22
|
+
opt.on("--output=x,y,z", Array,
|
23
|
+
"Output specified variables only") do |args|
|
24
|
+
self.output = args.collect{|v| v.to_sym}
|
25
|
+
end
|
26
|
+
opt.on("--all",
|
27
|
+
"Output all variables") do
|
28
|
+
self.output = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
@images = []
|
32
|
+
opt.on('--gif') do
|
33
|
+
self.images << :gif
|
34
|
+
end
|
35
|
+
opt.on('--png') do
|
36
|
+
self.images << :png
|
37
|
+
end
|
38
|
+
opt.on('--dot') do
|
39
|
+
self.images << :dot
|
40
|
+
end
|
41
|
+
|
42
|
+
end # options
|
43
|
+
|
44
|
+
# Command execution
|
45
|
+
def execute(args)
|
46
|
+
raise Quickl::Help unless args.size == 1
|
47
|
+
assert_readable_file(file = args.first)
|
48
|
+
context = Stamina::Engine.execute(File.read(file), file)
|
49
|
+
do_output(context, File.dirname(file))
|
50
|
+
context
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def do_output(context, dir)
|
56
|
+
self.output ||= context.vars
|
57
|
+
self.output.each do |varname|
|
58
|
+
varvalue = context[varname]
|
59
|
+
|
60
|
+
# textual output
|
61
|
+
puts "# #{varname} ###########################################"
|
62
|
+
puts varvalue.respond_to?(:to_adl) ? varvalue.to_adl : varvalue
|
63
|
+
puts
|
64
|
+
|
65
|
+
# image output
|
66
|
+
if varvalue.respond_to?(:to_dot)
|
67
|
+
output_images(varname, varvalue, dir)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def output_images(varname, varvalue, dir)
|
73
|
+
images.each do |format|
|
74
|
+
output_file = File.join(dir, "#{varname}.#{format}")
|
75
|
+
puts cmd = "dot -q -T#{format} -o#{output_file}"
|
76
|
+
IO::popen(cmd, "w") do |io|
|
77
|
+
io << varvalue.to_dot
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end # class Run
|
83
|
+
end # class Command
|
84
|
+
end # module Stamina
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require_relative 'version'
|
2
|
+
require_relative 'loader'
|
3
|
+
require_relative 'errors'
|
4
|
+
require_relative 'ext/math'
|
5
|
+
require_relative 'markable'
|
6
|
+
require_relative 'adl'
|
7
|
+
require_relative 'automaton'
|
8
|
+
require_relative 'utils'
|
9
|
+
require_relative 'dsl'
|
10
|
+
require_relative 'engine'
|
11
|
+
require_relative 'command'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Stamina
|
2
|
+
module Dsl
|
3
|
+
module Automata
|
4
|
+
|
5
|
+
#
|
6
|
+
# Coerces `arg` to an automaton
|
7
|
+
#
|
8
|
+
def automaton(arg)
|
9
|
+
Automaton.coerce(arg)
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Computes the synchronous composition of many automata
|
14
|
+
#
|
15
|
+
def compose(*args)
|
16
|
+
automata = args.collect{|a| automaton(a)}
|
17
|
+
Stamina::Automaton::Compose.execute(automata)
|
18
|
+
end
|
19
|
+
|
20
|
+
end # module Automata
|
21
|
+
include Automata
|
22
|
+
end # module Dsl
|
23
|
+
end # module Stamina
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Stamina
|
2
|
+
module Dsl
|
3
|
+
module Core
|
4
|
+
|
5
|
+
def assert(x, msg = nil)
|
6
|
+
raise Stamina::AssertionError,
|
7
|
+
"Assertion failed: #{msg || 'no message provided'}",
|
8
|
+
caller unless x
|
9
|
+
end
|
10
|
+
|
11
|
+
end # module Core
|
12
|
+
include Core
|
13
|
+
end # module Dsl
|
14
|
+
end # module Stamina
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative 'engine/context'
|
2
|
+
module Stamina
|
3
|
+
class Engine
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
extend(Stamina::Dsl)
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute_binding
|
10
|
+
binding
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(code, file = nil)
|
14
|
+
code = <<-EOF
|
15
|
+
main = begin
|
16
|
+
#{code}
|
17
|
+
end
|
18
|
+
Context.new(local_variables, binding)
|
19
|
+
EOF
|
20
|
+
if file
|
21
|
+
eval(code, execute_binding, file)
|
22
|
+
else
|
23
|
+
eval(code, execute_binding)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.execute(*args)
|
28
|
+
new.execute(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
end # class Engine
|
32
|
+
end # module Stamina
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Stamina
|
2
|
+
class Engine
|
3
|
+
class Context
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :vars, :binding
|
7
|
+
|
8
|
+
def initialize(vars, binding)
|
9
|
+
@vars = vars.collect{|v| v.to_sym}
|
10
|
+
@binding = binding
|
11
|
+
end
|
12
|
+
|
13
|
+
def each
|
14
|
+
vars.each do |key|
|
15
|
+
yield(key, self[key])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](name)
|
20
|
+
binding.eval(name.to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
Hash[collect{|k,v| [k,v]}]
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
collect{|k,v|
|
29
|
+
"#{k}: #{v}"
|
30
|
+
}.join("\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
end # class Context
|
34
|
+
end # class Engine
|
35
|
+
end # module Stamina
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Stamina
|
2
|
+
|
3
|
+
# Raised when an algorithm explicitely abords something
|
4
|
+
class Abord < StandardError; end
|
5
|
+
|
6
|
+
# Main class of all stamina errors.
|
7
|
+
class StaminaError < StandardError; end
|
8
|
+
|
9
|
+
# Raised when an assertion fails.
|
10
|
+
class AssertionError < StandardError; end
|
11
|
+
|
12
|
+
# Raised by samples implementations and other induction algorithms
|
13
|
+
# when a sample is inconsistent (same string labeled as being both
|
14
|
+
# positive and negative)
|
15
|
+
class InconsistencyError < StaminaError; end
|
16
|
+
|
17
|
+
# Specific errors of the ADL module.
|
18
|
+
module ADL
|
19
|
+
|
20
|
+
# Raised by the ADL module when an automaton, string or sample
|
21
|
+
# format is violated at parsing time.
|
22
|
+
class ParseError < StaminaError; end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end # module Stamina
|
@@ -0,0 +1,19 @@
|
|
1
|
+
if RUBY_VERSION < "1.9"
|
2
|
+
|
3
|
+
def Math.log2( x )
|
4
|
+
Math.log( x ) / Math.log( 2 )
|
5
|
+
end
|
6
|
+
|
7
|
+
def Math.logn( x, n )
|
8
|
+
Math.log( x ) / Math.log( n )
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
def Math.max(i, j)
|
14
|
+
i > j ? i : j
|
15
|
+
end
|
16
|
+
|
17
|
+
def Math.min(i, j)
|
18
|
+
i < j ? i : j
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Stamina
|
2
|
+
#
|
3
|
+
# Allows any object to be markable with user-data.
|
4
|
+
#
|
5
|
+
# This module is expected to be included by classes that want to implement the
|
6
|
+
# Markable design pattern. Moreover, if the instances of the including class
|
7
|
+
# respond to <tt>state_changed</tt>, this method is automatically invoked when
|
8
|
+
# marks change. This method is used by <tt>automaton</tt> in order to make it
|
9
|
+
# possible to track changes and check modified automata for consistency.
|
10
|
+
#
|
11
|
+
# == Detailed API
|
12
|
+
module Markable
|
13
|
+
|
14
|
+
#
|
15
|
+
# Returns user-value associated to _key_, nil if no such key in user-data.
|
16
|
+
#
|
17
|
+
def [](key) @data[key] end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Associates _value_ to _key_ in user-data. Overrides previous value if
|
21
|
+
# present.
|
22
|
+
#
|
23
|
+
def []=(key,value)
|
24
|
+
oldvalue = @data[key]
|
25
|
+
@data[key] = value
|
26
|
+
state_changed(:loaded_pair, [key,oldvalue,value]) if self.respond_to? :state_changed
|
27
|
+
end
|
28
|
+
|
29
|
+
# Removes a mark
|
30
|
+
def remove_mark(key)
|
31
|
+
oldvalue = @data[key]
|
32
|
+
@data.delete(key)
|
33
|
+
state_changed(:loaded_pair, [key,oldvalue,nil]) if self.respond_to? :state_changed
|
34
|
+
end
|
35
|
+
|
36
|
+
# Extracts the copy of attributes which can subsequently be modified.
|
37
|
+
def data
|
38
|
+
@data.nil? ? {} : @data.dup
|
39
|
+
end
|
40
|
+
|
41
|
+
end # module Markable
|
42
|
+
end # module Stamina
|