stamina 0.4.0 → 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 (116) hide show
  1. data/CHANGELOG.md +22 -5
  2. data/LICENCE.md +2 -2
  3. data/bin/stamina +1 -7
  4. data/lib/stamina.rb +10 -19
  5. metadata +54 -333
  6. data/.gemtest +0 -0
  7. data/Gemfile +0 -2
  8. data/Gemfile.lock +0 -37
  9. data/Manifest.txt +0 -16
  10. data/README.md +0 -78
  11. data/Rakefile +0 -23
  12. data/example/adl/automaton.adl +0 -49
  13. data/example/adl/sample.adl +0 -53
  14. data/example/basic/characteristic_sample.adl +0 -32
  15. data/example/basic/target.adl +0 -9
  16. data/example/competition/31_test.adl +0 -1500
  17. data/example/competition/31_training.adl +0 -1759
  18. data/lib/stamina/abbadingo.rb +0 -2
  19. data/lib/stamina/abbadingo/random_dfa.rb +0 -48
  20. data/lib/stamina/abbadingo/random_sample.rb +0 -146
  21. data/lib/stamina/adl.rb +0 -298
  22. data/lib/stamina/automaton.rb +0 -1263
  23. data/lib/stamina/automaton/complete.rb +0 -36
  24. data/lib/stamina/automaton/equivalence.rb +0 -55
  25. data/lib/stamina/automaton/metrics.rb +0 -78
  26. data/lib/stamina/automaton/minimize.rb +0 -25
  27. data/lib/stamina/automaton/minimize/hopcroft.rb +0 -116
  28. data/lib/stamina/automaton/minimize/pitchies.rb +0 -64
  29. data/lib/stamina/automaton/strip.rb +0 -16
  30. data/lib/stamina/automaton/walking.rb +0 -363
  31. data/lib/stamina/classifier.rb +0 -52
  32. data/lib/stamina/command.rb +0 -45
  33. data/lib/stamina/command/abbadingo_dfa.rb +0 -81
  34. data/lib/stamina/command/abbadingo_samples.rb +0 -40
  35. data/lib/stamina/command/adl2dot.rb +0 -71
  36. data/lib/stamina/command/classify.rb +0 -48
  37. data/lib/stamina/command/help.rb +0 -27
  38. data/lib/stamina/command/infer.rb +0 -141
  39. data/lib/stamina/command/metrics.rb +0 -51
  40. data/lib/stamina/command/robustness.rb +0 -22
  41. data/lib/stamina/command/score.rb +0 -35
  42. data/lib/stamina/errors.rb +0 -23
  43. data/lib/stamina/ext/math.rb +0 -20
  44. data/lib/stamina/induction/blue_fringe.rb +0 -265
  45. data/lib/stamina/induction/commons.rb +0 -156
  46. data/lib/stamina/induction/rpni.rb +0 -186
  47. data/lib/stamina/induction/union_find.rb +0 -377
  48. data/lib/stamina/input_string.rb +0 -123
  49. data/lib/stamina/loader.rb +0 -1
  50. data/lib/stamina/markable.rb +0 -42
  51. data/lib/stamina/sample.rb +0 -267
  52. data/lib/stamina/scoring.rb +0 -213
  53. data/lib/stamina/utils.rb +0 -1
  54. data/lib/stamina/utils/decorate.rb +0 -81
  55. data/lib/stamina/version.rb +0 -14
  56. data/stamina.gemspec +0 -191
  57. data/stamina.noespec +0 -32
  58. data/tasks/debug_mail.rake +0 -78
  59. data/tasks/debug_mail.txt +0 -13
  60. data/tasks/gem.rake +0 -68
  61. data/tasks/spec_test.rake +0 -79
  62. data/tasks/unit_test.rake +0 -77
  63. data/tasks/yard.rake +0 -51
  64. data/test/stamina/abbadingo/random_dfa_test.rb +0 -16
  65. data/test/stamina/abbadingo/random_sample_test.rb +0 -78
  66. data/test/stamina/adl_test.rb +0 -516
  67. data/test/stamina/automaton/classifier_test.rb +0 -259
  68. data/test/stamina/automaton/complete_test.rb +0 -58
  69. data/test/stamina/automaton/equivalence_test.rb +0 -120
  70. data/test/stamina/automaton/metrics_test.rb +0 -36
  71. data/test/stamina/automaton/minimize/hopcroft_test.rb +0 -15
  72. data/test/stamina/automaton/minimize/minimize_test.rb +0 -55
  73. data/test/stamina/automaton/minimize/pitchies_test.rb +0 -15
  74. data/test/stamina/automaton/minimize/rice_edu_10.adl +0 -16
  75. data/test/stamina/automaton/minimize/rice_edu_10.min.adl +0 -13
  76. data/test/stamina/automaton/minimize/rice_edu_13.adl +0 -13
  77. data/test/stamina/automaton/minimize/rice_edu_13.min.adl +0 -7
  78. data/test/stamina/automaton/minimize/should_strip_1.adl +0 -8
  79. data/test/stamina/automaton/minimize/should_strip_1.min.adl +0 -6
  80. data/test/stamina/automaton/minimize/unknown_1.adl +0 -16
  81. data/test/stamina/automaton/minimize/unknown_1.min.adl +0 -12
  82. data/test/stamina/automaton/strip_test.rb +0 -36
  83. data/test/stamina/automaton/to_dot_test.rb +0 -64
  84. data/test/stamina/automaton/walking/dfa_delta_test.rb +0 -39
  85. data/test/stamina/automaton/walking_test.rb +0 -206
  86. data/test/stamina/automaton_additional_test.rb +0 -190
  87. data/test/stamina/automaton_test.rb +0 -1104
  88. data/test/stamina/exit.rb +0 -3
  89. data/test/stamina/induction/blue_fringe_test.rb +0 -83
  90. data/test/stamina/induction/induction_test.rb +0 -70
  91. data/test/stamina/induction/redblue_mergesamestatebug_expected.adl +0 -19
  92. data/test/stamina/induction/redblue_mergesamestatebug_pta.dot +0 -64
  93. data/test/stamina/induction/redblue_mergesamestatebug_sample.adl +0 -9
  94. data/test/stamina/induction/redblue_universal_expected.adl +0 -4
  95. data/test/stamina/induction/redblue_universal_sample.adl +0 -5
  96. data/test/stamina/induction/rpni_inria_expected.adl +0 -7
  97. data/test/stamina/induction/rpni_inria_sample.adl +0 -9
  98. data/test/stamina/induction/rpni_test.rb +0 -129
  99. data/test/stamina/induction/rpni_test_pta.dot +0 -22
  100. data/test/stamina/induction/rpni_universal_expected.adl +0 -4
  101. data/test/stamina/induction/rpni_universal_sample.adl +0 -4
  102. data/test/stamina/induction/union_find_test.rb +0 -124
  103. data/test/stamina/input_string_test.rb +0 -323
  104. data/test/stamina/markable_test.rb +0 -70
  105. data/test/stamina/randdfa.adl +0 -66
  106. data/test/stamina/sample.adl +0 -4
  107. data/test/stamina/sample_classify_test.rb +0 -149
  108. data/test/stamina/sample_test.rb +0 -290
  109. data/test/stamina/scoring_test.rb +0 -63
  110. data/test/stamina/small_dfa.dot +0 -16
  111. data/test/stamina/small_dfa.gif +0 -0
  112. data/test/stamina/small_nfa.dot +0 -18
  113. data/test/stamina/small_nfa.gif +0 -0
  114. data/test/stamina/stamina_test.rb +0 -80
  115. data/test/stamina/utils/decorate_test.rb +0 -65
  116. data/test/test_all.rb +0 -7
