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,23 @@
|
|
1
|
+
module Stamina
|
2
|
+
class Automaton
|
3
|
+
|
4
|
+
#
|
5
|
+
# Checks if this automaton is minimal.
|
6
|
+
#
|
7
|
+
def minimal?
|
8
|
+
self.minimize.state_count == self.state_count
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# Returns a minimized version of this automaton.
|
13
|
+
#
|
14
|
+
# This method should only be called on deterministic automata.
|
15
|
+
#
|
16
|
+
def minimize(options = {})
|
17
|
+
Minimize::Hopcroft.execute(self, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
end # class Automaton
|
21
|
+
end # module Stamina
|
22
|
+
require_relative 'minimize/hopcroft'
|
23
|
+
require_relative 'minimize/pitchies'
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Stamina
|
2
|
+
class Automaton
|
3
|
+
module Minimize
|
4
|
+
class Hopcroft
|
5
|
+
|
6
|
+
# Creates an algorithm instance
|
7
|
+
def initialize(automaton, options)
|
8
|
+
raise ArgumentError, "Deterministic automaton expected", caller unless automaton.deterministic?
|
9
|
+
@automaton = automaton
|
10
|
+
end
|
11
|
+
|
12
|
+
# Compute a Hash {symbol => state_group} from a group of states
|
13
|
+
def reverse_delta(group)
|
14
|
+
h = Hash.new{|h,k| h[k]=Set.new}
|
15
|
+
group.each do |state|
|
16
|
+
state.in_edges.each do |edge|
|
17
|
+
h[edge.symbol] << edge.source
|
18
|
+
end
|
19
|
+
end
|
20
|
+
h
|
21
|
+
end
|
22
|
+
|
23
|
+
# Computes a minimal dfa from the grouping information
|
24
|
+
def compute_minimal_dfa(groups)
|
25
|
+
indexes = []
|
26
|
+
fa = Automaton.new do |fa|
|
27
|
+
|
28
|
+
# create one state for each group
|
29
|
+
groups.each_with_index do |group,index|
|
30
|
+
group.each{|s| indexes[s.index] = index}
|
31
|
+
data = group.inject(nil) do |memo,s|
|
32
|
+
if memo.nil?
|
33
|
+
s.data
|
34
|
+
else
|
35
|
+
{:initial => memo[:initial] || s.initial?,
|
36
|
+
:accepting => memo[:accepting] || s.accepting?,
|
37
|
+
:error => memo[:error] || s.error?}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
fa.add_state(data)
|
41
|
+
end
|
42
|
+
|
43
|
+
# connect transitions now
|
44
|
+
groups.each_with_index do |group,index|
|
45
|
+
group.each do |s|
|
46
|
+
s_index = indexes[s.index]
|
47
|
+
s.out_edges.each do |edge|
|
48
|
+
symbol, t_index = edge.symbol, indexes[edge.target.index]
|
49
|
+
unless fa.ith_state(s_index).dfa_step(symbol)
|
50
|
+
fa.connect(s_index, t_index, symbol)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
fa.drop_states *fa.states.select{|s| s.sink?}
|
58
|
+
fa.state_count == 0 ? Automaton::DUM : fa
|
59
|
+
end
|
60
|
+
|
61
|
+
# Computes the initial partition
|
62
|
+
def initial_partition
|
63
|
+
p = [Set.new, Set.new]
|
64
|
+
@automaton.states.each do |s|
|
65
|
+
(s.accepting? ? p[0] : p[1]) << s
|
66
|
+
end
|
67
|
+
p.reject{|g| g.empty?}
|
68
|
+
end
|
69
|
+
|
70
|
+
# Main method of the algorithm
|
71
|
+
def main
|
72
|
+
# Partition states a first time according to accepting/non accepting
|
73
|
+
@partition = initial_partition # P in pseudo code
|
74
|
+
@worklist = @partition.dup # W in pseudo code
|
75
|
+
|
76
|
+
# Until a block needs to be refined
|
77
|
+
until @worklist.empty?
|
78
|
+
refined = @worklist.pop
|
79
|
+
|
80
|
+
# We compute the reverse delta on the group and look at the groups
|
81
|
+
rdelta = reverse_delta(refined)
|
82
|
+
rdelta.each_pair do |symbol, sources| # sources is la in pseudo code
|
83
|
+
|
84
|
+
# Find blocks to be refined
|
85
|
+
@partition.dup.each_with_index do |block, index| # block is R in pseudo code
|
86
|
+
next if block.subset?(sources)
|
87
|
+
intersection = block & sources # R1 in pseudo code
|
88
|
+
next if intersection.empty?
|
89
|
+
difference = block - intersection # R2 in pseudo code
|
90
|
+
|
91
|
+
# replace R in P with R1 and R2
|
92
|
+
@partition[index] = intersection
|
93
|
+
@partition << difference
|
94
|
+
|
95
|
+
# Adds the new blocks as to be refined
|
96
|
+
if @worklist.include?(block)
|
97
|
+
@worklist.delete(block)
|
98
|
+
@worklist << intersection << difference
|
99
|
+
else
|
100
|
+
@worklist << (intersection.size <= difference.size ? intersection : difference)
|
101
|
+
end
|
102
|
+
end # @partition.each
|
103
|
+
|
104
|
+
end # rdelta.each_pair
|
105
|
+
end # until @worklist.empty?
|
106
|
+
|
107
|
+
compute_minimal_dfa(@partition)
|
108
|
+
end # def main
|
109
|
+
|
110
|
+
# Execute the minimizer
|
111
|
+
def self.execute(automaton, options={})
|
112
|
+
Hopcroft.new(automaton.strip.complete!, options).main
|
113
|
+
end
|
114
|
+
|
115
|
+
end # class Hopcroft
|
116
|
+
end # module Minimize
|
117
|
+
end # class Automaton
|
118
|
+
end # module Stamina
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Stamina
|
2
|
+
class Automaton
|
3
|
+
module Minimize
|
4
|
+
#
|
5
|
+
# Straightforward and simple to understand minimization algorithm.
|
6
|
+
#
|
7
|
+
# The principle of the algorithm is to successively refine a partition of
|
8
|
+
# the DFA states. This partition is represented by an array of integers,
|
9
|
+
# one for each state, that uniquely identifies the partition block to which
|
10
|
+
# the state belongs. As usual, the initial partition separates accepting
|
11
|
+
# from non accepting states:
|
12
|
+
#
|
13
|
+
# P0 = [0, 1, 0, 0, ..., 1] # N integers, 1 (resp. 0) for accepting (resp
|
14
|
+
# # non accepting) states.
|
15
|
+
#
|
16
|
+
# A refinement step of the algorithm consists in refining this partition by
|
17
|
+
# looking forward in the DFA for each symbol in the alphabet. Consider a given
|
18
|
+
# symbol, say 'a', and the transition function given by a (complete) DFA. We
|
19
|
+
# can represent the restriction of this function over a given symbol, say 'a'
|
20
|
+
# by a simple array, containing the target state reached through 'a' for each
|
21
|
+
# state of the DFA:
|
22
|
+
#
|
23
|
+
# DELTA('a') = [5, 7, 1, ..., 0] # N integers, containing the unique identifier
|
24
|
+
# # of the target state reached through 'a' from
|
25
|
+
# # each state of the DFA, in order
|
26
|
+
#
|
27
|
+
# Now, given a partition of the DFA states Pi, one can simply look which block
|
28
|
+
# of the partition is reached through a given letter, say 'a' by combining it
|
29
|
+
# with DELTA('a')
|
30
|
+
#
|
31
|
+
# REACHED(Pi, 'a') = [ Pi[DELTA('a')[j]] | foreach 0 <= j < N-1 ]
|
32
|
+
#
|
33
|
+
# Given a partition Pi, if two states in the same block reach different blocks
|
34
|
+
# along the same symbol, they must be separated, by definition. Interrestingly,
|
35
|
+
# this information is contained in pairs of integers given by Pi and REACHED(Pi, 'a').
|
36
|
+
# In other words, consider the pairs
|
37
|
+
#
|
38
|
+
# PAIRS(Pi, 'a') = [ (Pi[j], REACHED(Pi, 'a')[j]) | foreach 0 <= j < N-1 ]
|
39
|
+
#
|
40
|
+
# Now, without loss of generality, one can simply give a unique number to each
|
41
|
+
# different pair in such an array of pairs (a naïve way of doing so is to define
|
42
|
+
# a total order relation over pairs, sorting them, and taking the smallest index
|
43
|
+
# of each pair in the sorted array). This leads to a partition refinement:
|
44
|
+
#
|
45
|
+
# REFINEMENT(Pi, 'a') = [ unique-number-of(PAIRS(Pi, 'a')[j]) | foreach 0 <= j < N-1 ]
|
46
|
+
#
|
47
|
+
# A step of the algorithm consists in applying such a refinement for each symbol in
|
48
|
+
# the alphabet:
|
49
|
+
#
|
50
|
+
# foreach symbol in Sigma
|
51
|
+
# Pi = REFINEMENT(Pi, symbol)
|
52
|
+
#
|
53
|
+
# The algorithm applies such refinements until a fix point is reached:
|
54
|
+
#
|
55
|
+
# # Trivial partition with all states in same block
|
56
|
+
# Pi = [ 0 | foreach 0 <= j < N-1 ]
|
57
|
+
#
|
58
|
+
# # initial non trivial partition separating accepting for non accepting
|
59
|
+
# # states
|
60
|
+
# Pj = [...]
|
61
|
+
#
|
62
|
+
# # fixpoint loop until Pi == Pj, i.e. no change has been made
|
63
|
+
# while Pj != Pi # warning here, we compare the real partitions...
|
64
|
+
# Pi = Pj
|
65
|
+
# foreach symbol in Sigma
|
66
|
+
# Pj = REFINEMENT(Pj, symbol)
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
class Pitchies
|
70
|
+
|
71
|
+
# Creates an algorithm instance
|
72
|
+
def initialize(automaton, options)
|
73
|
+
raise ArgumentError, "Deterministic automaton expected", caller unless automaton.deterministic?
|
74
|
+
@automaton = automaton
|
75
|
+
end
|
76
|
+
|
77
|
+
def minimized_dfa(oldfa, nb_states, partition)
|
78
|
+
fa = Automaton.new(false) do |newfa|
|
79
|
+
# Add the number of states, with default marks
|
80
|
+
newfa.add_n_states(nb_states, {:initial => false, :accepting => false, :error => false})
|
81
|
+
|
82
|
+
# Refine the marks using the source dfa as reference
|
83
|
+
partition.each_with_index do |block, state_index|
|
84
|
+
source = oldfa.ith_state(state_index)
|
85
|
+
target = newfa.ith_state(block)
|
86
|
+
target.initial! if source.initial?
|
87
|
+
target.accepting! if source.accepting?
|
88
|
+
target.error! if source.error?
|
89
|
+
end
|
90
|
+
|
91
|
+
# Now, create the transitions
|
92
|
+
partition.each_with_index do |block, state_index|
|
93
|
+
source = oldfa.ith_state(state_index)
|
94
|
+
target = newfa.ith_state(block)
|
95
|
+
source.out_edges.each do |edge|
|
96
|
+
where = partition[edge.target.index]
|
97
|
+
if target.dfa_step(edge.symbol) == nil
|
98
|
+
newfa.connect(target, where, edge.symbol)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
fa.drop_states *fa.states.select{|s| s.sink?}
|
104
|
+
fa.state_count == 0 ? Automaton::DUM : fa
|
105
|
+
end
|
106
|
+
|
107
|
+
def main
|
108
|
+
alph, states = @automaton.alphabet, @automaton.states
|
109
|
+
old_nb_states = -1
|
110
|
+
partition = states.collect{|s| s.accepting? ? 1 : 0}
|
111
|
+
until (nb_states = partition.uniq.size) == old_nb_states
|
112
|
+
old_nb_states = nb_states
|
113
|
+
alph.each do |symbol|
|
114
|
+
reached = states.collect{|s| partition[s.dfa_step(symbol).index]}
|
115
|
+
rehash = Hash.new{|h,k| h[k] = h.size}
|
116
|
+
partition = partition.zip(reached).collect{|pair| rehash[pair]}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
minimized_dfa(@automaton, nb_states, partition)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Execute the minimizer
|
123
|
+
def self.execute(automaton, options={})
|
124
|
+
Pitchies.new(automaton.strip.complete!, options).main
|
125
|
+
end
|
126
|
+
|
127
|
+
end # class Pitchies
|
128
|
+
end # module Minimize
|
129
|
+
end # class Automaton
|
130
|
+
end # module Stamina
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Stamina
|
2
|
+
class Automaton
|
3
|
+
|
4
|
+
# Removes unreachable states from the initial ones
|
5
|
+
def strip!
|
6
|
+
depth(:reachable)
|
7
|
+
drop_states(*states.select{|s| s[:reachable].nil?})
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns a copy of this automaton with unreachable states removed
|
11
|
+
def strip
|
12
|
+
dup.strip!
|
13
|
+
end
|
14
|
+
|
15
|
+
end # class Automaton
|
16
|
+
end # module Stamina
|
@@ -0,0 +1,361 @@
|
|
1
|
+
module Stamina
|
2
|
+
class Automaton
|
3
|
+
#
|
4
|
+
# Provides useful automaton walking methods. This module is automatically
|
5
|
+
# included in Automaton and is not intended to be used directly.
|
6
|
+
#
|
7
|
+
# == Examples
|
8
|
+
# # Building an automaton for the regular language a(ba)*
|
9
|
+
# s0, s1 = nil
|
10
|
+
# fa = Automaton.new do
|
11
|
+
# s0 = add_state(:initial => true)
|
12
|
+
# s1 = add_state(:accepting => true)
|
13
|
+
# connect(0,1,'a')
|
14
|
+
# connect(1,0,'b')
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # some examples with reached
|
18
|
+
# fa.dfa_reached('? a b') # -> s0 dfa variant method
|
19
|
+
# fa.dfa_reached('? a a') # -> nil
|
20
|
+
# fa.dfa_reached('? b a', s1) # -> s1 from an explicit init state
|
21
|
+
#
|
22
|
+
# fa.reached('? a b') # -> [s0] generic method on automaton
|
23
|
+
# fa.reached('? a a') # -> []
|
24
|
+
# fa.reached('? b a', s1) # -> [s1]
|
25
|
+
#
|
26
|
+
# # some examples with split (the most powerful one!)
|
27
|
+
# fa.dfa_split('? a b a b') # [['a','b','a','b'], s0, []]
|
28
|
+
# fa.dfa_split('? a b b a') # [['a','b'], s0, ['b','a']]
|
29
|
+
#
|
30
|
+
# fa.split('? a b a b') # [['a','b','a','b'], [s0], []]
|
31
|
+
# fa.split('? a b b a') # [['a','b'], [s0], ['b','a']]
|
32
|
+
#
|
33
|
+
# # All of this works on non-deterministic automata as well (and epsilon
|
34
|
+
# # symbols are taken into account), but you'll probably need to read
|
35
|
+
# # the following section to master the power of this module in this case!
|
36
|
+
#
|
37
|
+
# == Using this module
|
38
|
+
# This section fully details the design choices that has been made for the
|
39
|
+
# implementation of the Walking module used by Stamina on Automaton. It is provided
|
40
|
+
# because Walking is one of the core classes of Stamina, that probably all
|
41
|
+
# users (and contributors) will use. Walking usage is really user-friendly,
|
42
|
+
# so <b>you are normally not required</b> to read this section in the first
|
43
|
+
# place ! Read it only if of interest for you, or if you experiment unexpected
|
44
|
+
# results.
|
45
|
+
#
|
46
|
+
# Methods defined by this module respect common conventions that you must be
|
47
|
+
# aware of:
|
48
|
+
#
|
49
|
+
# === Generic methods vs. dfa variants
|
50
|
+
# The convention is simple: methods whose name starts with 'dfa_' are expected
|
51
|
+
# to be used on deterministic automata only (that is, automata answering _true_
|
52
|
+
# to the deterministic? method invocation). We refer to those methods as
|
53
|
+
# 'dfa variants'. Strange results may occur if invoked on non-deterministic
|
54
|
+
# automata. Other methods are called 'generic methods' and can be used on any
|
55
|
+
# automaton. Generic methods and dfa variants sometimes use different conventions
|
56
|
+
# according to arguments and returned values, as explained below.
|
57
|
+
#
|
58
|
+
# === Argument conventions
|
59
|
+
# - all methods taking a _symbol_ argument expect it to be a valid instance of
|
60
|
+
# the class used for representing input symbols on edges of your automaton
|
61
|
+
# (that is, the mark you've installed under :symbol key on the edge, see
|
62
|
+
# Automaton documentation for details).
|
63
|
+
# - all methods taking an _input_ argument support the following objects for it:
|
64
|
+
# - InputString instance: an real input string typically coming from a Sample.
|
65
|
+
# - Array of symbols: where by symbol, we mean an input symbol as explained
|
66
|
+
# above (and not a Ruby Symbol instance). The array is never modified by the
|
67
|
+
# methods, so that you don't have to worry about where this array comes from.
|
68
|
+
# - String (a real Ruby one): in this case, the input is expected to be an ADL
|
69
|
+
# input string, which is parsed using ADL::parse_string. Note that 'a b a b'
|
70
|
+
# is NOT a valid ADL input string, so that you typically have to use the '?'
|
71
|
+
# sign to indicate that the tested string is basically unlabeled.
|
72
|
+
# - all methods taking a _from_ argument support the following objects for it:
|
73
|
+
# - ommited: _from_ is interpreted as the set of initial states by generic
|
74
|
+
# methods and the last rule applies. _from_ is interpreted as the unique initial
|
75
|
+
# state of the deterministic automaton by dfa method variants (<tt>dfa_xxx</tt>),
|
76
|
+
# and the third rule applies.
|
77
|
+
# - Integer: _from_ is interpreted as a state index, and the next rule applies
|
78
|
+
# on the index-th state of the automaton.
|
79
|
+
# - State: _from_ is interpreted by the generic methods as a singleton set
|
80
|
+
# containing the state and the last rule applies. Deterministic method
|
81
|
+
# variants interpret it as the start state from which the walk must start.
|
82
|
+
# In this case, they always return a State instance (or _nil_) instead of
|
83
|
+
# an array of states.
|
84
|
+
# - Array: _from_ is interpreted as a set of states (duplicates are supported
|
85
|
+
# so it's actually a bag) from which the walk must start. Indexes of states
|
86
|
+
# are also supported, see Automaton documentation about indexes.
|
87
|
+
#
|
88
|
+
# === Returned value conventions
|
89
|
+
# Moreover, (unless stated explicitely) all methods returning states as (part of)
|
90
|
+
# their returned value respect the following _return_ conventions (which somewhat
|
91
|
+
# summarizes the _from_ conventions above):
|
92
|
+
# - generic methods *always* return an array of states (without duplicates) which
|
93
|
+
# can be modified. This array is *never* sorted by state index. To insist:
|
94
|
+
# even when invoked on a deterministic automaton with a State argument as
|
95
|
+
# _from_, they will return an array of states as show by the code excerpt
|
96
|
+
# below. Lastly, the returned value is *never* _nil_, but an empty array may
|
97
|
+
# be returned when it makes sense (no reached states for example).
|
98
|
+
#
|
99
|
+
# fa = Automaton.new do ... end # build a(ba)* automaton
|
100
|
+
# s0 = fa.initial_state
|
101
|
+
# fa.reached('? a b a b', s0) # returns [s0], not s0 !
|
102
|
+
#
|
103
|
+
# - dfa variant methods respond to your query using the same language as you:
|
104
|
+
# if _from_ is ommitted, is a State or an Integer, the the result will be a
|
105
|
+
# single State instance, or _nil_ if it makes sense (no reached state for
|
106
|
+
# example). Otherwise, they behaves exactly as generic methods (*always* return
|
107
|
+
# an array of states, ...)
|
108
|
+
#
|
109
|
+
# === Epsilon symbol aware methods
|
110
|
+
# Stamina does not allow epsilon symbols on deterministic automata; thus, this
|
111
|
+
# subsection only applies to generic methods.
|
112
|
+
#
|
113
|
+
# Methods documented as 'epsilon aware' (almost all generic methods) *always*
|
114
|
+
# take epsilon symbols into account in their computations (Stamina uses _nil_ as
|
115
|
+
# epsilon symbol, by convention), in a natural way. For example:
|
116
|
+
#
|
117
|
+
# fa = Automaton.new do ... end # build a non-deterministic automaton
|
118
|
+
# # with epsilon symbols
|
119
|
+
#
|
120
|
+
# # the line below computes the set of reached states
|
121
|
+
# # (from the set of initial states) by walking the dfa
|
122
|
+
# # with a string.
|
123
|
+
# #
|
124
|
+
# # The actual computation is in fact the set of reached
|
125
|
+
# # states with the string (regex) 'eps* a eps* b eps*',
|
126
|
+
# # where eps is the epsilon symbol.
|
127
|
+
# reached = fa.reached('? a b')
|
128
|
+
#
|
129
|
+
# == Detailed API
|
130
|
+
module Walking
|
131
|
+
|
132
|
+
#
|
133
|
+
# Returns reachable states from _from_ states with an input _symbol_. This
|
134
|
+
# method is not epsilon symbol aware.
|
135
|
+
#
|
136
|
+
def step(from, symbol)
|
137
|
+
from = walking_to_from(from)
|
138
|
+
from.collect{|s| s.step(symbol)}.flatten.uniq
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Returns the state reached from _from_ states with an input _symbol_. Returns
|
143
|
+
# nil or the empty array (according to _from_ conventions) if no state can be
|
144
|
+
# reached with the given symbol.
|
145
|
+
#
|
146
|
+
def dfa_step(from, symbol)
|
147
|
+
step = walking_to_from(from).collect{|s| s.dfa_step(symbol)}.flatten.uniq
|
148
|
+
walking_to_dfa_result(step, from)
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# Computes an array representing the set of states that can be reached from
|
153
|
+
# _from_ states with the given input _symbol_.
|
154
|
+
#
|
155
|
+
# This method is epsilon symbol aware (represented with nil) on non
|
156
|
+
# deterministic automata, meaning that it actually computes the set of
|
157
|
+
# reachable states through strings respecting the <tt>eps* symbol eps*</tt>
|
158
|
+
# regular expression, where eps is the epsilon symbol.
|
159
|
+
#
|
160
|
+
def delta(from, symbol)
|
161
|
+
walking_to_from(from).collect{|s| s.delta(symbol)}.flatten.uniq
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# Returns the target state (or the target states, according to _from_
|
166
|
+
# conventions) that can be reached from _from_ states with a given input
|
167
|
+
# _symbol_. Returns nil (or an empty array, according to the same conventions)
|
168
|
+
# if no such state exists.
|
169
|
+
#
|
170
|
+
def dfa_delta(from, symbol)
|
171
|
+
if from.is_a?(Automaton::State)
|
172
|
+
from.dfa_delta(symbol)
|
173
|
+
else
|
174
|
+
delta = walking_to_from(from).collect{|s| s.dfa_delta(symbol)}.flatten.uniq
|
175
|
+
walking_to_dfa_result(delta, from)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# Splits a given input and returns a triplet <tt>[parsed,reached,remaining]</tt>
|
181
|
+
# where _parsed_ is an array of parsed symbols, _reached_ is the set of reached
|
182
|
+
# states with the _parsed_ input string and _remaining_ is an array of symbols
|
183
|
+
# with the unparsable part of the string. This method is epsilon symbol aware.
|
184
|
+
#
|
185
|
+
# By construction, the following properties are verified:
|
186
|
+
# - <tt>parsed + remaining == input</tt> (assuming input is an array of symbols),
|
187
|
+
# which means that atring concatenation of parsed and remaining symbols is
|
188
|
+
# is the input string.
|
189
|
+
# - <tt>reached.empty? == false</tt>, because at least initial states (or
|
190
|
+
# _from_ if provided) are reached.
|
191
|
+
# - <tt>remaining.empty? == parses?(input,from)</tt>, meaning that the automaton
|
192
|
+
# parses the whole input if there is no remaining symol.
|
193
|
+
# - <tt>delta(reached, remaining[0]).empty? unless remaining.empty?</tt>,
|
194
|
+
# which express the splitting stop condition: splitting continues while at
|
195
|
+
# least one state can be reached with the next symbol.
|
196
|
+
#
|
197
|
+
def split(input, from=nil, sort=false)
|
198
|
+
if deterministic?
|
199
|
+
parsed, reached, remaining = dfa_split(input, from)
|
200
|
+
[parsed, walking_from_dfa_to_nfa_result(reached), remaining]
|
201
|
+
else
|
202
|
+
# the three elements of the triplet
|
203
|
+
parsed = []
|
204
|
+
reached = walking_to_from(from)
|
205
|
+
remaining = walking_to_modifiable_symbols(input)
|
206
|
+
|
207
|
+
# walk now
|
208
|
+
until remaining.empty?
|
209
|
+
symb = remaining[0]
|
210
|
+
next_reached = delta(reached, symb)
|
211
|
+
|
212
|
+
# stop it if no reached state
|
213
|
+
break if next_reached.empty?
|
214
|
+
|
215
|
+
# otherwise, update triplet
|
216
|
+
parsed << remaining.shift
|
217
|
+
reached = next_reached
|
218
|
+
end
|
219
|
+
reached.sort! if sort
|
220
|
+
[parsed, reached, remaining]
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Same as split, respecting dfa conventions.
|
225
|
+
def dfa_split(input, from=nil)
|
226
|
+
from = initial_state if from.nil?
|
227
|
+
from = ith_state(from) if from.is_a?(Integer)
|
228
|
+
if from.is_a?(Automaton::State)
|
229
|
+
# the three elements of the triplet
|
230
|
+
parsed = []
|
231
|
+
reached = from
|
232
|
+
remaining = walking_to_modifiable_symbols(input)
|
233
|
+
|
234
|
+
# walk now
|
235
|
+
until remaining.empty?
|
236
|
+
symb = remaining[0]
|
237
|
+
next_reached = reached.dfa_delta(symb)
|
238
|
+
|
239
|
+
# stop it if no reached state
|
240
|
+
break if next_reached.nil?
|
241
|
+
|
242
|
+
# otherwise, update triplet
|
243
|
+
parsed << remaining.shift
|
244
|
+
reached = next_reached
|
245
|
+
end
|
246
|
+
[parsed, reached, remaining]
|
247
|
+
else
|
248
|
+
# the three elements of the triplet
|
249
|
+
parsed = []
|
250
|
+
reached = walking_to_from(from)
|
251
|
+
remaining = walking_to_modifiable_symbols(input)
|
252
|
+
|
253
|
+
# walk now
|
254
|
+
until remaining.empty?
|
255
|
+
symb = remaining[0]
|
256
|
+
next_reached = dfa_delta(reached, symb)
|
257
|
+
|
258
|
+
# stop it if no reached state
|
259
|
+
break if next_reached.nil? or next_reached.empty?
|
260
|
+
|
261
|
+
# otherwise, update triplet
|
262
|
+
parsed << remaining.shift
|
263
|
+
reached = next_reached
|
264
|
+
end
|
265
|
+
[parsed, walking_to_dfa_result(reached, from), remaining]
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
#
|
270
|
+
# Walks the automaton with an input string, starting at states _from_,
|
271
|
+
# collects the set of all reached states and returns it. Unlike split,
|
272
|
+
# <b>returned array is empty if the string is not parsable by the automaton</b>.
|
273
|
+
# This method is epsilon symbol aware.
|
274
|
+
#
|
275
|
+
def reached(input, from=nil)
|
276
|
+
parsed, reached, remaining = split(input, from)
|
277
|
+
remaining.empty? ? reached : []
|
278
|
+
end
|
279
|
+
|
280
|
+
# Same as reached, respecting dfa conventions.
|
281
|
+
def dfa_reached(input, from=nil)
|
282
|
+
walking_to_dfa_result(reached(input,from),from)
|
283
|
+
end
|
284
|
+
|
285
|
+
#
|
286
|
+
# Checks if the automaton is able to parse an input string. Returns true if
|
287
|
+
# at least one state can be reached, false otherwise. Unlike accepts?, the
|
288
|
+
# labeling of the reached state does not count.
|
289
|
+
#
|
290
|
+
def parses?(input, from=nil)
|
291
|
+
not(reached(input,from).empty?)
|
292
|
+
end
|
293
|
+
|
294
|
+
#
|
295
|
+
# Checks if the automaton accepts an input string. Returns true if at least
|
296
|
+
# one accepting state can be reached, false otherwise.
|
297
|
+
#
|
298
|
+
def accepts?(input, from=nil)
|
299
|
+
not reached(input,from).select{|s| s.accepting? and not s.error?}.empty?
|
300
|
+
end
|
301
|
+
|
302
|
+
#
|
303
|
+
# Checks if the automaton rejects an input string. Returns true if no
|
304
|
+
# accepting state can be reached, false otherwise.
|
305
|
+
#
|
306
|
+
def rejects?(input, from=nil)
|
307
|
+
not(accepts?(input, from))
|
308
|
+
end
|
309
|
+
|
310
|
+
# Returns '1' if the string is accepted by the automaton,
|
311
|
+
# '0' otherwise.
|
312
|
+
def label_of(str)
|
313
|
+
accepts?(str) ? '1' : '0'
|
314
|
+
end
|
315
|
+
|
316
|
+
### protected section ########################################################
|
317
|
+
protected
|
318
|
+
|
319
|
+
#
|
320
|
+
# Converts an input to a modifiable array of symbols.
|
321
|
+
#
|
322
|
+
# If _input_ is an array, it is simply duplicated. If an InputString,
|
323
|
+
# InputString#symbols is invoked and result is duplicated. If _input_ is a
|
324
|
+
# ruby String, it is split using <tt>input.split(' ')</tt>. Raises an
|
325
|
+
# ArgumentError otherwise.
|
326
|
+
#
|
327
|
+
def walking_to_modifiable_symbols(input)
|
328
|
+
case input
|
329
|
+
when Array
|
330
|
+
input.dup
|
331
|
+
when InputString
|
332
|
+
input.symbols.dup
|
333
|
+
when String
|
334
|
+
ADL::parse_string(input).symbols.dup
|
335
|
+
else
|
336
|
+
raise(ArgumentError,
|
337
|
+
"#{input} cannot be converted to a array of symbols", caller)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
# Implements _from_ conventions.
|
342
|
+
def walking_to_from(from)
|
343
|
+
return initial_states if from.nil?
|
344
|
+
Array===from ? from.collect{|s| to_state(s)} : [to_state(from)]
|
345
|
+
end
|
346
|
+
|
347
|
+
# Implements _return_ conventions of dfa_xxx methods.
|
348
|
+
def walking_to_dfa_result(result, from)
|
349
|
+
result.compact! # methods are allowed to return [nil]
|
350
|
+
Array===from ? result : (result.empty? ? nil : result[0])
|
351
|
+
end
|
352
|
+
|
353
|
+
# Implements _return_ conventions of standard methods that uses dfa_xxx ones.
|
354
|
+
def walking_from_dfa_to_nfa_result(result)
|
355
|
+
Array===result ? result : (result.nil? ? [] : [result])
|
356
|
+
end
|
357
|
+
|
358
|
+
end # end Walking
|
359
|
+
include Stamina::Automaton::Walking
|
360
|
+
end # class Automaton
|
361
|
+
end # end Stamina
|