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.
Files changed (36) hide show
  1. data/CHANGELOG.md +78 -0
  2. data/LICENCE.md +22 -0
  3. data/lib/stamina-core/stamina-core.rb +1 -0
  4. data/lib/stamina-core/stamina/adl.rb +298 -0
  5. data/lib/stamina-core/stamina/automaton.rb +1300 -0
  6. data/lib/stamina-core/stamina/automaton/complement.rb +26 -0
  7. data/lib/stamina-core/stamina/automaton/complete.rb +36 -0
  8. data/lib/stamina-core/stamina/automaton/compose.rb +111 -0
  9. data/lib/stamina-core/stamina/automaton/determinize.rb +104 -0
  10. data/lib/stamina-core/stamina/automaton/equivalence.rb +57 -0
  11. data/lib/stamina-core/stamina/automaton/hide.rb +41 -0
  12. data/lib/stamina-core/stamina/automaton/metrics.rb +77 -0
  13. data/lib/stamina-core/stamina/automaton/minimize.rb +23 -0
  14. data/lib/stamina-core/stamina/automaton/minimize/hopcroft.rb +118 -0
  15. data/lib/stamina-core/stamina/automaton/minimize/pitchies.rb +130 -0
  16. data/lib/stamina-core/stamina/automaton/strip.rb +16 -0
  17. data/lib/stamina-core/stamina/automaton/walking.rb +361 -0
  18. data/lib/stamina-core/stamina/command.rb +38 -0
  19. data/lib/stamina-core/stamina/command/adl2dot.rb +82 -0
  20. data/lib/stamina-core/stamina/command/help.rb +23 -0
  21. data/lib/stamina-core/stamina/command/robustness.rb +21 -0
  22. data/lib/stamina-core/stamina/command/run.rb +84 -0
  23. data/lib/stamina-core/stamina/core.rb +11 -0
  24. data/lib/stamina-core/stamina/dsl.rb +6 -0
  25. data/lib/stamina-core/stamina/dsl/automata.rb +23 -0
  26. data/lib/stamina-core/stamina/dsl/core.rb +14 -0
  27. data/lib/stamina-core/stamina/engine.rb +32 -0
  28. data/lib/stamina-core/stamina/engine/context.rb +35 -0
  29. data/lib/stamina-core/stamina/errors.rb +26 -0
  30. data/lib/stamina-core/stamina/ext/math.rb +19 -0
  31. data/lib/stamina-core/stamina/loader.rb +3 -0
  32. data/lib/stamina-core/stamina/markable.rb +42 -0
  33. data/lib/stamina-core/stamina/utils.rb +1 -0
  34. data/lib/stamina-core/stamina/utils/decorate.rb +81 -0
  35. data/lib/stamina-core/stamina/version.rb +14 -0
  36. 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