sequitur 0.1.23 → 0.1.25

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +11 -437
  3. data/CHANGELOG.md +9 -0
  4. data/Gemfile +0 -2
  5. data/LICENSE.txt +1 -1
  6. data/README.md +2 -3
  7. data/Rakefile +0 -2
  8. data/appveyor.yml +10 -10
  9. data/examples/inductive_english.rb +35 -0
  10. data/examples/integer_sample.rb +0 -1
  11. data/examples/porridge.rb +9 -9
  12. data/examples/word_sample.rb +4 -5
  13. data/lib/sequitur/constants.rb +7 -4
  14. data/lib/sequitur/digram.rb +11 -11
  15. data/lib/sequitur/dynamic_grammar.rb +12 -12
  16. data/lib/sequitur/formatter/base_formatter.rb +2 -2
  17. data/lib/sequitur/formatter/base_text.rb +8 -9
  18. data/lib/sequitur/formatter/debug.rb +10 -4
  19. data/lib/sequitur/grammar_visitor.rb +7 -7
  20. data/lib/sequitur/production.rb +203 -205
  21. data/lib/sequitur/production_ref.rb +18 -20
  22. data/lib/sequitur/sequitur_grammar.rb +135 -137
  23. data/lib/sequitur/symbol_sequence.rb +29 -32
  24. data/lib/sequitur.rb +6 -6
  25. data/sig/lib/sequitur/constants.rbs +10 -0
  26. data/sig/lib/sequitur/digram.rbs +37 -0
  27. data/sig/lib/sequitur/dynamic_grammar.rbs +58 -0
  28. data/sig/lib/sequitur/formatter/base_formatter.rbs +20 -0
  29. data/sig/lib/sequitur/formatter/base_text.rbs +62 -0
  30. data/sig/lib/sequitur/formatter/debug.rbs +89 -0
  31. data/sig/lib/sequitur/production.rbs +120 -0
  32. data/sig/lib/sequitur/production_ref.rbs +73 -0
  33. data/sig/lib/sequitur/sequitur_grammar.rbs +55 -0
  34. data/sig/lib/sequitur/symbol_sequence.rbs +83 -0
  35. data/sig/lib/sequitur.rbs +9 -0
  36. data/spec/sequitur/digram_spec.rb +13 -12
  37. data/spec/sequitur/dynamic_grammar_spec.rb +5 -11
  38. data/spec/sequitur/formatter/base_text_spec.rb +70 -72
  39. data/spec/sequitur/formatter/debug_spec.rb +90 -92
  40. data/spec/sequitur/grammar_visitor_spec.rb +70 -71
  41. data/spec/sequitur/production_ref_spec.rb +92 -92
  42. data/spec/sequitur/production_spec.rb +30 -34
  43. data/spec/sequitur/sequitur_grammar_spec.rb +47 -46
  44. data/spec/sequitur/symbol_sequence_spec.rb +102 -105
  45. data/spec/spec_helper.rb +0 -1
  46. metadata +28 -17
  47. data/.travis.yml +0 -29
@@ -2,155 +2,153 @@
2
2
 
3
3
  require_relative 'dynamic_grammar'
4
4
 
5
-
6
5
  module Sequitur # Module for classes implementing the Sequitur algorithm
7
- # Specialization of the DynamicGrammar class.
8
- # A Sequitur grammar is a context-free grammar that is entirely built
9
- # from a sequence of input tokens through the Sequitur algorithm.
10
- class SequiturGrammar < DynamicGrammar
11
- # Build the grammar from an enumerator of tokens.
12
- # @param anEnum [Enumerator] an enumerator that will iterate
13
- # over the input tokens.
14
- def initialize(anEnum)
15
- super()
16
- # Make start production compliant with utility rule
17
- 2.times { start.incr_refcount }
18
-
19
- # Read the input sequence and apply the Sequitur algorithm
20
- anEnum.each do |a_token|
21
- add_token(a_token)
22
- enforce_rules
6
+ # Specialization of the DynamicGrammar class.
7
+ # A Sequitur grammar is a context-free grammar that is entirely built
8
+ # from a sequence of input tokens through the Sequitur algorithm.
9
+ class SequiturGrammar < DynamicGrammar
10
+ # Build the grammar from an enumerator of tokens.
11
+ # @param anEnum [Enumerator] an enumerator that will iterate
12
+ # over the input tokens.
13
+ def initialize(anEnum)
14
+ super()
15
+ # Make start production compliant with utility rule
16
+ 2.times { start.incr_refcount }
17
+
18
+ # Read the input sequence and apply the Sequitur algorithm
19
+ anEnum.each do |a_token|
20
+ add_token(a_token)
21
+ enforce_rules
22
+ end
23
23
  end
