sequitur 0.0.14 → 0.1.00

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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZTMxYzgwZmZkMzg1YTYzMjEzZTA4Yzg0NWY2YTdkZjk2ODI2ZDVhYg==
4
+ ZDAyNmJiYjE4YTQzNWMxZDI0ZWU2NTJhMGZiMTBiMmI0ZDg5MTBkOA==
5
5
  data.tar.gz: !binary |-
6
- NTgxM2M1MjY2MzNkYmE0NjhhNzBkOTIwNDdmOGM0OWYwMjNkOGMwZA==
6
+ MzI2YzIxMmZkYjExYTk1Yjc4ZTk0MDg0ZjczYjUxNDI4YWNkMWMxZg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- NmI0YzAyNTRhYmRmODc5MTgwZmYyYWNmMzExMmRhM2FjNWI0OGQyOTYxZmRk
10
- N2FkOTc2ZDU1Mzg3NjllN2M1ZmQyMDM2YWJjNGIzY2IwN2JjYzhiZjg2MGQ1
11
- ZjcxYmQxYzAwYmFlOTc2MTRmMmU4YzJmMDk5ZDU5OTZkZWFlYjE=
9
+ NzEwYTk3ZGFhOGU5ZDZkMWRlMWVmZWU5MDQxOTQ0YTk5ZDIzMGM4YjY1ODAw
10
+ NjQyYmU0ODEwZTRhYzM4ZjVmMWQ3ZDgyNjdiNDkzNjMyZmExZjFmN2Q0MTk3
11
+ ODE4NTQwYjBiZGQ5M2QyY2JlZTczMzIzMWI0YjUwYzVmNzkyNjg=
12
12
  data.tar.gz: !binary |-
13
- NDU3NDUxZjA1NDg0Njg5MzYzMmM0MDRiMDIzOGY2ZTlmMDIwYWNlMjJjYTNm
14
- ZjA0YTdhMjc0MTg3MTFkNjBmMWVlZTQzY2FlMDVmNjg5ODFmODMwNDMxY2U5
15
- OGI3ZGI4ZDFjZDFhMzFjMTFjZTg5MWYzMzc3Y2QwZWIyNmFiZDU=
13
+ MTYxOWNiMjg5MzRhY2JlOTdmYjJjZmI0YjVkYzA1MmIxMmY5NjQ4NDY2Zjc1
14
+ NWJlY2MyY2Q4ZWE2OTFmZGFiZWU1YjZkMjNmMzYzY2ZmYmM2MTE3MWVjODNj
15
+ ZDBlNzA3M2VjNmI3NTJhNTk5NGY0OGY4ODFlOGJhZDY1MDMyZmU=
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ### 0.1.00 / 2014-09-16
2
+ * [CHANGE] Version number bumped. Added grammar rendering through specialized formatters.
3
+
4
+
1
5
  ### 0.0.14 / 2014-09-10
2
6
  * [CHANGE] Removal of invariant checking methods in `SequiturGrammar` class. These caused polynomial slowdown.
3
7
 
data/README.md CHANGED
@@ -5,6 +5,7 @@ _Ruby gem implementing the Sequitur algorithm_
5
5
 
