stamina-core 0.5.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.
- 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
|