@@ -1,36 +0,0 @@
1
- module Stamina
2
- class Automaton
3
-
4
- #
5
- # Checks if this automaton is complete
6
- #
7
- def complete?
8
- alph = alphabet
9
- states.find{|s| !(alphabet - s.out_symbols).empty?}.nil?
10
- end
11
-
12
- #
13
- # Returns a completed copy of this automaton
14
- #
15
- def complete
16
- self.dup.complete!
17
- end
18
-
19
- #
20
- # Completes this automaton.
21
- #
22
- def complete!(sink_data = {:initial => false, :accepting => false, :error => false})
23
- alph = alphabet
24
- sink = add_state(sink_data)
25
- each_state do |s|
26
- out_symbols = s.out_symbols
27
- (alph-out_symbols).each do |symbol|
28
- connect(s, sink, symbol)
29
- end
30
- end
31
- drop_state(sink) if sink.adjacent_states == [sink]
32
- self
33
- end
34
-
35
- end # class Automaton
36
- end # module Stamina
@@ -1,55 +0,0 @@
1
- module Stamina
2
- class Automaton
3
-
4
- #
5
- # Checks if this automaton is equivalent to another one.
6
- #
7
- # Automata must be both minimal and complete to guarantee that this method
8
- # works.
9
- #
10
- def equivalent?(other, equiv = nil, key = :equiv_state)
11
- equiv ||= Proc.new{|s1,s2| (s1.accepting? == s2.accepting?) &&
12
- (s1.error? == s2.error?) &&
13
- (s1.initial? == s2.initial?) }
14
-
15
- # Both must already have basic attributes in common
16
- return false unless state_count==other.state_count
17
- return false unless edge_count==other.edge_count
18
- return false unless equiv[initial_state, other.initial_state]
19
-
20
- # We instantiate the decoration algorithm for checking equivalence on this
21
- # automaton:
22
- # * decoration is the index of the equivalent state in other automaton
23
- # * d0 is thus 'other.initial_state.index'
24
- # * suppremum is identity and fails when the equivalent state is not unique
25
- # * propagation checks transition function delta
26
- #
27
- algo = Stamina::Utils::Decorate.new(key)
28
- algo.set_suppremum do |d0, d1|
29
- if (d0.nil? or d1.nil?)
30
- (d0 || d1)
31
- elsif d0==d1
32
- d0
33
- else
34
- raise Stamina::Abord
35
- end
36
- end
37
- algo.set_propagate do |d,e|
38
- reached = other.ith_state(d).dfa_step(e.symbol)
39
- raise Stamina::Abord if reached.nil?
40
- raise Stamina::Abord unless equiv[e.target, reached]
41
- reached.index
42
- end
43
-
44
- # Run the algorithm now
45
- begin
46
- algo.execute(self, nil, other.initial_state.index)
47
- return true
48
- rescue Stamina::Abord
49
- return false
50
- end
51
- end
52
- alias :<=> :equivalent?
53
-
54
- end # class Automaton
55
- end # module Stamina
@@ -1,78 +0,0 @@
1
- require 'stamina/utils/decorate'
2
- module Stamina
3
- class Automaton
4
- #
5
- # Provides useful metric methods on automata.
6
- #
7
- # This module is automatically included by Automaton and is not intended
8
- # to be used directly.
9
- #
10
- module Metrics
11
-
12
- #
13
- # Returns the number of letters of the alphabet.
14
- #
15
- def alphabet_size
16
- alphabet.size
17
- end
18
-
19
- #
20
- # Returns the average degree of states, that is,
21
- # <code>edge_count/state_count</code>
22
- #
23
- def avg_degree
24
- edge_count.to_f/state_count.to_f
25
- end
26
- alias :avg_out_degree :avg_degree
27
- alias :avg_in_degree :avg_degree
28
-
29
- #
30
- # Number of accepting states over all states
31
- #
32
- def accepting_ratio
33
- states.select{|s|s.accepting?}.size.to_f/state_count.to_f
34
- end
35
-
36
- #
37
- # Number of error states over all states
38
- #
39
- def error_ratio
40
- states.select{|s|s.error?}.size.to_f/state_count.to_f
41
- end
42
-
43
- #
44
- # Computes the depth of the automaton.
45
- #
46
- # The depth of an automaton is defined as the length of the longest shortest
47
- # path from the initial state to a state.
48
- #
49
- # This method has a side effect on state marks, as it keeps the depth of
50
- # each state as a mark under _key_, which defaults to :depth.
51
- #
52
- def depth(key = :depth)
53
- algo = Stamina::Utils::Decorate.new(key)
54
- algo.set_suppremum do |d0,d1|
55
- # Here, unreached state has the max value (i.e. nil is +INF)
56
- # and we look at the minimum depth for each state
57
- if d0.nil?
58
- d1
59
- elsif d1.nil?
60
- d0
61
- else
62
- (d0 <= d1 ? d0 : d1)
63
- end
64
- end
65
- algo.set_propagate {|d,e| d+1 }
66
- algo.execute(self, nil, 0)
67
- deepest = states.max do |s0,s1|
68
- # Here, we do not take unreachable states into account
69
- # so that -1 is taken when nil is encountered
70
- (s0[:depth] || -1) <=> (s1[:depth] || -1)
71
- end
72
- deepest[:depth]
73
- end
74
-
75
- end # module Metrics
76
- include Metrics
77
- end # class Automaton
78
- end # module Stamina
@@ -1,25 +0,0 @@
1
- module Stamina
2
- class Automaton
3
-
4
- #
5
- # Checks if this automaton is minimal.
6
- #
7
- def minimal?
8
- self.minimize <=> self.complete
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
-
21
- end # class Automaton
22
- end # module Stamina
23
- require 'stamina/automaton/minimize/hopcroft'
24
- require 'stamina/automaton/minimize/pitchies'
25
-
@@ -1,116 +0,0 @@
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
- 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
- end
58
-
59
- # Computes the initial partition
60
- def initial_partition
61
- p = [Set.new, Set.new]
62
- @automaton.states.each do |s|
63
- (s.accepting? ? p[0] : p[1]) << s
64
- end
65
- p.reject{|g| g.empty?}
66
- end
67
-
68
- # Main method of the algorithm
69
- def main
70
- # Partition states a first time according to accepting/non accepting
71
- @partition = initial_partition # P in pseudo code
72
- @worklist = @partition.dup # W in pseudo code
73
-
74
- # Until a block needs to be refined
75
- until @worklist.empty?
76
- refined = @worklist.pop
77
-
78
- # We compute the reverse delta on the group and look at the groups
79
- rdelta = reverse_delta(refined)
80
- rdelta.each_pair do |symbol, sources| # sources is la in pseudo code
81
-
82
- # Find blocks to be refined
83
- @partition.dup.each_with_index do |block, index| # block is R in pseudo code
84
- next if block.subset?(sources)
85
- intersection = block & sources # R1 in pseudo code
86
- next if intersection.empty?
87
- difference = block - intersection # R2 in pseudo code
88
-
89
- # replace R in P with R1 and R2
90
- @partition[index] = intersection
91
- @partition << difference
92
-
93
- # Adds the new blocks as to be refined
94
- if @worklist.include?(block)
95
- @worklist.delete(block)
96
- @worklist << intersection << difference
97
- else
98
- @worklist << (intersection.size <= difference.size ? intersection : difference)
99
- end
100
- end # @partition.each
101
-
102
- end # rdelta.each_pair
103
- end # until @worklist.empty?
104
-
105
- compute_minimal_dfa(@partition)
106
- end # def main
107
-
108
- # Execute the minimizer
109
- def self.execute(automaton, options={})
110
- Hopcroft.new(automaton.strip.complete!, options).main
111
- end
112
-
113
- end # class Hopcroft
114
- end # module Minimize
115
- end # class Automaton
116
- end # module Stamina
@@ -1,64 +0,0 @@
1
- module Stamina
2
- class Automaton
3
- module Minimize
4
- class Pitchies
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
- def minimized_dfa(oldfa, nb_states, partition)
13
- Automaton.new(false) do |newfa|
14
- # Add the number of states, with default marks
15
- newfa.add_n_states(nb_states, {:initial => false, :accepting => false, :error => false})
16
-
17
- # Refine the marks using the source dfa as reference
18
- partition.each_with_index do |block, state_index|
19
- source = oldfa.ith_state(state_index)
20
- target = newfa.ith_state(block)
21
- target.initial! if source.initial?
22
- target.accepting! if source.accepting?
23
- target.error! if source.error?
24
- end
25
-
26
- # Now, create the transitions
27
- partition.each_with_index do |block, state_index|
28
- source = oldfa.ith_state(state_index)
29
- target = newfa.ith_state(block)
30
- source.out_edges.each do |edge|
31
- where = partition[edge.target.index]
32
- if target.dfa_step(edge.symbol) == nil
33
- newfa.connect(target, where, edge.symbol)
34
- end
35
- end
36
- end
37
- end
38
- end
39
-
40
- def main
41
- alph, states = @automaton.alphabet, @automaton.states
42
- old_nb_states = -1
43
- partition = states.collect{|s| s.accepting? ? 1 : 0}
44
- until (nb_states = partition.uniq.size) == old_nb_states
45
- old_nb_states = nb_states
46
- alph.each do |symbol|
47
- reached = states.collect{|s| partition[s.dfa_step(symbol).index]}
48
- rehash = Hash.new{|h,k| h[k] = h.size}
49
- partition = partition.zip(reached).collect{|pair| rehash[pair]}
50
- end
51
- end
52
- minimized_dfa(@automaton, nb_states, partition)
53
- end
54
-
55
- # Execute the minimizer
56
- def self.execute(automaton, options={})
57
- Pitchies.new(automaton.strip.complete!, options).main
58
- end
59
-
60
- end # class Pitchies
61
- end # module Minimize
62
- end # class Automaton
63
- end # module Stamina
64
-
@@ -1,16 +0,0 @@
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
@@ -1,363 +0,0 @@
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
- include Stamina::Classifier
361
- end # class Automaton
362
- end # end Stamina
363
-