24
- end
25
-
26
- private
27
-
28
- # Struct used for internal purposes
29
- CollisionDiagnosis = Struct.new(
30
- :collision_found, # true if collision detected
31
- :digram, # The digram involved in a collision
32
- :productions) # The productions where the digram occurs
33
-
34
-
35
-
36
- # Assuming that a new input token was added to the start production,
37
- # enforce the digram unicity and rule utility rules
38
- # begin
39
- # if a digram D occurs twice in the grammar then
40
- # add a production P : D (if not already there)
41
- # replace both Ds with R (reduction step).
42
- # end
43
- # if a production P : RHS in referenced only once then
44
- # replace P by its RHS (derivation step)
45
- # remove P from grammar
46
- # end
47
- # end until digram unicity and rule utility are met
48
- def enforce_rules
49
- loop do
50
- unicity_diagnosis = detect_collision if unicity_diagnosis.nil?
51
- restore_unicity(unicity_diagnosis) if unicity_diagnosis.collision_found
52
-
53
- prod_index = detect_useless_production
54
- restore_utility(prod_index) unless prod_index.nil?
55
-
56
- unicity_diagnosis = detect_collision
57
- prod_index = detect_useless_production
58
- break unless unicity_diagnosis.collision_found || !prod_index.nil?
24
+
25
+ private
26
+
27
+ # Struct used for internal purposes
28
+ CollisionDiagnosis = Struct.new(
29
+ :collision_found, # true if collision detected
30
+ :digram, # The digram involved in a collision
31
+ :productions # The productions where the digram occurs
32
+ )
33
+
34
+ # Assuming that a new input token was added to the start production,
35
+ # enforce the digram unicity and rule utility rules
36
+ # begin
37
+ # if a digram D occurs twice in the grammar then
38
+ # add a production P : D (if not already there)
39
+ # replace both Ds with R (reduction step).
40
+ # end
41
+ # if a production P : RHS in referenced only once then
42
+ # replace P by its RHS (derivation step)
43
+ # remove P from grammar
44
+ # end
45
+ # end until digram unicity and rule utility are met
46
+ def enforce_rules
47
+ loop do
48
+ unicity_diagnosis = detect_collision if unicity_diagnosis.nil?
49
+ restore_unicity(unicity_diagnosis) if unicity_diagnosis.collision_found
50
+
51
+ prod_index = detect_useless_production
52
+ restore_utility(prod_index) unless prod_index.nil?
53
+
54
+ unicity_diagnosis = detect_collision
55
+ prod_index = detect_useless_production
56
+ break unless unicity_diagnosis.collision_found || !prod_index.nil?
57
+ end
59
58
  end
60
- end
61
-
62
- # Check whether a digram is used twice in the grammar.
63
- # Return an empty Hash if each digram appears once.
64
- # Otherwise return a Hash with a pair of the form: digram => [Pi, Pk]
65
- # Where Pi, Pk are two productions where the digram occurs.
66
- def detect_collision
67
- diagnosis = CollisionDiagnosis.new(false)
68
- found_so_far = {}
69
- productions.each do |a_prod|
70
- prod_digrams = a_prod.digrams
71
- prod_digrams.each do |a_digr|
72
- its_key = a_digr.key
73
- if found_so_far.include? its_key
74
- orig_digr = found_so_far[its_key]
75
- # Disregard sequence like a a a
76
- if (orig_digr.production == a_prod) && a_digr.repeating? &&
77
- (orig_digr == a_digr)
78
- next
79
- end
80
59
 
