sequitur 0.0.14 → 0.1.00

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