stamina-core 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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