81
- diagnosis.digram = orig_digr
82
- diagnosis.productions = [orig_digr.production, a_prod]
83
- diagnosis.collision_found = true
84
- break
85
- else
86
- found_so_far[its_key] = a_digr
60
+ # Check whether a digram is used twice in the grammar.
61
+ # Return an empty Hash if each digram appears once.
62
+ # Otherwise return a Hash with a pair of the form: digram => [Pi, Pk]
63
+ # Where Pi, Pk are two productions where the digram occurs.
64
+ def detect_collision
65
+ diagnosis = CollisionDiagnosis.new(false)
66
+ found_so_far = {}
67
+ productions.each do |a_prod|
68
+ prod_digrams = a_prod.digrams
69
+ prod_digrams.each do |a_digr|
70
+ its_key = a_digr.key
71
+ if found_so_far.include? its_key
72
+ orig_digr = found_so_far[its_key]
73
+ # Disregard sequence like a a a
74
+ if (orig_digr.production == a_prod) && a_digr.repeating? &&
75
+ (orig_digr == a_digr)
76
+ next
77
+ end
78
+
79
+ diagnosis.digram = orig_digr
80
+ diagnosis.productions = [orig_digr.production, a_prod]
81
+ diagnosis.collision_found = true
82
+ break
83
+ else
84
+ found_so_far[its_key] = a_digr
85
+ end
87
86
  end
87
+ break if diagnosis.collision_found
88
88
  end
89
- break if diagnosis.collision_found
89
+
90
+ diagnosis
90
91
  end
91
92
 
92
- return diagnosis
93
- end
94
-
95
- # When a collision diagnosis indicates that a given
96
- # digram d occurs twice in the grammar
97
- # Then create a new production that will have
98
- # the symbols of d as its rhs members.
99
- def restore_unicity(aDiagnosis)
100
- prods = aDiagnosis.productions
101
- if prods.any?(&:single_digram?)
102
- (simple, compound) = prods.partition(&:single_digram?)
103
- compound[0].reduce_step(simple[0])
104
- else
105
- # Create a new production with the digram's symbols as its
106
- # sole rhs members.
107
- new_prod = build_production_for(aDiagnosis.digram)
108
- prods[0].reduce_step(new_prod)
109
- prods[1].reduce_step(new_prod) unless prods[1] == prods[0]
93
+ # When a collision diagnosis indicates that a given
94
+ # digram d occurs twice in the grammar
95
+ # Then create a new production that will have
96
+ # the symbols of d as its rhs members.
97
+ def restore_unicity(aDiagnosis)
98
+ prods = aDiagnosis.productions
99
+ if prods.any?(&:single_digram?)
100
+ (simple, compound) = prods.partition(&:single_digram?)
101
+ compound[0].reduce_step(simple[0])
102
+ else
103
+ # Create a new production with the digram's symbols as its
104
+ # sole rhs members.
105
+ new_prod = build_production_for(aDiagnosis.digram)
106
+ prods[0].reduce_step(new_prod)
107
+ prods[1].reduce_step(new_prod) unless prods[1] == prods[0]
108
+ end
110
109
  end
111
- end
112
-
113
- # Return a production that is used less than twice in the grammar.
114
- def detect_useless_production
115
- useless = productions.index { |prod| prod.refcount < 2 }
116
- useless = nil if useless&.zero?
117
-
118
- return useless
119
- end
120
-
121
- # Given the passed production P is referenced only once.
122
- # Then replace P by its RHS where it is referenced.
123
- # And delete P
124
- def restore_utility(prod_index)
125
- # Retrieve useless prod from its index
126
- useless_prod = productions[prod_index]
127
-
128
- # Retrieve production referencing useless one
129
- referencing = nil
130
- productions.reverse_each do |a_prod|
131
- # Next line assumes non-recursive productions
132
- next if a_prod == useless_prod
133
-
134
- refs = a_prod.references_of(useless_prod)
135
- next if refs.empty?
136
-
137
- referencing = a_prod
138
- break
110
+
111
+ # Return a production that is used less than twice in the grammar.
112
+ def detect_useless_production
113
+ useless = productions.index { |prod| prod.refcount < 2 }
114
+ useless = nil if useless&.zero?
115
+
116
+ useless
139
117
  end
