plurimath-parslet 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.txt +284 -0
  3. data/LICENSE +23 -0
  4. data/README.adoc +454 -0
  5. data/Rakefile +71 -0
  6. data/lib/parslet/accelerator/application.rb +62 -0
  7. data/lib/parslet/accelerator/engine.rb +112 -0
  8. data/lib/parslet/accelerator.rb +162 -0
  9. data/lib/parslet/atoms/alternative.rb +53 -0
  10. data/lib/parslet/atoms/base.rb +157 -0
  11. data/lib/parslet/atoms/can_flatten.rb +137 -0
  12. data/lib/parslet/atoms/capture.rb +38 -0
  13. data/lib/parslet/atoms/context.rb +103 -0
  14. data/lib/parslet/atoms/dsl.rb +112 -0
  15. data/lib/parslet/atoms/dynamic.rb +32 -0
  16. data/lib/parslet/atoms/entity.rb +45 -0
  17. data/lib/parslet/atoms/ignored.rb +26 -0
  18. data/lib/parslet/atoms/infix.rb +115 -0
  19. data/lib/parslet/atoms/lookahead.rb +52 -0
  20. data/lib/parslet/atoms/named.rb +32 -0
  21. data/lib/parslet/atoms/re.rb +41 -0
  22. data/lib/parslet/atoms/repetition.rb +87 -0
  23. data/lib/parslet/atoms/scope.rb +26 -0
  24. data/lib/parslet/atoms/sequence.rb +48 -0
  25. data/lib/parslet/atoms/str.rb +42 -0
  26. data/lib/parslet/atoms/visitor.rb +89 -0
  27. data/lib/parslet/atoms.rb +34 -0
  28. data/lib/parslet/cause.rb +101 -0
  29. data/lib/parslet/context.rb +21 -0
  30. data/lib/parslet/convenience.rb +33 -0
  31. data/lib/parslet/error_reporter/contextual.rb +120 -0
  32. data/lib/parslet/error_reporter/deepest.rb +100 -0
  33. data/lib/parslet/error_reporter/tree.rb +63 -0
  34. data/lib/parslet/error_reporter.rb +8 -0
  35. data/lib/parslet/export.rb +163 -0
  36. data/lib/parslet/expression/treetop.rb +92 -0
  37. data/lib/parslet/expression.rb +51 -0
  38. data/lib/parslet/graphviz.rb +97 -0
  39. data/lib/parslet/parser.rb +68 -0
  40. data/lib/parslet/pattern/binding.rb +49 -0
  41. data/lib/parslet/pattern.rb +113 -0
  42. data/lib/parslet/position.rb +21 -0
  43. data/lib/parslet/rig/rspec.rb +52 -0
  44. data/lib/parslet/scope.rb +42 -0
  45. data/lib/parslet/slice.rb +105 -0
  46. data/lib/parslet/source/line_cache.rb +99 -0
  47. data/lib/parslet/source.rb +96 -0
  48. data/lib/parslet/transform.rb +265 -0
  49. data/lib/parslet/version.rb +5 -0
  50. data/lib/parslet.rb +314 -0
  51. data/plurimath-parslet.gemspec +42 -0
  52. data/spec/acceptance/infix_parser_spec.rb +145 -0
  53. data/spec/acceptance/mixing_parsers_spec.rb +74 -0
  54. data/spec/acceptance/regression_spec.rb +329 -0
  55. data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
  56. data/spec/acceptance/unconsumed_input_spec.rb +21 -0
  57. data/spec/examples/boolean_algebra_spec.rb +257 -0
  58. data/spec/examples/calc_spec.rb +278 -0
  59. data/spec/examples/capture_spec.rb +137 -0
  60. data/spec/examples/comments_spec.rb +186 -0
  61. data/spec/examples/deepest_errors_spec.rb +420 -0
  62. data/spec/examples/documentation_spec.rb +205 -0
  63. data/spec/examples/email_parser_spec.rb +275 -0
  64. data/spec/examples/empty_spec.rb +37 -0
  65. data/spec/examples/erb_spec.rb +482 -0
  66. data/spec/examples/ip_address_spec.rb +153 -0
  67. data/spec/examples/json_spec.rb +413 -0
  68. data/spec/examples/local_spec.rb +302 -0
  69. data/spec/examples/mathn_spec.rb +151 -0
  70. data/spec/examples/minilisp_spec.rb +492 -0
  71. data/spec/examples/modularity_spec.rb +340 -0
  72. data/spec/examples/nested_errors_spec.rb +322 -0
  73. data/spec/examples/optimized_erb_spec.rb +299 -0
  74. data/spec/examples/parens_spec.rb +239 -0
  75. data/spec/examples/prec_calc_spec.rb +525 -0
  76. data/spec/examples/readme_spec.rb +228 -0
  77. data/spec/examples/scopes_spec.rb +187 -0
  78. data/spec/examples/seasons_spec.rb +196 -0
  79. data/spec/examples/sentence_spec.rb +119 -0
  80. data/spec/examples/simple_xml_spec.rb +250 -0
  81. data/spec/examples/string_parser_spec.rb +407 -0
  82. data/spec/fixtures/examples/boolean_algebra.rb +62 -0
  83. data/spec/fixtures/examples/calc.rb +86 -0
  84. data/spec/fixtures/examples/capture.rb +36 -0
  85. data/spec/fixtures/examples/comments.rb +22 -0
  86. data/spec/fixtures/examples/deepest_errors.rb +99 -0
  87. data/spec/fixtures/examples/documentation.rb +32 -0
  88. data/spec/fixtures/examples/email_parser.rb +42 -0
  89. data/spec/fixtures/examples/empty.rb +10 -0
  90. data/spec/fixtures/examples/erb.rb +39 -0
  91. data/spec/fixtures/examples/ip_address.rb +103 -0
  92. data/spec/fixtures/examples/json.rb +107 -0
  93. data/spec/fixtures/examples/local.rb +60 -0
  94. data/spec/fixtures/examples/mathn.rb +47 -0
  95. data/spec/fixtures/examples/minilisp.rb +75 -0
  96. data/spec/fixtures/examples/modularity.rb +60 -0
  97. data/spec/fixtures/examples/nested_errors.rb +95 -0
  98. data/spec/fixtures/examples/optimized_erb.rb +105 -0
  99. data/spec/fixtures/examples/parens.rb +25 -0
  100. data/spec/fixtures/examples/prec_calc.rb +71 -0
  101. data/spec/fixtures/examples/readme.rb +59 -0
  102. data/spec/fixtures/examples/scopes.rb +43 -0
  103. data/spec/fixtures/examples/seasons.rb +40 -0
  104. data/spec/fixtures/examples/sentence.rb +18 -0
  105. data/spec/fixtures/examples/simple_xml.rb +51 -0
  106. data/spec/fixtures/examples/string_parser.rb +77 -0
  107. data/spec/parslet/atom_results_spec.rb +39 -0
  108. data/spec/parslet/atoms/alternative_spec.rb +26 -0
  109. data/spec/parslet/atoms/base_spec.rb +127 -0
  110. data/spec/parslet/atoms/capture_spec.rb +21 -0
  111. data/spec/parslet/atoms/combinations_spec.rb +5 -0
  112. data/spec/parslet/atoms/dsl_spec.rb +7 -0
  113. data/spec/parslet/atoms/entity_spec.rb +77 -0
  114. data/spec/parslet/atoms/ignored_spec.rb +15 -0
  115. data/spec/parslet/atoms/infix_spec.rb +5 -0
  116. data/spec/parslet/atoms/lookahead_spec.rb +22 -0
  117. data/spec/parslet/atoms/named_spec.rb +4 -0
  118. data/spec/parslet/atoms/re_spec.rb +14 -0
  119. data/spec/parslet/atoms/repetition_spec.rb +24 -0
  120. data/spec/parslet/atoms/scope_spec.rb +26 -0
  121. data/spec/parslet/atoms/sequence_spec.rb +28 -0
  122. data/spec/parslet/atoms/str_spec.rb +15 -0
  123. data/spec/parslet/atoms/visitor_spec.rb +101 -0
  124. data/spec/parslet/atoms_spec.rb +488 -0
  125. data/spec/parslet/convenience_spec.rb +54 -0
  126. data/spec/parslet/error_reporter/contextual_spec.rb +118 -0
  127. data/spec/parslet/error_reporter/deepest_spec.rb +82 -0
  128. data/spec/parslet/error_reporter/tree_spec.rb +7 -0
  129. data/spec/parslet/export_spec.rb +40 -0
  130. data/spec/parslet/expression/treetop_spec.rb +74 -0
  131. data/spec/parslet/minilisp.citrus +29 -0
  132. data/spec/parslet/minilisp.tt +29 -0
  133. data/spec/parslet/parser_spec.rb +36 -0
  134. data/spec/parslet/parslet_spec.rb +38 -0
  135. data/spec/parslet/pattern_spec.rb +272 -0
  136. data/spec/parslet/position_spec.rb +14 -0
  137. data/spec/parslet/rig/rspec_spec.rb +54 -0
  138. data/spec/parslet/scope_spec.rb +45 -0
  139. data/spec/parslet/slice_spec.rb +186 -0
  140. data/spec/parslet/source/line_cache_spec.rb +74 -0
  141. data/spec/parslet/source_spec.rb +210 -0
  142. data/spec/parslet/transform/context_spec.rb +56 -0
  143. data/spec/parslet/transform_spec.rb +183 -0
  144. data/spec/spec_helper.rb +74 -0
  145. data/spec/support/opal.rb +8 -0
  146. data/spec/support/opal.rb.erb +14 -0
  147. data/spec/support/parslet_matchers.rb +96 -0
  148. metadata +240 -0
