stamina 0.4.0 → 0.5.0

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