140
118
 
141
- referencing.derive_step(useless_prod)
142
- remove_production(prod_index)
143
- end
119
+ # Given the passed production P is referenced only once.
120
+ # Then replace P by its RHS where it is referenced.
121
+ # And delete P
122
+ def restore_utility(prod_index)
123
+ # Retrieve useless prod from its index
124
+ useless_prod = productions[prod_index]
125
+
126
+ # Retrieve production referencing useless one
127
+ referencing = nil
128
+ productions.reverse_each do |a_prod|
129
+ # Next line assumes non-recursive productions
130
+ next if a_prod == useless_prod
131
+
132
+ refs = a_prod.references_of(useless_prod)
133
+ next if refs.empty?
134
+
135
+ referencing = a_prod
136
+ break
137
+ end
138
+
139
+ referencing.derive_step(useless_prod)
140
+ remove_production(prod_index)
141
+ end
144
142
 
145
- # Create a new production that will have the symbols from digram
146
- # as its rhs members.
147
- def build_production_for(aDigram)
148
- new_prod = Production.new
149
- aDigram.symbols.each { |sym| new_prod.append_symbol(sym) }
150
- add_production(new_prod)
143
+ # Create a new production that will have the symbols from digram
144
+ # as its rhs members.
145
+ def build_production_for(aDigram)
146
+ new_prod = Production.new
147
+ aDigram.symbols.each { |sym| new_prod.append_symbol(sym) }
148
+ add_production(new_prod)
151
149
 
152
- return new_prod
153
- end
154
- end # class
150
+ new_prod
151
+ end
152
+ end # class
155
153
  end # module
156
154
  # End of file
@@ -4,7 +4,7 @@ module Sequitur # Module for classes implementing the Sequitur algorithm
4
4
  # Represents a sequence (concatenation) of grammar symbols
5
5
  # as they appear in rhs of productions
6
6
  class SymbolSequence
7
- # The sequence of symbols itself
7
+ # @return [Array] The sequence of symbols itself
8
8
  attr_reader(:symbols)
9
9
 
10
10
  # Create an empty sequence
@@ -31,15 +31,15 @@ module Sequitur # Module for classes implementing the Sequitur algorithm
31
31
  end
32
32
 
33
33
  # Tell whether the sequence is empty.
34
- # @return [true / false] true only if the sequence has no symbol in it.
34
+ # @[true / false] true only if the sequence has no symbol in it.
35
35
  def empty?
36
- return symbols.empty?
36
+ symbols.empty?
37
37
  end
38
38
 
39
39
  # Count the number of elements in the sequence.
40
- # @return [Fixnum] the number of elements
40
+ # @[Fixnum] the number of elements
41
41
  def size
42
- return symbols.size
42
+ symbols.size
43
43
  end
44
44
 
45
45
  # Append a grammar symbol at the end of the sequence.
@@ -55,58 +55,55 @@ module Sequitur # Module for classes implementing the Sequitur algorithm
55
55
  # Retrieve the element from the sequence at given position.
56
56
  # @param anIndex [Fixnum] A zero-based index of the element to access.
57
57
  def [](anIndex)
58
- return symbols[anIndex]
58
+ symbols[anIndex]
59
59
  end
60
60
 
61
61
  # Equality testing.
62
- # @param other [SymbolSequence or Array] the other other sequence
62
+ # @param other [SymbolSequence, Array] the other other sequence
63
63
  # to compare to.
64
- # @return true when an item from self equals the corresponding
64
+ # @return [TrueClass, FalseClass] true when an item from self equals the corresponding
65
65
  # item from 'other'
66
66
  def ==(other)
67
- return true if object_id == other.object_id
67
+ true if object_id == other.object_id
68
68
 
69
- same = case other
70
- when SymbolSequence
71
- symbols == other.symbols
72
- when Array
73
- symbols == other
74
- else
75
- false
76
- end
77
-
78
- return same
69
+ case other
70
+ when SymbolSequence
71
+ symbols == other.symbols
72
+ when Array
73
+ symbols == other
74
+ else
75
+ false
76
+ end
79
77
  end
80
78
 
81
79
  # Select the references to production appearing in the rhs.
