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.
- checksums.yaml +7 -0
- data/HISTORY.txt +284 -0
- data/LICENSE +23 -0
- data/README.adoc +454 -0
- data/Rakefile +71 -0
- data/lib/parslet/accelerator/application.rb +62 -0
- data/lib/parslet/accelerator/engine.rb +112 -0
- data/lib/parslet/accelerator.rb +162 -0
- data/lib/parslet/atoms/alternative.rb +53 -0
- data/lib/parslet/atoms/base.rb +157 -0
- data/lib/parslet/atoms/can_flatten.rb +137 -0
- data/lib/parslet/atoms/capture.rb +38 -0
- data/lib/parslet/atoms/context.rb +103 -0
- data/lib/parslet/atoms/dsl.rb +112 -0
- data/lib/parslet/atoms/dynamic.rb +32 -0
- data/lib/parslet/atoms/entity.rb +45 -0
- data/lib/parslet/atoms/ignored.rb +26 -0
- data/lib/parslet/atoms/infix.rb +115 -0
- data/lib/parslet/atoms/lookahead.rb +52 -0
- data/lib/parslet/atoms/named.rb +32 -0
- data/lib/parslet/atoms/re.rb +41 -0
- data/lib/parslet/atoms/repetition.rb +87 -0
- data/lib/parslet/atoms/scope.rb +26 -0
- data/lib/parslet/atoms/sequence.rb +48 -0
- data/lib/parslet/atoms/str.rb +42 -0
- data/lib/parslet/atoms/visitor.rb +89 -0
- data/lib/parslet/atoms.rb +34 -0
- data/lib/parslet/cause.rb +101 -0
- data/lib/parslet/context.rb +21 -0
- data/lib/parslet/convenience.rb +33 -0
- data/lib/parslet/error_reporter/contextual.rb +120 -0
- data/lib/parslet/error_reporter/deepest.rb +100 -0
- data/lib/parslet/error_reporter/tree.rb +63 -0
- data/lib/parslet/error_reporter.rb +8 -0
- data/lib/parslet/export.rb +163 -0
- data/lib/parslet/expression/treetop.rb +92 -0
- data/lib/parslet/expression.rb +51 -0
- data/lib/parslet/graphviz.rb +97 -0
- data/lib/parslet/parser.rb +68 -0
- data/lib/parslet/pattern/binding.rb +49 -0
- data/lib/parslet/pattern.rb +113 -0
- data/lib/parslet/position.rb +21 -0
- data/lib/parslet/rig/rspec.rb +52 -0
- data/lib/parslet/scope.rb +42 -0
- data/lib/parslet/slice.rb +105 -0
- data/lib/parslet/source/line_cache.rb +99 -0
- data/lib/parslet/source.rb +96 -0
- data/lib/parslet/transform.rb +265 -0
- data/lib/parslet/version.rb +5 -0
- data/lib/parslet.rb +314 -0
- data/plurimath-parslet.gemspec +42 -0
- data/spec/acceptance/infix_parser_spec.rb +145 -0
- data/spec/acceptance/mixing_parsers_spec.rb +74 -0
- data/spec/acceptance/regression_spec.rb +329 -0
- data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
- data/spec/acceptance/unconsumed_input_spec.rb +21 -0
- data/spec/examples/boolean_algebra_spec.rb +257 -0
- data/spec/examples/calc_spec.rb +278 -0
- data/spec/examples/capture_spec.rb +137 -0
- data/spec/examples/comments_spec.rb +186 -0
- data/spec/examples/deepest_errors_spec.rb +420 -0
- data/spec/examples/documentation_spec.rb +205 -0
- data/spec/examples/email_parser_spec.rb +275 -0
- data/spec/examples/empty_spec.rb +37 -0
- data/spec/examples/erb_spec.rb +482 -0
- data/spec/examples/ip_address_spec.rb +153 -0
- data/spec/examples/json_spec.rb +413 -0
- data/spec/examples/local_spec.rb +302 -0
- data/spec/examples/mathn_spec.rb +151 -0
- data/spec/examples/minilisp_spec.rb +492 -0
- data/spec/examples/modularity_spec.rb +340 -0
- data/spec/examples/nested_errors_spec.rb +322 -0
- data/spec/examples/optimized_erb_spec.rb +299 -0
- data/spec/examples/parens_spec.rb +239 -0
- data/spec/examples/prec_calc_spec.rb +525 -0
- data/spec/examples/readme_spec.rb +228 -0
- data/spec/examples/scopes_spec.rb +187 -0
- data/spec/examples/seasons_spec.rb +196 -0
- data/spec/examples/sentence_spec.rb +119 -0
- data/spec/examples/simple_xml_spec.rb +250 -0
- data/spec/examples/string_parser_spec.rb +407 -0
- data/spec/fixtures/examples/boolean_algebra.rb +62 -0
- data/spec/fixtures/examples/calc.rb +86 -0
- data/spec/fixtures/examples/capture.rb +36 -0
- data/spec/fixtures/examples/comments.rb +22 -0
- data/spec/fixtures/examples/deepest_errors.rb +99 -0
- data/spec/fixtures/examples/documentation.rb +32 -0
- data/spec/fixtures/examples/email_parser.rb +42 -0
- data/spec/fixtures/examples/empty.rb +10 -0
- data/spec/fixtures/examples/erb.rb +39 -0
- data/spec/fixtures/examples/ip_address.rb +103 -0
- data/spec/fixtures/examples/json.rb +107 -0
- data/spec/fixtures/examples/local.rb +60 -0
- data/spec/fixtures/examples/mathn.rb +47 -0
- data/spec/fixtures/examples/minilisp.rb +75 -0
- data/spec/fixtures/examples/modularity.rb +60 -0
- data/spec/fixtures/examples/nested_errors.rb +95 -0
- data/spec/fixtures/examples/optimized_erb.rb +105 -0
- data/spec/fixtures/examples/parens.rb +25 -0
- data/spec/fixtures/examples/prec_calc.rb +71 -0
- data/spec/fixtures/examples/readme.rb +59 -0
- data/spec/fixtures/examples/scopes.rb +43 -0
- data/spec/fixtures/examples/seasons.rb +40 -0
- data/spec/fixtures/examples/sentence.rb +18 -0
- data/spec/fixtures/examples/simple_xml.rb +51 -0
- data/spec/fixtures/examples/string_parser.rb +77 -0
- data/spec/parslet/atom_results_spec.rb +39 -0
- data/spec/parslet/atoms/alternative_spec.rb +26 -0
- data/spec/parslet/atoms/base_spec.rb +127 -0
- data/spec/parslet/atoms/capture_spec.rb +21 -0
- data/spec/parslet/atoms/combinations_spec.rb +5 -0
- data/spec/parslet/atoms/dsl_spec.rb +7 -0
- data/spec/parslet/atoms/entity_spec.rb +77 -0
- data/spec/parslet/atoms/ignored_spec.rb +15 -0
- data/spec/parslet/atoms/infix_spec.rb +5 -0
- data/spec/parslet/atoms/lookahead_spec.rb +22 -0
- data/spec/parslet/atoms/named_spec.rb +4 -0
- data/spec/parslet/atoms/re_spec.rb +14 -0
- data/spec/parslet/atoms/repetition_spec.rb +24 -0
- data/spec/parslet/atoms/scope_spec.rb +26 -0
- data/spec/parslet/atoms/sequence_spec.rb +28 -0
- data/spec/parslet/atoms/str_spec.rb +15 -0
- data/spec/parslet/atoms/visitor_spec.rb +101 -0
- data/spec/parslet/atoms_spec.rb +488 -0
- data/spec/parslet/convenience_spec.rb +54 -0
- data/spec/parslet/error_reporter/contextual_spec.rb +118 -0
- data/spec/parslet/error_reporter/deepest_spec.rb +82 -0
- data/spec/parslet/error_reporter/tree_spec.rb +7 -0
- data/spec/parslet/export_spec.rb +40 -0
- data/spec/parslet/expression/treetop_spec.rb +74 -0
- data/spec/parslet/minilisp.citrus +29 -0
- data/spec/parslet/minilisp.tt +29 -0
- data/spec/parslet/parser_spec.rb +36 -0
- data/spec/parslet/parslet_spec.rb +38 -0
- data/spec/parslet/pattern_spec.rb +272 -0
- data/spec/parslet/position_spec.rb +14 -0
- data/spec/parslet/rig/rspec_spec.rb +54 -0
- data/spec/parslet/scope_spec.rb +45 -0
- data/spec/parslet/slice_spec.rb +186 -0
- data/spec/parslet/source/line_cache_spec.rb +74 -0
- data/spec/parslet/source_spec.rb +210 -0
- data/spec/parslet/transform/context_spec.rb +56 -0
- data/spec/parslet/transform_spec.rb +183 -0
- data/spec/spec_helper.rb +74 -0
- data/spec/support/opal.rb +8 -0
- data/spec/support/opal.rb.erb +14 -0
- data/spec/support/parslet_matchers.rb +96 -0
- 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
|