6
6
  [![Build Status](https://travis-ci.org/famished-tiger/Sequitur.svg?branch=master)](https://travis-ci.org/famished-tiger/Sequitur)
7
7
  [![Gem Version](https://badge.fury.io/rb/sequitur.svg)](http://badge.fury.io/rb/sequitur)
8
+ [![Dependency Status](https://gemnasium.com/famished-tiger/Sequitur.png)](https://gemnasium.com/famished-tiger/Sequitur)
8
9
 
9
10
 
10
11
  ### What is the Sequitur algorithm? ###
@@ -19,22 +20,30 @@ It detects repeated token patterns and can represent them in a compact way.
19
20
 
20
21
  ```ruby
21
22
 
22
- require 'sequitur' # Load the Sequitur library
23
-
24
- input_sequence = 'abcabdab' # Let's analyze this string
23
+ require 'sequitur' # Load the Sequitur library
24
+
25
+ input_sequence = 'abcabdabcabd' # Let's analyze this string
25
26
 
26
27
  # The SEQUITUR algorithm will detect the repeated 'ab' pattern
27
28
  # and will generate a context-free grammar that represents the input string
28
29
  grammar = Sequitur.build_from(input_sequence)
29
30
 
30
- # Display the grammar rules
31
- # Each rule is displayed with the format:
32
- # rule_id : a_sequence_of_grammar_symbols
31
+ # To display the grammar rules on the console output
32
+ # We use a formatter
33
+ formatter = Sequitur::Formatter::BaseText.new(STDOUT)
34
+
35
+ # Now render the rules. Each rule is displayed with the format:
36
+ # rule_id : a_sequence_grammar_symbols.
33
37
  # Where:
34
- # - rule_id is the object id of a rule (in decimal)
38
+ # - rule_id is either 'start' or a name like 'P_xxxx' (xxxx is a sequential number)
35
39
  # - a grammar symbol is either a terminal symbol
36
- # (i.e. a character from the input) or the id of a production
37
- puts grammar.to_string
40
+ # (i.e. a character from the input) or a rule id
41
+ formatter.render(grammar.visitor)
42
+
43
+ # Rendered output is:
44
+ # start : P_2 P_2.
45
+ # P_1 : a b.
46
+ # P_2 : P_1 c P_1 d.
38
47
  ```
39
48
 
40
49
  ### TODO: Add more documentation ###
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Sequitur # Module used as a namespace
5
5
  # The version number of the gem.
6
- Version = '0.0.14'
6
+ Version = '0.1.00'
7
7
 
8
8
  # Brief description of the gem.
9
9
  Description = 'Ruby implementation of the Sequitur algorithm'
@@ -1,4 +1,5 @@
1
1
  require_relative 'production'
2
+ require_relative 'grammar_visitor'
2
3
 
3
4
  module Sequitur # Module for classes implementing the Sequitur algorithm
4
5
 
@@ -8,7 +9,7 @@ class DynamicGrammar
8
9
 
9
10
  # The set of production rules of the grammar
10
11
  attr_reader(:productions)
11
-
12
+
12
13
  # nodoc Trace the execution of the algorithm.
13
14
  attr(:trace)
14
15
 
@@ -31,7 +32,7 @@ class DynamicGrammar
31
32
  end
32
33
 
33
34
 
34
- # Add a production to the grammar.
35
+ # Add a given production to the grammar.
35
36
  def add_production(aProduction)
36
37
  # TODO: remove output
37
38
  puts "Adding #{aProduction.object_id}" if trace
@@ -42,7 +43,7 @@ class DynamicGrammar
42
43
 
43
44
 
44
45
  # Remove a production from the grammar
45
- def delete_production(anIndex)
46
+ def remove_production(anIndex)
46
47
  puts "Before production removal #{productions[anIndex].object_id}" if trace
47
48
  puts to_string if trace
48
49
  prod = productions.delete_at(anIndex)
@@ -59,6 +60,19 @@ class DynamicGrammar
59
60
  append_symbol_to(root, aToken)
60
61
  end
61
62
 
63
+ # Part of the 'visitee' role.
64
+ # [aVisitor] a GrammarVisitor instance
65
+ def accept(aVisitor)
66
+ aVisitor.start_visit_grammar(self)
67
+ productions.each { |prod| prod.accept(aVisitor) }
68
+ aVisitor.end_visit_grammar(self)
69
+ end
70
+
71
+ # Factory method. Returns a visitor for this grammar.
72
+ def visitor()
73
+ return GrammarVisitor.new(self)
74
+ end
75
+
62
76
  protected
63
77
 
64
78
  def append_symbol_to(aProduction, aSymbol)
@@ -80,6 +94,17 @@ class DynamicGrammar
80
94
  end
81
95
  end
82
96
 
97
+
98
+ # Visitor pattern.
99
+ # A visitee is expected to accept the visit from a visitor object
100
+ def accept(aVisitor)
101
+ aVisitor.start_visit_grammar(self)
102
+
103
+ # Let's proceed with the visit of productions
104
+ productions.each { |a_prod| a_prod.accept(aVisitor) }
105
+
106
+ aVisitor.end_visit_grammar(self)
107
+ end
83
108
  end # class
84
109
 
85
110
  end # module
@@ -0,0 +1,74 @@
1
+ module Sequitur
2
+ module Formatter
3
+
4
+ # A formatter class that can render a dynamic grammar in plain text.
5
+ # Synopsis:
6
+ # some_grammar = ... # Points to a DynamicGrammar-like object
7
+ # Output the result to the standard console output
8
+ # formatter = Sequitur::Formatter::BaseText.new(STDOUT)
9
+ # Render the grammar (through a visitor)
10
+ # formatter.run(some_grammar.visitor)
11
+ class BaseText
12
+ attr(:output)
13
+
14
+ # Constructor.
15
+ # [anIO]
16
+ def initialize(anIO)
17
+ @output = anIO
18
+ @prod_lookup = {}
19
+ end
20
+
21
+ public
22
+
23
+ def render(aVisitor)
24
+ aVisitor.subscribe(self)
25
+ aVisitor.start()
26
+ aVisitor.unsubscribe(self)
27
+ end
28
+
29
+ def before_grammar(aGrammar)
30
+ aGrammar.productions.each_with_index do |a_prod, index|
31
+ prod_lookup[a_prod] = index
32
+ end
33
+ end
34
+
35
+ def before_production(aProduction)
36
+ p_name = prod_name(aProduction)
37
+ output.print p_name
38
+ end
39
+
40
+ def before_rhs(_)
41
+ output.print ' :'
42
+ end
43
+
44
+ def before_terminal(aSymbol)
45
+ output.print " #{aSymbol}"
46
+ end
47
+
48
+
49
+ def before_non_terminal(aProduction)
50
+ p_name = prod_name(aProduction)
51
+ output.print " #{p_name}"
52
+ end
53
+
54
+ def after_production(_)
55
+ output.print ".\n"
56
+ end
57
+
58
+ private
59
+
60
+ def prod_lookup()
61
+ return @prod_lookup
62
+ end
63
+
64
+ def prod_name(aProduction)
65
+ prod_index = prod_lookup[aProduction]
66
+ name = (prod_index == 0) ? 'start' : "P_#{prod_index}"
67
+ return name
68
+ end
69
+
70
+ end # class
71
+ end # module
72
+ end # module
73
+
74
+ # End of file
@@ -0,0 +1,86 @@
1
+ module Sequitur
2
+ module Formatter
3
+ class Debug
4
+ attr(:indentation)
5
+ attr(:output)
6
+
7
+ # Constructor.
8
+ # [anIO]
9
+ def initialize(anIO)
10
+ @indentation = 0
11
+ @output = anIO
12
+ end
13
+
14
+ public
15
+
16
+ def render(aVisitor)
17
+ aVisitor.subscribe(self)
18
+ aVisitor.start()
19
+ aVisitor.unsubscribe(self)
20
+ end
21
+
22
+ def before_grammar(_)
23
+ output_event(__method__, indentation)
24
+ indent
25
+ end
26
+
27
+ def before_production(_)
28
+ output_event(__method__, indentation)
29
+ indent
30
+ end
31
+
32
+ def before_rhs(_)
33
+ output_event(__method__, indentation)
34
+ indent
35
+ end
36
+
37
+ def before_terminal(_)
38
+ output_event(__method__, indentation)
39
+ end
40
+
41
+ def after_terminal(_)
42
+ output_event(__method__, indentation)
43
+ end
44
+
45
+ def before_non_terminal(_)
46
+ output_event(__method__, indentation)
47
+ end
48
+
49
+ def after_non_terminal(_)
50
+ output_event(__method__, indentation)
51
+ end
52
+
53
+ def after_rhs(_)
54
+ dedent
55
+ output_event(__method__, indentation)
56
+ end
57
+
58
+ def after_production(_)
59
+ dedent
60
+ output_event(__method__, indentation)
61
+ end
62
+
63
+ def after_grammar(_)
64
+ dedent
65
+ output_event(__method__, indentation)
66
+ end
67
+
68
+ private
69
+
70
+ def indent()
71
+ @indentation += 1
72
+ end
73
+
74
+ def dedent()
75
+ @indentation -= 1
76
+ end
77
+
78
+ def output_event(anEvent, indentationLevel)
79
+ output.puts "#{' ' * 2 * indentationLevel}#{anEvent}"
80
+ end
81
+
82
+ end # class
83
+ end # module
84
+ end # module
85
+
86
+ # End of file
@@ -0,0 +1,81 @@
1
+ module Sequitur # Module for classes implementing the Sequitur algorithm
2
+
3
+ # A visitor class dedicated in the visit of Grammar.
4
+
5
+ class GrammarVisitor
6
+ attr_reader(:grammar)
7
+
8
+ attr_reader(:subscribers)
9
+
10
+ # Constructor.
11
+ # [aGrammar] a DynamicGrammar-like instance.
12
+ def initialize(aGrammar)
13
+ @grammar = aGrammar
14
+ @subscribers = []
15
+ end
16
+
17
+ public
18
+
19
+ # Add a subscriber to the list.
20
+ def subscribe(aSubscriber)
21
+ subscribers << aSubscriber
22
+ end
23
+
24
+ def unsubscribe(aSubscriber)
25
+ subscribers.delete_if { |entry| entry == aSubscriber}
26
+ end
27
+
28
+ # The signal to start the visit.
29
+ def start()
30
+ grammar.send(:accept, self)
31
+ end
32
+
33
+
34
+ def start_visit_grammar(aGrammar)
35
+ broadcast(:before_grammar, aGrammar)
36
+ end
37
+
38
+
39
+ def start_visit_production(aProduction)
40
+ broadcast(:before_production, aProduction)
41
+ broadcast(:before_rhs, aProduction.rhs)
42
+ end
43
+
44
+
45
+ def visit_prod_ref(aProdRef)
46
+ production = aProdRef.production
47
+ broadcast(:before_non_terminal, production)
48
+ broadcast(:after_non_terminal, production)
49
+ end
50
+
51
+ def visit_terminal(aTerminal)
52
+ broadcast(:before_terminal, aTerminal)
53
+ broadcast(:after_terminal, aTerminal)
54
+ end
55
+
56
+
57
+ def end_visit_production(aProduction)
58
+ broadcast(:after_rhs, aProduction.rhs)
59
+ broadcast(:after_production, aProduction)
60
+
61
+ end
62
+
63
+
64
+ def end_visit_grammar(aGrammar)
65
+ broadcast(:after_grammar, aGrammar)
66
+ end
67
+
68
+ private
69
+ def broadcast(msg, *args)
70
+ subscribers.each do |a_subscriber|
71
+ next unless a_subscriber.respond_to?(msg)
72
+ a_subscriber.send(msg, *args)
73
+ end
74
+ end
75
+
76
+
77
+ end # class
78
+
79
+ end # module
80
+
81
+ # End of file
@@ -15,7 +15,7 @@ module Sequitur # Module for classes implementing the Sequitur algorithm
15
15
  class Production
16
16
  # The right-hand side (rhs) consists of a sequence of grammar symbols
17
17
  attr_reader(:rhs)
18
-
18
+
19
19
  # The reference count (= how times other productions reference this one)
20
20
  attr_reader(:refcount)
21
21
 
@@ -59,7 +59,7 @@ class Production
59
59
  end
60
60
 
61
61
 
62
- # Return the set of productions appearing in the rhs.
62
+ # Return the set of references to production appearing in the rhs.
63
63
  def references()
64
64
  return rhs.select { |symb| symb.is_a?(ProductionRef) }
65
65
  end
@@ -71,8 +71,6 @@ class Production
71
71
  end
72
72
 
73
73
 
74
-
75
-
76
74
  # Return the list digrams found in rhs of this production.
77
75
  def recalc_digrams()
78
76
  return [] if rhs.size < 2
@@ -137,11 +135,11 @@ class Production
137
135
  msg << to_string
138
136
  fail StandardError, msg
139
137
  end
140
- new_symb = aSymbol.dup
138
+ new_symb = aSymbol.dup
141
139
  else
142
140
  new_symb = aSymbol
143
141
  end
144
-
142
+
145
143
  rhs << new_symb
146
144
  digrams << Digram.new(rhs[-2], rhs[-1], self) if rhs.size >= 2
147
145
  end
@@ -197,7 +195,7 @@ class Production
197
195
  rhs[index1].unbind if rhs[index1].is_a?(ProductionRef)
198
196
  rhs.delete_at(index1)
199
197
  end
200
-
198
+
201
199
  recalc_digrams
202
200
  end
203
201
 
@@ -211,19 +209,36 @@ class Production
211
209
  def replace_production(another)
212
210
  (0...rhs.size).to_a.reverse.each do |index|
213
211
  next unless rhs[index] == another
214
-
212
+
215
213
  # Avoid the aliasing of production reference
216
- other_rhs = another.rhs.map do |symb|
214
+ other_rhs = another.rhs.map do |symb|
217
215
  symb.is_a?(ProductionRef) ? symb.dup : symb
218
216
  end
219
217
  rhs.insert(index + 1, *other_rhs)
220
218
  another.decr_refcount
221
219
  rhs.delete_at(index)
222
220
  end
223
-
221
+
224
222
  recalc_digrams
225
223
  end
226
224
 
225
+
226
+ # Part of the 'visitee' role.
227
+ # [aVisitor] a GrammarVisitor instance
228
+ def accept(aVisitor)
229
+ aVisitor.start_visit_production(self)
230
+
231
+ rhs.each do |a_symb|
232
+ if a_symb.is_a?(ProductionRef)
233
+ a_symb.accept(aVisitor)
234
+ else
235
+ aVisitor.visit_terminal(a_symb)
236
+ end
237
+ end
238
+
239
+ aVisitor.end_visit_production(self)
240
+ end
241
+
227
242
  end # class
228
243
 
229
244
  end # module
@@ -81,6 +81,12 @@ class ProductionRef
81
81
  def unbound?()
82
82
  return production.nil?
83
83
  end
84
+
85
+ # Part of the 'visitee' role.
86
+ # [aVisitor] a GrammarVisitor instance
87
+ def accept(aVisitor)
88
+ aVisitor.visit_prod_ref(self)
89
+ end
84
90
 
85
91
  end # class
86
92
 
@@ -41,7 +41,7 @@ class SequiturGrammar < DynamicGrammar
41
41
  end
42
42
 
43
43
  # Remove a production from the grammar
44
- def delete_production(anIndex)
44
+ def remove_production(anIndex)
45
45
  prod = productions[anIndex]
46
46
 
47
47
  # Retrieve in the Hash all registered digrams from the removed production
@@ -170,7 +170,7 @@ class SequiturGrammar < DynamicGrammar
170
170
  !a_prod.references_of(curr_production).empty?
171
171
  end
172
172
  dependent.replace_production(productions[index])
173
- delete_production(index)
173
+ remove_production(index)
174
174
  update_digrams_from(dependent)
175
175
  end
176
176
 
data/lib/sequitur.rb CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  require_relative './sequitur/constants'
6
6
  require_relative './sequitur/sequitur_grammar'
7
+ require_relative './sequitur/formatter/debug'
8
+ require_relative './sequitur/formatter/base_text'
7
9
 
8
10
 
9
11
  module Sequitur
@@ -46,3 +46,4 @@ end # describe
46
46
  end # module
47
47
 
48
48
  # End of file
49
+
@@ -40,7 +40,7 @@ describe DynamicGrammar do
40
40
  subject.add_production(p_a)
41
41
  expect(subject.productions.size).to eq(2)
42
42
  expect(subject.productions.last).to eq(p_a)
43
-
43
+
44
44
  # Error: p_b, p_c not in grammar
45
45
  expect { add_production(p_bc) }.to raise_error(StandardError)
46
46
 
@@ -48,18 +48,18 @@ describe DynamicGrammar do
48
48
  expect(subject.productions.size).to eq(3)
49
49
  expect(subject.productions.last).to eq(p_b)
50
50
 
51
- # Error: p_c not in grammar
51
+ # Error: p_c not in grammar
52
52
  expect { add_production(p_bc) }.to raise_error(StandardError)
53
53
 
54
54
  subject.add_production(p_c)
55
55
  expect(subject.productions.size).to eq(4)
56
56
  expect(subject.productions.last).to eq(p_c)
57
-
57
+
58
58
  subject.add_production(p_bc)
59
59
  expect(subject.productions.size).to eq(5)
60
- expect(subject.productions.last).to eq(p_bc)
60
+ expect(subject.productions.last).to eq(p_bc)
61
61
  end
62
-
62
+
63
63
  it 'should complain when rhs refers to unknown production' do
64
64
  subject.add_production(p_a)
65
65
  subject.add_production(p_b)
@@ -67,11 +67,11 @@ describe DynamicGrammar do
67
67
  msg = "Production #{p_bc.object_id} refers to production #{p_c.object_id}"
68
68
  msg << ' that is not part of the grammar.'
69
69
  expect { subject.add_production(p_bc) }.to raise_error(StandardError, msg)
70
-
70
+
71
71
  end
72
72
  end # context
73
73
 
74
-
74
+
75
75
  context 'Removing a production from the grammar:' do
76
76
  it 'should remove an existing production' do
77
77
  subject.add_production(p_a) # index = 1
@@ -79,25 +79,67 @@ describe DynamicGrammar do
79
79
  subject.add_production(p_c) # index = 3
80
80
  subject.add_production(p_bc) # index = 4
81
81
  expect(subject.productions.size).to eq(5)
82
-
82
+
83
83
  expect(p_a.refcount).to eq(0)
84
84
  expect(p_b.refcount).to eq(1)
85
85
  expect(p_c.refcount).to eq(1)
86
-
87
- subject.delete_production(1) # 1 => p_a
86
+
87
+ subject.remove_production(1) # 1 => p_a
88
88
  expect(subject.productions.size).to eq(4)
89
89
  expect(p_b.refcount).to eq(1)
90
90
  expect(p_c.refcount).to eq(1)
91
91
  expect(subject.productions).not_to include(p_a)
92
-
93
- subject.delete_production(3) # 3 => p_bc
94
-
92
+
93
+ subject.remove_production(3) # 3 => p_bc
94
+
95
95
  expect(subject.productions.size).to eq(3)
96
96
  expect(p_b.refcount).to eq(0)
97
- expect(p_c.refcount).to eq(0)
98
- expect(subject.productions).not_to include(p_bc)
97
+ expect(p_c.refcount).to eq(0)
98
+ expect(subject.productions).not_to include(p_bc)
99
+ end
100
+
101
+ end # context
102
+
103
+ context 'Visiting:' do
104
+ it 'should return a visitor' do
105
+ expect { subject.visitor }.not_to raise_error
106
+ expect(subject.visitor).to be_kind_of(GrammarVisitor)
107
+ end
108
+
109
+ it 'should accept a visitor' do
110
+ subject.add_production(p_a) # index = 1
111
+ subject.add_production(p_b) # index = 2
112
+ subject.add_production(p_c) # index = 3
113
+ subject.add_production(p_bc) # index = 4
114
+
115
+ a_visitor = subject.visitor
116
+ fake_formatter = double('fake-formatter')
117
+ a_visitor.subscribe(fake_formatter)
118
+
119
+ expect(fake_formatter).to receive(:before_grammar).with(subject).ordered
120
+ expect(fake_formatter).to receive(:before_production).with(subject.root).ordered
121
+ expect(fake_formatter).to receive(:before_rhs).with([]).ordered
122
+ expect(fake_formatter).to receive(:after_rhs).with([]).ordered
123
+ expect(fake_formatter).to receive(:after_production).with(subject.root)
124
+ expect(fake_formatter).to receive(:before_production).with(p_a)
125
+ expect(fake_formatter).to receive(:before_rhs).with(p_a.rhs)
126
+ expect(fake_formatter).to receive(:after_rhs).with(p_a.rhs)
127
+ expect(fake_formatter).to receive(:after_production).with(p_a)
128
+ expect(fake_formatter).to receive(:before_production).with(p_b)
129
+ expect(fake_formatter).to receive(:before_rhs).with(p_b.rhs)
130
+ expect(fake_formatter).to receive(:after_rhs).with(p_b.rhs)
131
+ expect(fake_formatter).to receive(:after_production).with(p_b)
132
+ expect(fake_formatter).to receive(:before_production).with(p_c)
133
+ expect(fake_formatter).to receive(:before_rhs).with(p_c.rhs)
134
+ expect(fake_formatter).to receive(:after_rhs).with(p_c.rhs)
135
+ expect(fake_formatter).to receive(:after_production).with(p_c)
136
+ expect(fake_formatter).to receive(:before_production).with(p_bc)
137
+ expect(fake_formatter).to receive(:before_rhs).with(p_bc.rhs)
138
+ expect(fake_formatter).to receive(:after_rhs).with(p_bc.rhs)
139
+ expect(fake_formatter).to receive(:after_production).with(p_bc)
140
+ expect(fake_formatter).to receive(:after_grammar).with(subject)
141
+ subject.send(:accept, a_visitor)
99
142
  end
100
-
101
143
  end # context
102
144
 
103
145
 
@@ -0,0 +1,82 @@
1
+ require_relative '../../spec_helper'
2
+ require 'stringio'
3
+
4
+ require_relative '../../../lib/sequitur/dynamic_grammar'
5
+
6
+ # Load the class under test
7
+ require_relative '../../../lib/sequitur/formatter/base_text'
8
+
9
+ module Sequitur # Re-open the module to get rid of qualified names
10
+ module Formatter
11
+
12
+ describe BaseText do
13
+ # Factory method. Build a production with the given sequence
14
+ # of symbols as its rhs.
15
+ def build_production(*symbols)
16
+ prod = Production.new
17
+ symbols.each { |symb| prod.append_symbol(symb) }
18
+ return prod
19
+ end
20
+
21
+ let(:p_a) { build_production(:a) }
22
+ let(:p_b) { build_production(:b) }
23
+ let(:p_c) { build_production(:c) }
24
+ let(:p_bc) { build_production(p_b, p_c) }
25
+
26
+ let(:empty_grammar) { DynamicGrammar.new }
27
+ let(:sample_grammar) do
28
+ grm = DynamicGrammar.new
29
+ grm.add_production(p_a)
30
+ grm.add_production(p_b)
31
+ grm.add_production(p_c)
32
+ grm.add_production(p_bc)
33
+ grm
34
+ end
35
+
36
+ let(:destination) { StringIO.new('', 'w') }
37
+
38
+ context 'Standard creation & initialization:' do
39
+
40
+ it 'should be initialized with an IO argument' do
41
+ expect { BaseText.new(StringIO.new('', 'w')) }.not_to raise_error
42
+ end
43
+
44
+ it "should know its output destination" do
45
+ instance = BaseText.new(destination)
46
+ expect(instance.output).to eq(destination)
47
+ end
48
+ end # context
49
+
50
+
51
+
52
+ context 'Formatting events:' do
53
+ it 'should support events of an empty grammar' do
54
+ instance = BaseText.new(destination)
55
+ a_visitor = empty_grammar.visitor
56
+ instance.render(a_visitor)
57
+ expectations =<<-SNIPPET
58
+ start :.
59
+ SNIPPET
60
+ expect(destination.string).to eq(expectations)
61
+ end
62
+
63
+ it 'should support events of a non-empty grammar' do
64
+ instance = BaseText.new(destination)
65
+ a_visitor = sample_grammar.visitor
66
+ instance.render(a_visitor)
67
+ expectations =<<-SNIPPET
68
+ start :.
69
+ P_1 : a.
70
+ P_2 : b.
71
+ P_3 : c.
72
+ P_4 : P_2 P_3.
73
+ SNIPPET
74
+ expect(destination.string).to eq(expectations)
75
+ end
76
+ end # context
77
+ end # describe
78
+
79
+ end # module
80
+ end # module
81
+
82
+ # End of file
@@ -0,0 +1,114 @@
1
+ require_relative '../../spec_helper'
2
+ require 'stringio'
3
+
4
+ require_relative '../../../lib/sequitur/dynamic_grammar'
5
+
6
+ # Load the class under test
7
+ require_relative '../../../lib/sequitur/formatter/debug'
8
+
9
+ module Sequitur # Re-open the module to get rid of qualified names
10
+ module Formatter
11
+
12
+ describe Debug do
13
+ # Factory method. Build a production with the given sequence
14
+ # of symbols as its rhs.
15
+ def build_production(*symbols)
16
+ prod = Production.new
17
+ symbols.each { |symb| prod.append_symbol(symb) }
18
+ return prod
19
+ end
20
+
21
+ let(:p_a) { build_production(:a) }
22
+ let(:p_b) { build_production(:b) }
23
+ let(:p_c) { build_production(:c) }
24
+ let(:p_bc) { build_production(p_b, p_c) }
25
+
26
+ let(:empty_grammar) { DynamicGrammar.new }
27
+ let(:sample_grammar) do
28
+ grm = DynamicGrammar.new
29
+ grm.add_production(p_a)
30
+ grm.add_production(p_b)
31
+ grm.add_production(p_c)
32
+ grm.add_production(p_bc)
33
+ grm
34
+ end
35
+
36
+ let(:destination) { StringIO.new('', 'w') }
37
+
38
+ context 'Standard creation & initialization:' do
39
+
40
+ it 'should be initialized with an IO argument' do
41
+ expect { Debug.new(StringIO.new('', 'w')) }.not_to raise_error
42
+ end
43
+
44
+ it "should know its output destination" do
45
+ instance = Debug.new(destination)
46
+ expect(instance.output).to eq(destination)
47
+ end
48
+ end # context
49
+
50
+
51
+
52
+ context 'Formatting events:' do
53
+ it 'should support events of an empty grammar' do
54
+ instance = Debug.new(destination)
55
+ a_visitor = empty_grammar.visitor
56
+ instance.render(a_visitor)
57
+ expectations =<<-SNIPPET
58
+ before_grammar
59
+ before_production
60
+ before_rhs
61
+ after_rhs
62
+ after_production
63
+ after_grammar
64
+ SNIPPET
65
+ expect(destination.string).to eq(expectations)
66
+ end
67
+
68
+ it 'should support events of a non-empty grammar' do
69
+ instance = Debug.new(destination)
70
+ a_visitor = sample_grammar.visitor
71
+ instance.render(a_visitor)
72
+ expectations =<<-SNIPPET
73
+ before_grammar
74
+ before_production
75
+ before_rhs
76
+ after_rhs
77
+ after_production
78
+ before_production
79
+ before_rhs
80
+ before_terminal
81
+ after_terminal
82
+ after_rhs
83
+ after_production
84
+ before_production
85
+ before_rhs
86
+ before_terminal
87
+ after_terminal
88
+ after_rhs
89
+ after_production
90
+ before_production
91
+ before_rhs
92
+ before_terminal
93
+ after_terminal
94
+ after_rhs
95
+ after_production
96
+ before_production
97
+ before_rhs
98
+ before_non_terminal
99
+ after_non_terminal
100
+ before_non_terminal
101
+ after_non_terminal
102
+ after_rhs
103
+ after_production
104
+ after_grammar
105
+ SNIPPET
106
+ expect(destination.string).to eq(expectations)
107
+ end
108
+ end # context
109
+ end # describe
110
+
111
+ end # module
112
+ end # module
113
+
114
+ # End of file
@@ -0,0 +1,98 @@
1
+ require_relative '../spec_helper'
2
+
3
+ # Load the class under test
4
+ require_relative '../../lib/sequitur/grammar_visitor'
5
+
6
+ module Sequitur # Re-open the module to get rid of qualified names
7
+
8
+ describe GrammarVisitor do
9
+ # Use a double(mock) as a grammar
10
+ let(:fake) { double('fake-grammar') }
11
+
12
+ context 'Standard creation & initialization:' do
13
+
14
+ # Default instantiation rule
15
+ subject { GrammarVisitor.new(fake) }
16
+
17
+ it 'should be initialized with a grammar argument' do
18
+ expect { GrammarVisitor.new(fake) }.not_to raise_error
19
+ end
20
+
21
+ it 'should know the grammar to visit' do
22
+ expect(subject.grammar).to eq(fake)
23
+ end
24
+
25
+ it "shouldn't have subscribers at start" do
26
+ expect(subject.subscribers).to be_empty
27
+ end
28
+ end # context
29
+
30
+
31
+ context 'Subscribing:' do
32
+
33
+ # Default instantiation rule
34
+ subject { GrammarVisitor.new(fake) }
35
+
36
+ let(:listener1) { double('fake-formatter1') }
37
+ let(:listener2) { double('fake-formatter2') }
38
+
39
+ it 'should allow subscriptions' do
40
+ subject.subscribe(listener1)
41
+ expect(subject.subscribers.size).to eq(1)
42
+ expect(subject.subscribers).to eq([listener1])
43
+
44
+ subject.subscribe(listener2)
45
+ expect(subject.subscribers.size).to eq(2)
46
+ expect(subject.subscribers).to eq([listener1, listener2])
47
+ end
48
+
49
+ it 'should allow un-subcriptions' do
50
+ subject.subscribe(listener1)
51
+ subject.subscribe(listener2)
52
+ subject.unsubscribe(listener2)
53
+ expect(subject.subscribers.size).to eq(1)
54
+ expect(subject.subscribers).to eq([listener1])
55
+ subject.unsubscribe(listener1)
56
+ expect(subject.subscribers).to be_empty
57
+ end
58
+
59
+ end # context
60
+
61
+ context 'Notifying visit events:' do
62
+
63
+ # Default instantiation rule
64
+ subject do
65
+ instance = GrammarVisitor.new(fake)
66
+ instance.subscribe(listener1)
67
+ instance
68
+ end
69
+
70
+ # Use doubles/mocks to simulate formatters
71
+ let(:listener1) { double('fake-formatter1') }
72
+ let(:listener2) { double('fake-formatter2') }
73
+ let(:mock_production) { double('fake-production') }
74
+
75
+ it 'should react to the start_visit_grammar message' do
76
+ # Notify subscribers when start the visit of the grammar
77
+ expect(listener1).to receive(:before_grammar).with(fake)
78
+
79
+ subject.start_visit_grammar(fake)
80
+ end
81
+
82
+ it 'should react to the start_visit_production message' do
83
+ expect(mock_production).to receive(:rhs).and_return([1, 2, 3])
84
+
85
+ # Notify subscribers when start the visit of the production
86
+ expect(listener1).to receive(:before_production).with(mock_production)
87
+ expect(listener1).to receive(:before_rhs).with([1, 2, 3])
88
+
89
+ subject.start_visit_production(mock_production)
90
+ end
91
+
92
+ end # context
93
+
94
+ end # describe
95
+
96
+ end # module
97
+
98
+ # End of file
@@ -92,7 +92,17 @@ describe ProductionRef do
92
92
  subject.unbind
93
93
  expect { subject.hash }.to raise_error(StandardError)
94
94
  end
95
- end
95
+
96
+ it 'should accept a visitor' do
97
+ # Use a mock visitor
98
+ fake = double('fake_visitor')
99
+
100
+ # Visitor should receive a visit message
101
+ expect(fake).to receive(:visit_prod_ref).once
102
+ expect { subject.accept(fake) }.not_to raise_error
103
+ end
104
+
105
+ end # context
96
106
 
97
107
  end # describe
98
108
 
@@ -322,6 +322,45 @@ describe Production do
322
322
  end
323
323
 
324
324
  end # context
325
+
326
+ context 'Visiting:' do
327
+ it 'should accept a visitor when its rhs is empty' do
328
+ # Use a mock visitor
329
+ fake = double('fake_visitor')
330
+
331
+ # Empty production: visitor will receive a start and end visit messages
332
+ expect(fake).to receive(:start_visit_production).once.ordered
333
+ expect(fake).to receive(:end_visit_production).once.ordered
334
+
335
+ expect { subject.accept(fake) }.not_to raise_error
336
+ end
337
+
338
+ it 'should accept a visitor when rhs consists of terminals only' do
339
+ # Use a mock visitor
340
+ fake = double('fake_visitor')
341
+ expect(fake).to receive(:start_visit_production).once.ordered
342
+ expect(fake).to receive(:visit_terminal).with('b').ordered
343
+ expect(fake).to receive(:visit_terminal).with('c').ordered
344
+ expect(fake).to receive(:end_visit_production).once.ordered
345
+
346
+ expect { p_bc.accept(fake) }.not_to raise_error
347
+ end
348
+
349
+ it 'should accept a visitor when rhs consists of non-terminals' do
350
+ # Add two production references (=non-terminals) to RHS of subject
351
+ subject.append_symbol(p_a)
352
+ subject.append_symbol(p_bc)
353
+
354
+ fake = double('fake_visitor')
355
+ expect(fake).to receive(:start_visit_production).once.ordered
356
+ expect(fake).to receive(:visit_prod_ref).with(p_a).ordered
357
+ expect(fake).to receive(:visit_prod_ref).with(p_bc).ordered
358
+ expect(fake).to receive(:end_visit_production).once.ordered
359
+
360
+ expect { subject.accept(fake) }.not_to raise_error
361
+ end
362
+
363
+ end # context
325
364
 
326
365
  end # describe
327
366
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequitur
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.14
4
+ version: 0.1.00
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-10 00:00:00.000000000 Z
11
+ date: 2014-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -89,11 +89,17 @@ files:
89
89
  - lib/sequitur/constants.rb
90
90
  - lib/sequitur/digram.rb
91
91
  - lib/sequitur/dynamic_grammar.rb
92
+ - lib/sequitur/formatter/base_text.rb
93
+ - lib/sequitur/formatter/debug.rb
94
+ - lib/sequitur/grammar_visitor.rb
92
95
  - lib/sequitur/production.rb
93
96
  - lib/sequitur/production_ref.rb
94
97
  - lib/sequitur/sequitur_grammar.rb
95
98
  - spec/sequitur/digram_spec.rb
96
99
  - spec/sequitur/dynamic_grammar_spec.rb
100
+ - spec/sequitur/formatter/base_text_spec.rb
101
+ - spec/sequitur/formatter/debug_spec.rb
102
+ - spec/sequitur/grammar_visitor_spec.rb
97
103
  - spec/sequitur/production_ref_spec.rb
98
104
  - spec/sequitur/production_spec.rb
99
105
  - spec/sequitur/sequitur_grammar_spec.rb
@@ -132,6 +138,9 @@ summary: Ruby implementation of the Sequitur algorithm
132
138
  test_files:
133
139
  - spec/sequitur/digram_spec.rb
134
140
  - spec/sequitur/dynamic_grammar_spec.rb
141
+ - spec/sequitur/formatter/base_text_spec.rb
142
+ - spec/sequitur/formatter/debug_spec.rb
143
+ - spec/sequitur/grammar_visitor_spec.rb
135
144
  - spec/sequitur/production_ref_spec.rb
136
145
  - spec/sequitur/production_spec.rb
137
146
  - spec/sequitur/sequitur_grammar_spec.rb