82
- # @return [Array of ProductionRef]
80
+ # @[Array of ProductionRef]
83
81
  def references
84
82
  @memo_references ||= symbols.select { |symb| symb.is_a?(ProductionRef) }
85
- return @memo_references
83
+ @memo_references
86
84
  end
87
85
 
88
86
  # Select the references of the given production appearing in the rhs.
89
87
  # @param aProduction [Production]
90
- # @return [Array of ProductionRef]
88
+ # @[Array of ProductionRef]
91
89
  def references_of(aProduction)
92
- return [] if references.empty?
90
+ [] if references.empty?
93
91
 
94
- result = references.select { |a_ref| a_ref == aProduction }
95
- return result
92
+ references.select { |a_ref| a_ref == aProduction }
96
93
  end
97
94
 
98
95
  # Emit a text representation of the symbol sequence.
99
96
  # Text is of the form: space-separated sequence of symbols.
100
- # @return [String]
97
+ # @[String]
101
98
  def to_string
102
99
  rhs_text = symbols.map do |elem|
103
100
  case elem
104
- when String then "'#{elem}'"
105
- else elem.to_s
101
+ when String then "'#{elem}'"
102
+ else elem.to_s
106
103
  end
107
104
  end
108
105
 
109
- return rhs_text.join(' ')
106
+ rhs_text.join(' ')
110
107
  end
111
108
 
112
109
  # Insert at position the elements from another sequence.
@@ -122,9 +119,9 @@ module Sequitur # Module for classes implementing the Sequitur algorithm
122
119
  # Given that the production P passed as argument has exactly 2 symbols
123
120
  # in its rhs s1 s2, substitute in the rhs of self all occurrences of
124
121
  # s1 s2 by a reference to P.
125
- # @param index [Fixnum] the position of a two symbol sequence to be replaced
122
+ # @param index [Integer] the position of a two symbol sequence to be replaced
126
123
  # by the production
127
- # @param aProduction [Production or ProductionRef] a production that
124
+ # @param aProduction [Production, ProductionRef] a production that
128
125
  # consists exactly of one digram (= 2 symbols).
129
126
  def reduce_step(index, aProduction)
130
127
  if symbols[index].is_a?(ProductionRef)
@@ -144,7 +141,7 @@ module Sequitur # Module for classes implementing the Sequitur algorithm
144
141
  end
145
142
 
146
143
  # Remove the element at given position
147
- # @param position [Fixnum] a zero-based index.
144
+ # @param position [Integer] a zero-based index.
148
145
  def delete_at(position)
149
146
  invalidate_refs if symbols[position].is_a?(ProductionRef)
150
147
  symbols.delete_at(position)
data/lib/sequitur.rb CHANGED
@@ -9,22 +9,22 @@ require_relative './sequitur/sequitur_grammar'
9
9
  require_relative './sequitur/formatter/debug'
10
10
  require_relative './sequitur/formatter/base_text'
11
11
 
12
-
12
+ # Namespace for the classes of sequitur gem.
13
13
  module Sequitur
14
14
  # Build a Sequitur-generated grammar based on the sequence of input tokens.
15
15
  #
16
- # @param tokens [StringOrEnumerator] The input sequence of input tokens.
16
+ # @param tokens [String, Enumerator] The input sequence of input tokens.
17
17
  # Can be a sequence of characters (i.e. a String) or an Enumerator.
18
18
  # Tokens returned by enumerator should respond to the :hash message.
19
19
  # @return [SequiturGrammar] a grammar that encodes the input.
20
20
  def self.build_from(tokens)
21
21
  input_sequence = case tokens
22
- when String then tokens.chars
23
- when Enumerator then tokens
24
- else tokens.to_enum
22
+ when String then tokens.chars
23
+ when Enumerator then tokens
24
+ else tokens.to_enum
25
25
  end
26
26
 
27
- return SequiturGrammar.new(input_sequence)
27
+ SequiturGrammar.new(input_sequence)
28
28
  end
29
29
  end # module
30
30
 