@@ -0,0 +1,228 @@
1
+ require 'spec_helper'
2
+ require 'fixtures/examples/readme'
3
+
4
+ RSpec.describe 'Readme Example' do
5
+ include ReadmeExample
6
+
7
+ describe 'basic parslet functionality' do
8
+ context 'string parsing' do
9
+ it 'parses simple strings' do
10
+ parser = str('foo')
11
+ result = parser.parse('foo')
12
+ expect(result).to be_a(Parslet::Slice)
13
+ expect(result.to_s).to eq('foo')
14
+ end
15
+
16
+ it 'fails on non-matching strings' do
17
+ parser = str('foo')
18
+ expect { parser.parse('bar') }.to raise_error(Parslet::ParseFailed)
19
+ end
20
+ end
21
+
22
+ context 'character set matching' do
23
+ let(:parser) { Parslet.match('[abc]') }
24
+
25
+ it 'matches character a' do
26
+ result = parser.parse('a')
27
+ expect(result).to be_a(Parslet::Slice)
28
+ expect(result.to_s).to eq('a')
29
+ end
30
+
31
+ it 'matches character b' do
32
+ result = parser.parse('b')
33
+ expect(result).to be_a(Parslet::Slice)
34
+ expect(result.to_s).to eq('b')
35
+ end
36
+
37
+ it 'matches character c' do
38
+ result = parser.parse('c')
39
+ expect(result).to be_a(Parslet::Slice)
40
+ expect(result.to_s).to eq('c')
41
+ end
42
+
43
+ it 'fails on non-matching characters' do
44
+ expect { parser.parse('d') }.to raise_error(Parslet::ParseFailed)
45
+ expect { parser.parse('x') }.to raise_error(Parslet::ParseFailed)
46
+ end
47
+ end
48
+
49
+ context 'annotation' do
50
+ it 'annotates output with symbols' do
51
+ parser = str('foo').as(:important_bit)
52
+ result = parser.parse('foo')
53
+ expect(result).to eq({ important_bit: 'foo' })
54
+ end
55
+
56
+ it 'preserves slice information in annotations' do
57
+ parser = str('hello').as(:greeting)
58
+ result = parser.parse('hello')
59
+ expect(result[:greeting]).to be_a(Parslet::Slice)
60
+ expect(result[:greeting].to_s).to eq('hello')
61
+ end
62
+ end
63
+ end
64
+
65
+ describe 'demo methods' do
66
+ describe '.demo_basic_parsing' do
67
+ it 'returns hash with all basic parsing results' do
68
+ result = ReadmeExample.demo_basic_parsing
69
+
70
+ expect(result).to be_a(Hash)
71
+ expect(result.keys).to contain_exactly(:foo, :a, :b, :c, :annotated)
72
+
73
+ # Check individual results
74
+ expect(result[:foo].to_s).to eq('foo')
75
+ expect(result[:a].to_s).to eq('a')
76
+ expect(result[:b].to_s).to eq('b')
77
+ expect(result[:c].to_s).to eq('c')
78
+ expect(result[:annotated]).to eq({ important_bit: 'foo' })
79
+ end
80
+ end
81
+
82
+ describe '.demo_simple_string' do
83
+ it 'parses quoted strings' do
84
+ result = ReadmeExample.demo_simple_string
85
+ expect(result).to be_a(Parslet::Slice)
86
+ expect(result.to_s).to eq('"Simple Simple Simple"')
87
+ end
88
+ end
89
+
90
+ describe '.demo_smalltalk' do
91
+ it 'parses smalltalk keyword' do
92
+ result = ReadmeExample.demo_smalltalk
93
+ expect(result).to be_a(Parslet::Slice)
94
+ expect(result.to_s).to eq('smalltalk')
95
+ end
96
+ end
97
+ end
98
+
99
+ describe ReadmeExample::SimpleStringParser do
100
+ let(:parser) { ReadmeExample::SimpleStringParser.new }
101
+
102
+ it 'parses simple quoted strings' do
103
+ result = parser.parse('"hello"')
104
+ expect(result).to be_a(Parslet::Slice)
105
+ expect(result.to_s).to eq('"hello"')
106
+ end
107
+
108
+ it 'parses strings with spaces' do
109
+ result = parser.parse('"hello world"')
110
+ expect(result).to be_a(Parslet::Slice)
111
+ expect(result.to_s).to eq('"hello world"')
112
+ end
113
+
114
+ it 'parses empty strings' do
115
+ result = parser.parse('""')
116
+ expect(result).to be_a(Parslet::Slice)
117
+ expect(result.to_s).to eq('""')
118
+ end
119
+
120
+ it 'parses strings with special characters' do
121
+ result = parser.parse('"hello!@#$%^&*()"')
122
+ expect(result).to be_a(Parslet::Slice)
123
+ expect(result.to_s).to eq('"hello!@#$%^&*()"')
124
+ end
125
+
126
+ it 'fails on unquoted strings' do
127
+ expect { parser.parse('hello') }.to raise_error(Parslet::ParseFailed)
128
+ end
129
+
130
+ it 'fails on unclosed quotes' do
131
+ expect { parser.parse('"hello') }.to raise_error(Parslet::ParseFailed)
132
+ end
133
+
134
+ it 'fails on strings starting without quote' do
135
+ expect { parser.parse('hello"') }.to raise_error(Parslet::ParseFailed)
136
+ end
137
+ end
138
+
139
+ describe ReadmeExample::SmalltalkParser do
140
+ let(:parser) { ReadmeExample::SmalltalkParser.new }
141
+
142
+ it 'parses smalltalk keyword' do
143
+ result = parser.parse('smalltalk')
144
+ expect(result).to be_a(Parslet::Slice)
145
+ expect(result.to_s).to eq('smalltalk')
146
+ end
147
+
148
+ it 'fails on other keywords' do
149
+ expect { parser.parse('ruby') }.to raise_error(Parslet::ParseFailed)
150
+ expect { parser.parse('python') }.to raise_error(Parslet::ParseFailed)
151
+ end
152
+
153
+ it 'fails on partial matches' do
154
+ expect { parser.parse('small') }.to raise_error(Parslet::ParseFailed)
155
+ expect { parser.parse('smalltalking') }.to raise_error(Parslet::ParseFailed)
156
+ end
157
+ end
158
+
159
+ describe 'integration with original readme examples' do
160
+ it 'reproduces str parsing example' do
161
+ result = str('foo').parse('foo')
162
+ expect(result).to be_a(Parslet::Slice)
163
+ expect(result.to_s).to eq('foo')
164
+ end
165
+
166
+ it 'reproduces match parsing examples' do
167
+ parser = Parslet.match('[abc]')
168
+ expect(parser.parse('a').to_s).to eq('a')
169
+ expect(parser.parse('b').to_s).to eq('b')
170
+ expect(parser.parse('c').to_s).to eq('c')
171
+ end
172
+
173
+ it 'reproduces annotation example' do
174
+ result = str('foo').as(:important_bit).parse('foo')
175
+ expect(result).to eq({ important_bit: 'foo' })
176
+ end
177
+
178
+ it 'reproduces simple string parser example' do
179
+ quote = str('"')
180
+ simple_string = quote >> (quote.absent? >> any).repeat >> quote
181
+ result = simple_string.parse('"Simple Simple Simple"')
182
+ expect(result).to be_a(Parslet::Slice)
183
+ expect(result.to_s).to eq('"Simple Simple Simple"')
184
+ end
185
+
186
+ it 'reproduces smalltalk parser example' do
187
+ parser = ReadmeExample::SmalltalkParser.new
188
+ result = parser.parse('smalltalk')
189
+ expect(result).to be_a(Parslet::Slice)
190
+ expect(result.to_s).to eq('smalltalk')
191
+ end
192
+ end
193
+
194
+ describe 'error handling' do
195
+ it 'provides meaningful error messages for string parsing' do
196
+ expect { str('foo').parse('bar') }.to raise_error(Parslet::ParseFailed, /Expected "foo"/)
197
+ end
198
+
199
+ it 'provides meaningful error messages for character set parsing' do
200
+ expect { Parslet.match('[abc]').parse('x') }.to raise_error(Parslet::ParseFailed, /Failed to match \[abc\]/)
201
+ end
202
+
203
+ it 'handles empty input appropriately' do
204
+ expect { str('foo').parse('') }.to raise_error(Parslet::ParseFailed)
205
+ expect { Parslet.match('[abc]').parse('') }.to raise_error(Parslet::ParseFailed)
206
+ end
207
+ end
208
+
209
+ describe 'edge cases' do
210
+ it 'handles whitespace in string parsing' do
211
+ parser = str(' ')
212
+ result = parser.parse(' ')
213
+ expect(result.to_s).to eq(' ')
214
+ end
215
+
216
+ it 'handles special characters in match parsing' do
217
+ parser = Parslet.match('[\n\t]')
218
+ expect(parser.parse("\n").to_s).to eq("\n")
219
+ expect(parser.parse("\t").to_s).to eq("\t")
220
+ end
221
+
222
+ it 'handles unicode characters' do
223
+ parser = str('café')
224
+ result = parser.parse('café')
225
+ expect(result.to_s).to eq('café')
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,187 @@
1
+ require 'spec_helper'
2
+ require 'fixtures/examples/scopes'
3
+
4
+ RSpec.describe 'Scopes Example' do
5
+ include ScopesExample
6
+
7
+ describe 'ScopesExample basic scoping' do
8
+ describe '.create_parser' do
9
+ let(:parser) { ScopesExample.create_parser }
10
+
11
+ it 'returns a parslet atom' do
12
+ expect(parser).to respond_to(:parse)
13
+ end
14
+
15
+ it 'parses the expected scoped pattern "aba"' do
16
+ result = parser.parse('aba')
17
+ expect(result).to be_a(Parslet::Slice)
18
+ expect(result.to_s).to eq('aba')
19
+ end
20
+
21
+ it 'demonstrates scope behavior - captures work as expected' do
22
+ # The parser: str('a').capture(:a) >> scope { str('b').capture(:a) } >> dynamic { |s,c| str(c.captures[:a]) }
23
+ # First captures 'a' as :a, then in scope captures 'b' as :a
24
+ # The dynamic part uses the outer :a capture (which is 'a') to match the last character
25
+ # So it should parse 'aba'
26
+ result = parser.parse('aba')
27
+ expect(result).to be_a(Parslet::Slice)
28
+ expect(result.to_s).to eq('aba')
29
+ end
30
+
31
+ it 'fails when dynamic part does not match outer capture' do
32
+ # Should fail because the last character should match the outer capture 'a', not 'b'
33
+ expect { parser.parse('abb') }.to raise_error(Parslet::ParseFailed)
34
+ end
35
+
36
+ it 'fails on completely invalid input' do
37
+ expect { parser.parse('xyz') }.to raise_error(Parslet::ParseFailed)
38
+ end
39
+
40
+ it 'fails on partial input' do
41
+ expect { parser.parse('ab') }.to raise_error(Parslet::ParseFailed)
42
+ end
43
+ end
44
+
45
+ describe '.parse_scoped_input' do
46
+ it 'parses default input successfully' do
47
+ # Default should be 'aba' based on actual scoping behavior
48
+ result = ScopesExample.parse_scoped_input('aba')
49
+ expect(result).to be_a(Parslet::Slice)
50
+ expect(result.to_s).to eq('aba')
51
+ end
52
+
53
+ it 'parses custom valid input' do
54
+ result = ScopesExample.parse_scoped_input('aba')
55
+ expect(result).to be_a(Parslet::Slice)
56
+ expect(result.to_s).to eq('aba')
57
+ end
58
+
59
+ it 'fails on invalid input' do
60
+ expect { ScopesExample.parse_scoped_input('abc') }.to raise_error(Parslet::ParseFailed)
61
+ end
62
+ end
63
+
64
+ describe '.demonstrate_scope_success' do
65
+ it 'demonstrates successful scoped parsing' do
66
+ # This should work with the correct scoped pattern
67
+ result = ScopesExample.demonstrate_scope_success
68
+ expect(result).to be_a(Parslet::Slice)
69
+ expect(result.to_s).to eq('aba')
70
+ end
71
+ end
72
+
73
+ describe '.demonstrate_scope_failure' do
74
+ it 'returns ParseFailed exception instead of raising it' do
75
+ result = ScopesExample.demonstrate_scope_failure
76
+ expect(result).to be_a(Parslet::ParseFailed)
77
+ end
78
+
79
+ it 'captures the error message' do
80
+ error = ScopesExample.demonstrate_scope_failure
81
+ expect(error.message).to be_a(String)
82
+ expect(error.message.length).to be > 0
83
+ end
84
+
85
+ it 'does not raise an exception' do
86
+ expect { ScopesExample.demonstrate_scope_failure }.not_to raise_error
87
+ end
88
+ end
89
+ end
90
+
91
+ describe 'ScopesExample nested scoping' do
92
+ describe '.create_nested_scope_parser' do
93
+ let(:parser) { ScopesExample.create_nested_scope_parser }
94
+
95
+ it 'returns a parslet atom' do
96
+ expect(parser).to respond_to(:parse)
97
+ end
98
+
99
+ it 'parses nested scoped pattern correctly' do
100
+ # Parser: str('x').capture(:outer) >> scope { str('y').capture(:outer) >> dynamic { |s,c| str(c.captures[:outer]) } } >> dynamic { |s,c| str(c.captures[:outer]) }
101
+ # Captures 'x' as :outer, then in scope 'y' shadows :outer, first dynamic uses 'y', then outer dynamic uses original 'x'
102
+ # So pattern should be: x y y x = 'xyyx'
103
+ result = parser.parse('xyyx')
104
+ expect(result).to be_a(Parslet::Slice)
105
+ expect(result.to_s).to eq('xyyx')
106
+ end
107
+
108
+ it 'fails on incorrect nested pattern' do
109
+ expect { parser.parse('xyzx') }.to raise_error(Parslet::ParseFailed)
110
+ end
111
+
112
+ it 'fails on partial input' do
113
+ expect { parser.parse('xy') }.to raise_error(Parslet::ParseFailed)
114
+ end
115
+ end
116
+
117
+ describe '.parse_nested_scoped_input' do
118
+ it 'parses default nested input successfully' do
119
+ # Default should be 'xyyx' based on nested scoping
120
+ result = ScopesExample.parse_nested_scoped_input('xyyx')
121
+ expect(result).to be_a(Parslet::Slice)
122
+ expect(result.to_s).to eq('xyyx')
123
+ end
124
+
125
+ it 'parses custom valid nested input' do
126
+ result = ScopesExample.parse_nested_scoped_input('xyyx')
127
+ expect(result).to be_a(Parslet::Slice)
128
+ expect(result.to_s).to eq('xyyx')
129
+ end
130
+
131
+ it 'fails on invalid nested input' do
132
+ expect { ScopesExample.parse_nested_scoped_input('xyzx') }.to raise_error(Parslet::ParseFailed)
133
+ end
134
+ end
135
+ end
136
+
137
+ describe 'scope behavior demonstration' do
138
+ it 'shows how scopes work with captures' do
139
+ parser = ScopesExample.create_parser
140
+
141
+ # The parser demonstrates scope behavior with captures
142
+ # The dynamic part uses the outer :a capture (which is 'a')
143
+ result = parser.parse('aba')
144
+ expect(result.to_s).to eq('aba')
145
+ end
146
+
147
+ it 'shows nested scope behavior' do
148
+ parser = ScopesExample.create_nested_scope_parser
149
+
150
+ # Demonstrates multiple levels of scoping
151
+ result = parser.parse('xyyx')
152
+ expect(result.to_s).to eq('xyyx')
153
+ end
154
+
155
+ it 'demonstrates scope functionality' do
156
+ # Test the basic scoped parser
157
+ parser1 = ScopesExample.create_parser
158
+
159
+ # Should work with the correct pattern
160
+ expect(parser1.parse('aba').to_s).to eq('aba')
161
+ end
162
+ end
163
+
164
+ describe 'error handling' do
165
+ it 'provides meaningful error messages for scope violations' do
166
+ parser = ScopesExample.create_parser
167
+
168
+ begin
169
+ parser.parse('abc')
170
+ fail 'Expected ParseFailed to be raised'
171
+ rescue Parslet::ParseFailed => e
172
+ expect(e.message).to be_a(String)
173
+ expect(e.message.length).to be > 0
174
+ end
175
+ end
176
+
177
+ it 'handles empty input' do
178
+ parser = ScopesExample.create_parser
179
+ expect { parser.parse('') }.to raise_error(Parslet::ParseFailed)
180
+ end
181
+
182
+ it 'handles malformed input for nested scopes' do
183
+ parser = ScopesExample.create_nested_scope_parser
184
+ expect { parser.parse('invalid') }.to raise_error(Parslet::ParseFailed)
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,196 @@
1
+ require 'spec_helper'
2
+ require_relative '../fixtures/examples/seasons'
3
+
4
+ RSpec.describe 'Seasons Transform Example' do
5
+ let(:initial_tree) { { bud: { stem: [] } } }
6
+
7
+ describe SeasonsExample::Spring do
8
+ let(:spring) { SeasonsExample::Spring.new }
9
+
10
+ it 'adds a leaf branch to empty stems' do
11
+ tree = { stem: [] }
12
+ result = spring.apply(tree)
13
+ expect(result).to eq({ stem: [{ branch: :leaf }] })
14
+ end
15
+
16
+ it 'does not modify stems that are not empty arrays' do
17
+ # Spring only matches empty stems (sequence), not stems with existing content
18
+ tree = { stem: [{ branch: :existing }] }
19
+ result = spring.apply(tree)
20
+ expect(result).to eq({ stem: [{ branch: :existing }] })
21
+ end
22
+
23
+ it 'works with nested structures' do
24
+ tree = { bud: { stem: [] } }
25
+ result = spring.apply(tree)
26
+ expect(result).to eq({ bud: { stem: [{ branch: :leaf }] } })
27
+ end
28
+ end
29
+
30
+ describe SeasonsExample::Summer do
31
+ let(:summer) { SeasonsExample::Summer.new }
32
+
33
+ it 'transforms leaf branches to leaf and flower' do
34
+ tree = { stem: [{ branch: :leaf }] }
35
+ result = summer.apply(tree)
36
+ expect(result).to eq({ stem: [{ branch: [:leaf, :flower] }] })
37
+ end
38
+
39
+ it 'transforms multiple branches' do
40
+ tree = { stem: [{ branch: :leaf }, { branch: :leaf }] }
41
+ result = summer.apply(tree)
42
+ expect(result).to eq({ stem: [{ branch: [:leaf, :flower] }, { branch: [:leaf, :flower] }] })
43
+ end
44
+
45
+ it 'works with nested structures' do
46
+ tree = { bud: { stem: [{ branch: :leaf }] } }
47
+ result = summer.apply(tree)
48
+ expect(result).to eq({ bud: { stem: [{ branch: [:leaf, :flower] }] } })
49
+ end
50
+ end
51
+
52
+ describe SeasonsExample::Fall do
53
+ let(:fall) { SeasonsExample::Fall.new }
54
+
55
+ it 'empties branches and prints messages' do
56
+ tree = { branch: [:leaf, :flower] }
57
+
58
+ # Capture output
59
+ output = capture_output do
60
+ result = fall.apply(tree)
61
+ expect(result).to eq({ branch: [] })
62
+ end
63
+
64
+ expect(output).to include("Fruit!")
65
+ expect(output).to include("Falling Leaves!")
66
+ end
67
+
68
+ it 'handles branches with only leaves' do
69
+ tree = { branch: [:leaf] }
70
+
71
+ output = capture_output do
72
+ result = fall.apply(tree)
73
+ expect(result).to eq({ branch: [] })
74
+ end
75
+
76
+ expect(output).to include("Falling Leaves!")
77
+ expect(output).not_to include("Fruit!")
78
+ end
79
+
80
+ it 'handles branches with only flowers' do
81
+ tree = { branch: [:flower] }
82
+
83
+ output = capture_output do
84
+ result = fall.apply(tree)
85
+ expect(result).to eq({ branch: [] })
86
+ end
87
+
88
+ expect(output).to include("Fruit!")
89
+ expect(output).not_to include("Falling Leaves!")
90
+ end
91
+ end
92
+
93
+ describe SeasonsExample::Winter do
94
+ let(:winter) { SeasonsExample::Winter.new }
95
+
96
+ it 'empties all stems' do
97
+ tree = { stem: [{ branch: [] }, { branch: [] }] }
98
+ result = winter.apply(tree)
99
+ expect(result).to eq({ stem: [] })
100
+ end
101
+
102
+ it 'works with nested structures' do
103
+ tree = { bud: { stem: [{ branch: [] }] } }
104
+ result = winter.apply(tree)
105
+ expect(result).to eq({ bud: { stem: [] } })
106
+ end
107
+ end
108
+
109
+ describe 'do_seasons function' do
110
+ it 'cycles through all seasons correctly' do
111
+ tree = { bud: { stem: [] } }
112
+
113
+ output = capture_output do
114
+ result = SeasonsExample.do_seasons(tree)
115
+
116
+ # After all seasons, should be back to empty stem
117
+ expect(result).to eq({ bud: { stem: [] } })
118
+ end
119
+
120
+ # Should contain season announcements
121
+ expect(output).to include("And when SeasonsExample::Spring comes")
122
+ expect(output).to include("And when SeasonsExample::Summer comes")
123
+ expect(output).to include("And when SeasonsExample::Fall comes")
124
+ expect(output).to include("And when SeasonsExample::Winter comes")
125
+
126
+ # Should contain fall messages
127
+ expect(output).to include("Fruit!")
128
+ expect(output).to include("Falling Leaves!")
129
+ end
130
+ end
131
+
132
+ describe 'integration test' do
133
+ it 'processes the complete seasonal cycle' do
134
+ tree = { bud: { stem: [] } }
135
+
136
+ # Spring: adds leaf
137
+ spring = SeasonsExample::Spring.new
138
+ tree = spring.apply(tree)
139
+ expect(tree).to eq({ bud: { stem: [{ branch: :leaf }] } })
140
+
141
+ # Summer: leaf becomes leaf + flower
142
+ summer = SeasonsExample::Summer.new
143
+ tree = summer.apply(tree)
144
+ expect(tree).to eq({ bud: { stem: [{ branch: [:leaf, :flower] }] } })
145
+
146
+ # Fall: branch becomes empty
147
+ fall = SeasonsExample::Fall.new
148
+ output = capture_output do
149
+ tree = fall.apply(tree)
150
+ end
151
+ expect(tree).to eq({ bud: { stem: [{ branch: [] }] } })
152
+ expect(output).to include("Fruit!")
153
+ expect(output).to include("Falling Leaves!")
154
+
155
+ # Winter: stem becomes empty
156
+ winter = SeasonsExample::Winter.new
157
+ tree = winter.apply(tree)
158
+ expect(tree).to eq({ bud: { stem: [] } })
159
+ end
160
+
161
+ it 'runs two complete cycles as in the example' do
162
+ tree = { bud: { stem: [] } }
163
+
164
+ output = capture_output do
165
+ # First cycle
166
+ tree = SeasonsExample.do_seasons(tree)
167
+ expect(tree).to eq({ bud: { stem: [] } })
168
+
169
+ # Second cycle
170
+ tree = SeasonsExample.do_seasons(tree)
171
+ expect(tree).to eq({ bud: { stem: [] } })
172
+ end
173
+
174
+ # Should have two sets of season announcements
175
+ season_announcements = output.scan(/And when SeasonsExample::\w+ comes/).length
176
+ expect(season_announcements).to eq(8) # 4 seasons × 2 cycles
177
+
178
+ # Should have fruit and falling leaves messages from both cycles
179
+ fruit_count = output.scan(/Fruit!/).length
180
+ leaves_count = output.scan(/Falling Leaves!/).length
181
+ expect(fruit_count).to eq(2) # Once per cycle
182
+ expect(leaves_count).to eq(2) # Once per cycle
183
+ end
184
+ end
185
+
186
+ private
187
+
188
+ def capture_output
189
+ original_stdout = $stdout
190
+ $stdout = StringIO.new
191
+ yield
192
+ $stdout.string
193
+ ensure
194
+ $stdout = original_stdout
195
+ end
196
+ end