sequitur 0.1.23 → 0.1.25

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