@@ -0,0 +1,10 @@
1
+ module Sequitur
2
+ # @return [String] The version number of the gem.
3
+ Version: String
4
+
5
+ # @return [String] Brief description of the gem.
6
+ Description: String
7
+
8
+ # @return [String] The start folder of Sequitur.
9
+ RootDir: String
10
+ end
@@ -0,0 +1,37 @@
1
+ module Sequitur
2
+ # In linguistics, a digram is a sequence of two letters.
3
+ # In Sequitur, a digram is a sequence of two consecutive symbols that
4
+ # appear in a production rule. Each symbol in a digram
5
+ # can be a terminal or not.
6
+ class Digram
7
+ # The sequence of two consecutive grammar symbols.
8
+ # @return [Array<String, Symbol>] The two symbols should respond to the :hash message.
9
+ attr_reader symbols: Array<String|Symbol>
10
+
11
+ # @return [String] An unique hash key of the digram
12
+ attr_reader key: String
13
+
14
+ # @return [Sequitur::Production] The production in which the digram occurs
15
+ attr_reader production: Sequitur::Production
16
+
17
+ # Constructor.
18
+ # A digram represents a sequence of two symbols
19
+ # (that appears in a rhs of a production).
20
+ # Terminal symbols must respond to the :hash message.
21
+ # @param symbol1 [String, Symbol] First element of the digram
22
+ # @param symbol2 [String, Symbol] Second element of the digram
23
+ # @param aProduction [Sequitur::Production] Production in which the RHS
24
+ # the sequence symbol1 symbol2 appears.
25
+ def initialize: ((String | Symbol) symbol1, (String | Symbol) symbol2, Sequitur::Production aProduction) -> void
26
+
27
+ # Equality testing.
28
+ # true iff keys of both digrams are equal, false otherwise
29
+ # @param other [Sequitur::Digram] another to compare with
30
+ # @return [TrueClass, FalseClass]
31
+ def ==: (Sequitur::Digram other) -> bool
32
+
33
+ # Does the digram consists of twice the same symbols?
34
+ # @return [TrueClass, FalseClass] true when symbols.first == symbols.last
35
+ def repeating?: () -> bool
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ module Sequitur
2
+ # A dynamic grammar is a context-free grammar that can be built incrementally.
3
+ # Formally, a grammar has:
4
+ # One start production
5
+ # Zero or more other productions
6
+ # Each production has a rhs that is a sequence of grammar symbols.
7
+ # Grammar symbols are categorized into
8
+ # -terminal symbols (i.e. String, Ruby Symbol,...)
9
+ # -non-terminal symbols (i.e. ProductionRef)
10
+ class DynamicGrammar
11
+ # @return [Sequitur::Production] Link to the start production.
12
+ attr_reader start: Sequitur::Production
13
+
14
+ # @return [Array<Sequitur::Production>] The set of production rules of the grammar
15
+ attr_reader productions: Array[Sequitur::Production]
16
+
17
+ # @return [TrueClass, FalseClass] Trace the execution of the algorithm.
18
+ attr_accessor trace: bool
19
+
20
+ # Constructor.
21
+ # Build a grammar with one empty rule as start/start rule.
22
+ def initialize: () -> void
23
+
24
+ # Emit a text representation of the grammar.
25
+ # Each production rule is emitted per line.
26
+ # @return [String]
27
+ def to_string: () -> String
28
+
29
+ # Add a given production to the grammar.
30
+ # @param aProduction [Sequitur::Production]
31
+ # @return [Array<Sequitur::Production>]
32
+ def add_production: (Sequitur::Production aProduction) -> Array[Sequitur::Production]
33
+
34
+ # Remove a production with given index from the grammar
35
+ # @param anIndex [Integer]
36
+ # @return [Sequitur::Production] the production removed from the grammar.
37
+ def remove_production: (Integer anIndex) -> Sequitur::Production
38
+
39
+ # Add the given token to the grammar.
40
+ # Append the token to the rhs of the start/start rule.
41
+ # @param aToken [Object] input token to add
42
+ def add_token: (untyped aToken) -> untyped
43
+
44
+ # Part of the 'visitee' role in the Visitor design pattern.
45
+ # A visitee is expected to accept the visit from a visitor object
46
+ # @param aVisitor [Sequitur::GrammarVisitor] the visitor object
47
+ def accept: (Sequitur::GrammarVisitor aVisitor) -> untyped
48
+
49
+ # Factory method. Returns a visitor for this grammar.
50
+ # @return [Sequitur::GrammarVisitor]
51
+ def visitor: () -> Sequitur::GrammarVisitor
52
+
53
+ # Append a given symbol to the rhs of passed production.
54
+ # @param aProduction [Production]
55
+ # @param aSymbol [Object]
56
+ def append_symbol_to: (Production aProduction, untyped aSymbol) -> untyped
57
+ end
58
+ end
@@ -0,0 +1,20 @@
1
+ module Sequitur
2
+ # Namespace dedicated to grammar formatters.
3
+ module Formatter
4
+ # Superclass for grammar formatters.
5
+ class BaseFormatter
6
+ # The IO output stream in which the formatter's result will be sent.
7
+ attr_accessor output: untyped
8
+
9
+ # Constructor.
10
+ # @param anIO [IO] an output IO where the formatter's result will
11
+ # be placed.
12
+ def initialize: (untyped anIO) -> void
13
+
14
+ # Given a grammar or a grammar visitor, perform the visit
15
+ # and render the visit events in the output stream.
16
+ # @param aGrmOrVisitor [DynamicGrammar, GrammarVisitor]
17
+ def render: ((DynamicGrammar | GrammarVisitor) aGrmOrVisitor) -> untyped
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,62 @@
1
+ module Sequitur
2
+ module Formatter
3
+ # A formatter class that can render a dynamic grammar in plain text.
4
+ # @example
5
+ # some_grammar = ... # Points to a DynamicGrammar-like object
6
+ # # Output the result to the standard console output
7
+ # formatter = Sequitur::Formatter::BaseText.new(STDOUT)
8
+ # # Render the grammar (through a visitor)
9
+ # formatter.run(some_grammar.visitor)
10
+ class BaseText < BaseFormatter
11
+ attr_reader prod_lookup: ::Hash[Production, Integer]
12
+
13
+ # Constructor.
14
+ # @param anIO [IO] The output stream to which the rendered grammar
15
+ # is written.
16
+ def initialize: (IO anIO) -> void
17
+
18
+ # Method called by a GrammarVisitor to which the formatter subscribed.
19
+ # Notification of a visit event: the visitor is about to visit a grammar
20
+ # @param aGrammar [DynamicGrammar]
21
+ def before_grammar: (DynamicGrammar aGrammar) -> untyped
22
+
23
+ # Method called by a GrammarVisitor to which the formatter subscribed.
24
+ # Notification of a visit event: the visitor is about to visit
25
+ # a production
26
+ # @param aProduction [Production]
27
+ def before_production: (Production aProduction) -> untyped
28
+
29
+ # Method called by a GrammarVisitor to which the formatter subscribed.
30
+ # Notification of a visit event: the visitor is about to visit
31
+ # the rhs of a production
32
+ # @param _ [Array]
33
+ def before_rhs: (::Array[untyped] _)
34
+
35
+ # Method called by a GrammarVisitor to which the formatter subscribed.
36
+ # Notification of a visit event: the visitor is about to visit
37
+ # a terminal symbol from the rhs of a production
38
+ # @param aSymbol [Object]
39
+ def before_terminal: (untyped aSymbol) -> untyped
40
+
41
+ # Method called by a GrammarVisitor to which the formatter subscribed.
42
+ # Notification of a visit event: the visitor is about to visit
43
+ # a non-terminal (= an allusion to a production) in the rhs of a
44
+ # production
45
+ # @param aProduction [Production] a production occurring in the rhs
46
+ def before_non_terminal: (Production aProduction) -> untyped
47
+
48
+ # Method called by a GrammarVisitor to which the formatter subscribed.
49
+ # Notification of a visit event: the visitor complete the visit
50
+ # of a production
51
+ # @param _ [Production]
52
+ def after_production: (Production _) -> untyped
53
+
54
+ private
55
+
56
+ # Generate a name of a given production.
57
+ # @param aProduction [Production]
58
+ # @return [String]
59
+ def prod_name: (Production aProduction) -> String
60
+ end
61
+ end
62
+ end