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.
- checksums.yaml +4 -4
- data/.rubocop.yml +11 -437
- data/CHANGELOG.md +9 -0
- data/Gemfile +0 -2
- data/LICENSE.txt +1 -1
- data/README.md +2 -3
- data/Rakefile +0 -2
- data/appveyor.yml +10 -10
- data/examples/inductive_english.rb +35 -0
- data/examples/integer_sample.rb +0 -1
- data/examples/porridge.rb +9 -9
- data/examples/word_sample.rb +4 -5
- data/lib/sequitur/constants.rb +7 -4
- data/lib/sequitur/digram.rb +11 -11
- data/lib/sequitur/dynamic_grammar.rb +12 -12
- data/lib/sequitur/formatter/base_formatter.rb +2 -2
- data/lib/sequitur/formatter/base_text.rb +8 -9
- data/lib/sequitur/formatter/debug.rb +10 -4
- data/lib/sequitur/grammar_visitor.rb +7 -7
- data/lib/sequitur/production.rb +203 -205
- data/lib/sequitur/production_ref.rb +18 -20
- data/lib/sequitur/sequitur_grammar.rb +135 -137
- data/lib/sequitur/symbol_sequence.rb +29 -32
- data/lib/sequitur.rb +6 -6
- data/sig/lib/sequitur/constants.rbs +10 -0
- data/sig/lib/sequitur/digram.rbs +37 -0
- data/sig/lib/sequitur/dynamic_grammar.rbs +58 -0
- data/sig/lib/sequitur/formatter/base_formatter.rbs +20 -0
- data/sig/lib/sequitur/formatter/base_text.rbs +62 -0
- data/sig/lib/sequitur/formatter/debug.rbs +89 -0
- data/sig/lib/sequitur/production.rbs +120 -0
- data/sig/lib/sequitur/production_ref.rbs +73 -0
- data/sig/lib/sequitur/sequitur_grammar.rbs +55 -0
- data/sig/lib/sequitur/symbol_sequence.rbs +83 -0
- data/sig/lib/sequitur.rbs +9 -0
- data/spec/sequitur/digram_spec.rb +13 -12
- data/spec/sequitur/dynamic_grammar_spec.rb +5 -11
- data/spec/sequitur/formatter/base_text_spec.rb +70 -72
- data/spec/sequitur/formatter/debug_spec.rb +90 -92
- data/spec/sequitur/grammar_visitor_spec.rb +70 -71
- data/spec/sequitur/production_ref_spec.rb +92 -92
- data/spec/sequitur/production_spec.rb +30 -34
- data/spec/sequitur/sequitur_grammar_spec.rb +47 -46
- data/spec/sequitur/symbol_sequence_spec.rb +102 -105
- data/spec/spec_helper.rb +0 -1
- metadata +28 -17
- 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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
89
|
+
|
90
|
+
diagnosis
|
90
91
|
end
|
91
92
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
153
|
-
|
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
|
-
# @
|
34
|
+
# @[true / false] true only if the sequence has no symbol in it.
|
35
35
|
def empty?
|
36
|
-
|
36
|
+
symbols.empty?
|
37
37
|
end
|
38
38
|
|
39
39
|
# Count the number of elements in the sequence.
|
40
|
-
# @
|
40
|
+
# @[Fixnum] the number of elements
|
41
41
|
def size
|
42
|
-
|
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
|
-
|
58
|
+
symbols[anIndex]
|
59
59
|
end
|
60
60
|
|
61
61
|
# Equality testing.
|
62
|
-
# @param other [SymbolSequence
|
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
|
-
|
67
|
+
true if object_id == other.object_id
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
# @
|
80
|
+
# @[Array of ProductionRef]
|
83
81
|
def references
|
84
82
|
@memo_references ||= symbols.select { |symb| symb.is_a?(ProductionRef) }
|
85
|
-
|
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
|
-
# @
|
88
|
+
# @[Array of ProductionRef]
|
91
89
|
def references_of(aProduction)
|
92
|
-
|
90
|
+
[] if references.empty?
|
93
91
|
|
94
|
-
|
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
|
-
# @
|
97
|
+
# @[String]
|
101
98
|
def to_string
|
102
99
|
rhs_text = symbols.map do |elem|
|
103
100
|
case elem
|
104
|
-
|
105
|
-
|
101
|
+
when String then "'#{elem}'"
|
102
|
+
else elem.to_s
|
106
103
|
end
|
107
104
|
end
|
108
105
|
|
109
|
-
|
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 [
|
122
|
+
# @param index [Integer] the position of a two symbol sequence to be replaced
|
126
123
|
# by the production
|
127
|
-
# @param aProduction [Production
|
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 [
|
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 [
|
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
|
-
|
23
|
-
|
24
|
-
|
22
|
+
when String then tokens.chars
|
23
|
+
when Enumerator then tokens
|
24
|
+
else tokens.to_enum
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
SequiturGrammar.new(input_sequence)
|
28
28
|
end
|
29
29
|
end # module
|
30
30
|
|
@@ -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
|