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,302 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fixtures/examples/local'
|
3
|
+
|
4
|
+
RSpec.describe 'Local Example' do
|
5
|
+
include Parslet
|
6
|
+
|
7
|
+
describe 'LocalExample module methods' do
|
8
|
+
describe '.this' do
|
9
|
+
it 'creates a Parslet::Atoms::Entity' do
|
10
|
+
entity = LocalExample.this('test') { str('a') }
|
11
|
+
expect(entity).to be_a(Parslet::Atoms::Entity)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'accepts a block for recursive definitions' do
|
15
|
+
expect { LocalExample.this('test') { str('a') } }.not_to raise_error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.epsilon' do
|
20
|
+
it 'creates an epsilon parser (matches empty string)' do
|
21
|
+
epsilon = LocalExample.epsilon
|
22
|
+
expect(epsilon).to be_a(Parslet::Atoms::Base)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'matches empty input' do
|
26
|
+
result = LocalExample.epsilon.parse('')
|
27
|
+
expect(result.to_s).to eq('')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'fails on non-empty input' do
|
31
|
+
expect { LocalExample.epsilon.parse('a') }.to raise_error(Parslet::ParseFailed)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '.simple_inline_parser' do
|
36
|
+
it 'parses minimum valid input ("aa")' do
|
37
|
+
result = LocalExample.simple_inline_parser.parse('aa')
|
38
|
+
expect(result.to_s).to eq('aa')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'parses sequences with additional "a"s before "aa"' do
|
42
|
+
result = LocalExample.simple_inline_parser.parse('aaa')
|
43
|
+
expect(result.to_s).to eq('aaa')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'parses longer sequences' do
|
47
|
+
result = LocalExample.simple_inline_parser.parse('aaaaa')
|
48
|
+
expect(result.to_s).to eq('aaaaa')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'parses longest supported sequence' do
|
52
|
+
result = LocalExample.simple_inline_parser.parse('aaaaaa')
|
53
|
+
expect(result.to_s).to eq('aaaaaa')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'fails on sequences longer than supported' do
|
57
|
+
expect { LocalExample.simple_inline_parser.parse('aaaaaaa') }.to raise_error(Parslet::ParseFailed)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'fails on single "a"' do
|
61
|
+
expect { LocalExample.simple_inline_parser.parse('a') }.to raise_error(Parslet::ParseFailed)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'fails on empty input' do
|
65
|
+
expect { LocalExample.simple_inline_parser.parse('') }.to raise_error(Parslet::ParseFailed)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'fails on non-"a" characters' do
|
69
|
+
expect { LocalExample.simple_inline_parser.parse('baa') }.to raise_error(Parslet::ParseFailed)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '.greedy_blind_parser' do
|
74
|
+
it 'parses sequences of "a" greedily' do
|
75
|
+
result = LocalExample.greedy_blind_parser.parse('aaaa')
|
76
|
+
expect(result).to be_a(Hash)
|
77
|
+
expect(result).to have_key(:e)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'handles empty input (epsilon case)' do
|
81
|
+
result = LocalExample.greedy_blind_parser.parse('')
|
82
|
+
expect(result.to_s).to eq('')
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'handles single "a"' do
|
86
|
+
result = LocalExample.greedy_blind_parser.parse('a')
|
87
|
+
expect(result).to be_a(Hash)
|
88
|
+
expect(result[:e].to_s).to eq('a')
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'creates recursive structure for multiple "a"s' do
|
92
|
+
result = LocalExample.greedy_blind_parser.parse('aaa')
|
93
|
+
expect(result).to be_a(Hash)
|
94
|
+
expect(result).to have_key(:e)
|
95
|
+
expect(result).to have_key(:rec)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'fails on non-"a" characters' do
|
99
|
+
expect { LocalExample.greedy_blind_parser.parse('b') }.to raise_error(Parslet::ParseFailed)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe '.greedy_non_blind_parser' do
|
104
|
+
it 'parses "aa" as end pattern' do
|
105
|
+
result = LocalExample.greedy_non_blind_parser.parse('aa')
|
106
|
+
expect(result).to be_a(Hash)
|
107
|
+
expect(result).to have_key(:e2)
|
108
|
+
expect(result[:e2].to_s).to eq('aa')
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'handles longer sequences ending with "aa"' do
|
112
|
+
result = LocalExample.greedy_non_blind_parser.parse('aaaa')
|
113
|
+
expect(result).to be_a(Hash)
|
114
|
+
# Should have both e1 and rec components
|
115
|
+
expect(result).to have_key(:e1)
|
116
|
+
expect(result).to have_key(:rec)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'creates recursive structure for complex patterns' do
|
120
|
+
result = LocalExample.greedy_non_blind_parser.parse('aaaaaa')
|
121
|
+
expect(result).to be_a(Hash)
|
122
|
+
expect(result).to have_key(:e1)
|
123
|
+
expect(result).to have_key(:rec)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'fails on single "a"' do
|
127
|
+
expect { LocalExample.greedy_non_blind_parser.parse('a') }.to raise_error(Parslet::ParseFailed)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'fails on empty input' do
|
131
|
+
expect { LocalExample.greedy_non_blind_parser.parse('') }.to raise_error(Parslet::ParseFailed)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '.demonstrate_local_variables' do
|
136
|
+
it 'returns a parser constructed with local variables' do
|
137
|
+
parser = LocalExample.demonstrate_local_variables
|
138
|
+
expect(parser).to be_a(Parslet::Atoms::Base)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'creates a working parser from local variables' do
|
142
|
+
parser = LocalExample.demonstrate_local_variables
|
143
|
+
result = parser.parse('aaaa')
|
144
|
+
expect(result.to_s).to eq('aaaa')
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'demonstrates local variable scoping' do
|
148
|
+
parser = LocalExample.demonstrate_local_variables
|
149
|
+
expect { parser.parse('aa') }.not_to raise_error
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe 'parsing methods' do
|
155
|
+
describe '.parse_with_greedy_blind' do
|
156
|
+
it 'parses input using greedy blind parser' do
|
157
|
+
result = LocalExample.parse_with_greedy_blind('aaa')
|
158
|
+
expect(result).to be_a(Hash)
|
159
|
+
expect(result).to have_key(:e)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'handles empty input' do
|
163
|
+
result = LocalExample.parse_with_greedy_blind('')
|
164
|
+
expect(result.to_s).to eq('')
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'raises error for invalid input' do
|
168
|
+
expect { LocalExample.parse_with_greedy_blind('b') }.to raise_error(Parslet::ParseFailed)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe '.parse_with_greedy_non_blind' do
|
173
|
+
it 'parses input using greedy non-blind parser' do
|
174
|
+
result = LocalExample.parse_with_greedy_non_blind('aaaa')
|
175
|
+
expect(result).to be_a(Hash)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'handles minimum valid input' do
|
179
|
+
result = LocalExample.parse_with_greedy_non_blind('aa')
|
180
|
+
expect(result).to be_a(Hash)
|
181
|
+
expect(result[:e2].to_s).to eq('aa')
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'raises error for insufficient input' do
|
185
|
+
expect { LocalExample.parse_with_greedy_non_blind('a') }.to raise_error(Parslet::ParseFailed)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe '.parse_with_simple_inline' do
|
190
|
+
it 'parses input using simple inline parser (minimum: "aa")' do
|
191
|
+
result = LocalExample.parse_with_simple_inline('aa')
|
192
|
+
expect(result.to_s).to eq('aa')
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'parses longer sequences' do
|
196
|
+
result = LocalExample.parse_with_simple_inline('aaaaa')
|
197
|
+
expect(result.to_s).to eq('aaaaa')
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'raises error for single "a"' do
|
201
|
+
expect { LocalExample.parse_with_simple_inline('a') }.to raise_error(Parslet::ParseFailed)
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'raises error for empty input' do
|
205
|
+
expect { LocalExample.parse_with_simple_inline('') }.to raise_error(Parslet::ParseFailed)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe 'advanced parsing concepts' do
|
211
|
+
describe 'inline parser construction' do
|
212
|
+
it 'demonstrates parser construction without class wrapper' do
|
213
|
+
# This shows the concept of building parsers inline
|
214
|
+
inline_parser = str('hello') >> str(' ') >> str('world')
|
215
|
+
result = inline_parser.parse('hello world')
|
216
|
+
expect(result.to_s).to eq('hello world')
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'shows local variable usage in parser building' do
|
220
|
+
# Demonstrates using local variables to build parsers
|
221
|
+
greeting = str('hello')
|
222
|
+
space = str(' ')
|
223
|
+
target = str('world')
|
224
|
+
parser = greeting >> space >> target
|
225
|
+
|
226
|
+
result = parser.parse('hello world')
|
227
|
+
expect(result.to_s).to eq('hello world')
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe 'greedy vs non-greedy parsing' do
|
232
|
+
it 'compares greedy blind vs greedy non-blind behavior' do
|
233
|
+
input = 'aaaa'
|
234
|
+
|
235
|
+
# Both should parse successfully but with different structures
|
236
|
+
blind_result = LocalExample.parse_with_greedy_blind(input)
|
237
|
+
non_blind_result = LocalExample.parse_with_greedy_non_blind(input)
|
238
|
+
|
239
|
+
expect(blind_result).to be_a(Hash)
|
240
|
+
expect(non_blind_result).to be_a(Hash)
|
241
|
+
|
242
|
+
# They should have different structures
|
243
|
+
expect(blind_result.keys).not_to eq(non_blind_result.keys)
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'shows different parsing strategies for same input' do
|
247
|
+
input = 'aaaaaa'
|
248
|
+
|
249
|
+
# Test that both parsers handle the input but differently
|
250
|
+
expect { LocalExample.parse_with_greedy_blind(input) }.not_to raise_error
|
251
|
+
expect { LocalExample.parse_with_greedy_non_blind(input) }.not_to raise_error
|
252
|
+
|
253
|
+
blind_result = LocalExample.parse_with_greedy_blind(input)
|
254
|
+
non_blind_result = LocalExample.parse_with_greedy_non_blind(input)
|
255
|
+
|
256
|
+
# Results should be different in structure
|
257
|
+
expect(blind_result).not_to eq(non_blind_result)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
describe 'recursive parser construction' do
|
262
|
+
it 'demonstrates recursive entity usage' do
|
263
|
+
# The greedy parsers use recursive entities
|
264
|
+
result = LocalExample.parse_with_greedy_blind('aaaaa')
|
265
|
+
|
266
|
+
# Should create nested recursive structure
|
267
|
+
expect(result).to have_key(:e)
|
268
|
+
expect(result).to have_key(:rec)
|
269
|
+
|
270
|
+
# Recursive structure should be present
|
271
|
+
current = result
|
272
|
+
depth = 0
|
273
|
+
while current.is_a?(Hash) && current.key?(:rec) && current[:rec].is_a?(Hash)
|
274
|
+
current = current[:rec]
|
275
|
+
depth += 1
|
276
|
+
break if depth > 10 # Safety check
|
277
|
+
end
|
278
|
+
|
279
|
+
expect(depth).to be > 0
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe 'epsilon parser behavior' do
|
284
|
+
it 'demonstrates epsilon as empty match' do
|
285
|
+
epsilon = LocalExample.epsilon
|
286
|
+
|
287
|
+
# Should match empty string
|
288
|
+
expect { epsilon.parse('') }.not_to raise_error
|
289
|
+
|
290
|
+
# Should fail on any content
|
291
|
+
expect { epsilon.parse('a') }.to raise_error(Parslet::ParseFailed)
|
292
|
+
expect { epsilon.parse(' ') }.to raise_error(Parslet::ParseFailed)
|
293
|
+
end
|
294
|
+
|
295
|
+
it 'shows epsilon usage in alternatives' do
|
296
|
+
# Epsilon is used in the greedy blind parser as an alternative
|
297
|
+
result = LocalExample.parse_with_greedy_blind('')
|
298
|
+
expect(result.to_s).to eq('')
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Load the example file to get the classes
|
4
|
+
$:.unshift File.dirname(__FILE__) + "/../../example"
|
5
|
+
|
6
|
+
RSpec.describe 'Mathn Compatibility Example' do
|
7
|
+
describe 'parser behavior' do
|
8
|
+
let(:possible_whitespace) { Parslet.match['\s'].repeat }
|
9
|
+
let(:cephalopod) { Parslet.str('octopus') | Parslet.str('squid') }
|
10
|
+
let(:parenthesized_cephalopod) do
|
11
|
+
Parslet.str('(') >>
|
12
|
+
possible_whitespace >>
|
13
|
+
cephalopod >>
|
14
|
+
possible_whitespace >>
|
15
|
+
Parslet.str(')')
|
16
|
+
end
|
17
|
+
let(:parser) do
|
18
|
+
possible_whitespace >>
|
19
|
+
parenthesized_cephalopod >>
|
20
|
+
possible_whitespace
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'fails to parse invalid input as expected' do
|
24
|
+
# This should fail to parse because "sqeed" is not "squid" or "octopus"
|
25
|
+
expect { parser.parse %{(\nsqeed)\n} }.to raise_error(Parslet::ParseFailed)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'successfully parses valid cephalopod input' do
|
29
|
+
result = parser.parse %{(
|
30
|
+
squid)
|
31
|
+
}
|
32
|
+
expect(result.to_s).to eq("(\nsquid)\n")
|
33
|
+
|
34
|
+
result = parser.parse %{( octopus )}
|
35
|
+
expect(result.to_s).to eq('( octopus )')
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'handles whitespace correctly' do
|
39
|
+
result = parser.parse %{(squid)}
|
40
|
+
expect(result.to_s).to eq('(squid)')
|
41
|
+
|
42
|
+
result = parser.parse %{ ( squid ) }
|
43
|
+
expect(result.to_s).to eq(' ( squid ) ')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'mathn compatibility' do
|
48
|
+
def attempt_parse_with_timeout(timeout = 5)
|
49
|
+
# Opal doesn't support Threads, so we'll just run the parse directly
|
50
|
+
# The mathn compatibility issue was specific to MRI Ruby anyway
|
51
|
+
begin
|
52
|
+
possible_whitespace = Parslet.match['\s'].repeat
|
53
|
+
cephalopod = Parslet.str('octopus') | Parslet.str('squid')
|
54
|
+
parenthesized_cephalopod = Parslet.str('(') >> possible_whitespace >> cephalopod >> possible_whitespace >> Parslet.str(')')
|
55
|
+
parser = possible_whitespace >> parenthesized_cephalopod >> possible_whitespace
|
56
|
+
|
57
|
+
parser.parse %{(\nsqeed)\n}
|
58
|
+
rescue Parslet::ParseFailed => e
|
59
|
+
raise e
|
60
|
+
rescue => e
|
61
|
+
raise e
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'terminates properly before requiring mathn' do
|
66
|
+
# This should fail with ParseFailed, not hang
|
67
|
+
# In Opal, we don't have the mathn compatibility issue, so this should work fine
|
68
|
+
expect { attempt_parse_with_timeout }.to raise_error(Parslet::ParseFailed)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Skip mathn test on Ruby 2.5+ since mathn was deprecated
|
72
|
+
if RUBY_VERSION.gsub(/[^\d]/, '').to_i < 250
|
73
|
+
it 'still terminates properly after requiring mathn' do
|
74
|
+
# Require mathn in an isolated way
|
75
|
+
begin
|
76
|
+
require 'mathn'
|
77
|
+
|
78
|
+
# This should still fail with ParseFailed, not hang
|
79
|
+
# The fix in parslet should prevent infinite loops even with mathn loaded
|
80
|
+
expect { attempt_parse_with_timeout }.to raise_error(Parslet::ParseFailed)
|
81
|
+
rescue LoadError
|
82
|
+
skip "mathn not available in this Ruby version"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
else
|
86
|
+
it 'skips mathn test on Ruby 2.5+' do
|
87
|
+
skip "mathn was deprecated in Ruby 2.5+"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'integration test' do
|
93
|
+
it 'demonstrates the mathn compatibility fix' do
|
94
|
+
# The key point of this example is that parslet should not hang
|
95
|
+
# when mathn is loaded, even with failing parses
|
96
|
+
|
97
|
+
# Test that we can create the parser components
|
98
|
+
possible_whitespace = Parslet.match['\s'].repeat
|
99
|
+
expect(possible_whitespace).to be_a(Parslet::Atoms::Repetition)
|
100
|
+
|
101
|
+
cephalopod = Parslet.str('octopus') | Parslet.str('squid')
|
102
|
+
expect(cephalopod).to be_a(Parslet::Atoms::Alternative)
|
103
|
+
|
104
|
+
parenthesized_cephalopod = Parslet.str('(') >> possible_whitespace >> cephalopod >> possible_whitespace >> Parslet.str(')')
|
105
|
+
expect(parenthesized_cephalopod).to be_a(Parslet::Atoms::Sequence)
|
106
|
+
|
107
|
+
parser = possible_whitespace >> parenthesized_cephalopod >> possible_whitespace
|
108
|
+
expect(parser).to be_a(Parslet::Atoms::Sequence)
|
109
|
+
|
110
|
+
# Test that parsing fails appropriately (not hangs)
|
111
|
+
expect { parser.parse %{(\nsqeed)\n} }.to raise_error(Parslet::ParseFailed)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'reproduces the example behavior' do
|
115
|
+
# This test reproduces what the example file does:
|
116
|
+
# 1. Attempts to parse invalid input (should fail)
|
117
|
+
# 2. Shows that it terminates properly
|
118
|
+
|
119
|
+
output = capture_output do
|
120
|
+
# Simulate the attempt_parse function
|
121
|
+
begin
|
122
|
+
possible_whitespace = Parslet.match['\s'].repeat
|
123
|
+
cephalopod = Parslet.str('octopus') | Parslet.str('squid')
|
124
|
+
parenthesized_cephalopod = Parslet.str('(') >> possible_whitespace >> cephalopod >> possible_whitespace >> Parslet.str(')')
|
125
|
+
parser = possible_whitespace >> parenthesized_cephalopod >> possible_whitespace
|
126
|
+
|
127
|
+
parser.parse %{(\nsqeed)\n}
|
128
|
+
rescue Parslet::ParseFailed
|
129
|
+
# Expected - this is what should happen
|
130
|
+
end
|
131
|
+
|
132
|
+
puts 'it terminates before we require mathn'
|
133
|
+
puts 'okay!'
|
134
|
+
end
|
135
|
+
|
136
|
+
expect(output).to include('it terminates before we require mathn')
|
137
|
+
expect(output).to include('okay!')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def capture_output
|
144
|
+
original_stdout = $stdout
|
145
|
+
$stdout = StringIO.new
|
146
|
+
yield
|
147
|
+
$stdout.string
|
148
|
+
ensure
|
149
|
+
$stdout = original_stdout
|
150
|
+
end
|
151
|
+
end
|