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,482 @@
1
+ require 'spec_helper'
2
+ require_relative '../fixtures/examples/erb'
3
+
4
+ RSpec.describe 'ERB Parser Example' do
5
+ let(:parser) { ErbParser.new }
6
+ let(:transform) { ErbTransform.new }
7
+
8
+ describe ErbParser do
9
+ describe '#ruby' do
10
+ it 'parses ruby code until %>' do
11
+ result = parser.ruby.parse('x + 1')
12
+ expected = { ruby: 'x + 1' }
13
+ expect(result).to parse_as(expected)
14
+ end
15
+
16
+ it 'parses complex ruby expressions' do
17
+ result = parser.ruby.parse('User.find(1).name')
18
+ expected = { ruby: 'User.find(1).name' }
19
+ expect(result).to parse_as(expected)
20
+ end
21
+
22
+ it 'stops at %>' do
23
+ result = parser.ruby.parse('x + 1')
24
+ expected = { ruby: 'x + 1' }
25
+ expect(result).to parse_as(expected)
26
+ end
27
+
28
+ it 'handles empty ruby code' do
29
+ result = parser.ruby.parse('')
30
+ expected = { ruby: [] }
31
+ expect(result).to parse_as(expected)
32
+ end
33
+ end
34
+
35
+ describe '#expression' do
36
+ it 'parses expression with equals sign' do
37
+ result = parser.expression.parse('= x + 1')
38
+ expected = { expression: { ruby: ' x + 1' } }
39
+ expect(result).to parse_as(expected)
40
+ end
41
+
42
+ it 'parses complex expressions' do
43
+ result = parser.expression.parse('= User.find(1).name')
44
+ expected = { expression: { ruby: ' User.find(1).name' } }
45
+ expect(result).to parse_as(expected)
46
+ end
47
+
48
+ it 'parses string expressions' do
49
+ result = parser.expression.parse("= 'hello world'")
50
+ expected = { expression: { ruby: " 'hello world'" } }
51
+ expect(result).to parse_as(expected)
52
+ end
53
+ end
54
+
55
+ describe '#comment' do
56
+ it 'parses comment with hash sign' do
57
+ result = parser.comment.parse('# this is a comment')
58
+ expected = { comment: { ruby: ' this is a comment' } }
59
+ expect(result).to parse_as(expected)
60
+ end
61
+
62
+ it 'parses empty comment' do
63
+ result = parser.comment.parse('#')
64
+ expected = { comment: { ruby: [] } }
65
+ expect(result).to parse_as(expected)
66
+ end
67
+
68
+ it 'parses comment with ruby code' do
69
+ result = parser.comment.parse('# x = 1')
70
+ expected = { comment: { ruby: ' x = 1' } }
71
+ expect(result).to parse_as(expected)
72
+ end
73
+ end
74
+
75
+ describe '#code' do
76
+ it 'parses plain ruby code' do
77
+ result = parser.code.parse('x = 1')
78
+ expected = { code: { ruby: 'x = 1' } }
79
+ expect(result).to parse_as(expected)
80
+ end
81
+
82
+ it 'parses method calls' do
83
+ result = parser.code.parse('puts "hello"')
84
+ expected = { code: { ruby: 'puts "hello"' } }
85
+ expect(result).to parse_as(expected)
86
+ end
87
+
88
+ it 'parses variable assignments' do
89
+ result = parser.code.parse('a = 2')
90
+ expected = { code: { ruby: 'a = 2' } }
91
+ expect(result).to parse_as(expected)
92
+ end
93
+ end
94
+
95
+ describe '#erb' do
96
+ it 'parses expression erb' do
97
+ result = parser.erb.parse('= x + 1')
98
+ expected = { expression: { ruby: ' x + 1' } }
99
+ expect(result).to parse_as(expected)
100
+ end
101
+
102
+ it 'parses comment erb' do
103
+ result = parser.erb.parse('# comment')
104
+ expected = { comment: { ruby: ' comment' } }
105
+ expect(result).to parse_as(expected)
106
+ end
107
+
108
+ it 'parses code erb' do
109
+ result = parser.erb.parse('x = 1')
110
+ expected = { code: { ruby: 'x = 1' } }
111
+ expect(result).to parse_as(expected)
112
+ end
113
+ end
114
+
115
+ describe '#erb_with_tags' do
116
+ it 'parses erb expression with tags' do
117
+ result = parser.erb_with_tags.parse('<%= x + 1 %>')
118
+ expected = { expression: { ruby: ' x + 1 ' } }
119
+ expect(result).to parse_as(expected)
120
+ end
121
+
122
+ it 'parses erb comment with tags' do
123
+ result = parser.erb_with_tags.parse('<%# comment %>')
124
+ expected = { comment: { ruby: ' comment ' } }
125
+ expect(result).to parse_as(expected)
126
+ end
127
+
128
+ it 'parses erb code with tags' do
129
+ result = parser.erb_with_tags.parse('<% x = 1 %>')
130
+ expected = { code: { ruby: ' x = 1 ' } }
131
+ expect(result).to parse_as(expected)
132
+ end
133
+
134
+ it 'handles no spaces around erb content' do
135
+ result = parser.erb_with_tags.parse('<%=x%>')
136
+ expected = { expression: { ruby: 'x' } }
137
+ expect(result).to parse_as(expected)
138
+ end
139
+ end
140
+
141
+ describe '#text' do
142
+ it 'parses plain text' do
143
+ result = parser.text.parse('Hello world')
144
+ expect(result).to eq('Hello world')
145
+ end
146
+
147
+ it 'parses text with spaces' do
148
+ result = parser.text.parse('The value is ')
149
+ expect(result).to eq('The value is ')
150
+ end
151
+
152
+ it 'parses text with punctuation' do
153
+ result = parser.text.parse('Hello, world!')
154
+ expect(result).to eq('Hello, world!')
155
+ end
156
+
157
+ it 'stops at erb tags' do
158
+ result = parser.text.parse('Hello ')
159
+ expect(result).to eq('Hello ')
160
+ end
161
+
162
+ it 'fails on empty string' do
163
+ expect { parser.text.parse('') }.to raise_error(Parslet::ParseFailed)
164
+ end
165
+ end
166
+
167
+ describe '#text_with_ruby (root)' do
168
+ it 'parses simple text with erb expression' do
169
+ result = parser.parse('The value of x is <%= x %>.')
170
+ expected = {
171
+ text: [
172
+ { text: 'The value of x is ' },
173
+ { expression: { ruby: ' x ' } },
174
+ { text: '.' }
175
+ ]
176
+ }
177
+ expect(result).to parse_as(expected)
178
+ end
179
+
180
+ it 'parses erb code block' do
181
+ result = parser.parse('<% 1 + 2 %>')
182
+ expected = {
183
+ text: [
184
+ { code: { ruby: ' 1 + 2 ' } }
185
+ ]
186
+ }
187
+ expect(result).to parse_as(expected)
188
+ end
189
+
190
+ it 'parses erb comment' do
191
+ result = parser.parse('<%# commented %>')
192
+ expected = {
193
+ text: [
194
+ { comment: { ruby: ' commented ' } }
195
+ ]
196
+ }
197
+ expect(result).to parse_as(expected)
198
+ end
199
+
200
+ it 'parses mixed text and erb' do
201
+ result = parser.parse('Hello <% name = "World" %><%=name%>!')
202
+ expected = {
203
+ text: [
204
+ { text: 'Hello ' },
205
+ { code: { ruby: ' name = "World" ' } },
206
+ { expression: { ruby: 'name' } },
207
+ { text: '!' }
208
+ ]
209
+ }
210
+ expect(result).to parse_as(expected)
211
+ end
212
+
213
+ it 'parses complex erb template' do
214
+ input = 'The <% a = 2 %>value of a is <%= a %>.'
215
+ result = parser.parse(input)
216
+
217
+ expected = {
218
+ text: [
219
+ { text: 'The ' },
220
+ { code: { ruby: ' a = 2 ' } },
221
+ { text: 'value of a is ' },
222
+ { expression: { ruby: ' a ' } },
223
+ { text: '.' }
224
+ ]
225
+ }
226
+ expect(result).to parse_as(expected)
227
+ end
228
+
229
+ it 'parses multiline erb template' do
230
+ input = <<~ERB.chomp
231
+ Hello
232
+ <%= "World" %>
233
+ ERB
234
+
235
+ result = parser.parse(input)
236
+ expected = {
237
+ text: [
238
+ { text: "Hello\n" },
239
+ { expression: { ruby: ' "World" ' } }
240
+ ]
241
+ }
242
+ expect(result).to parse_as(expected)
243
+ end
244
+
245
+ it 'handles empty input' do
246
+ result = parser.parse('')
247
+ expected = { text: [] }
248
+ expect(result).to parse_as(expected)
249
+ end
250
+
251
+ it 'parses only text' do
252
+ result = parser.parse('Just plain text')
253
+ expected = {
254
+ text: [
255
+ { text: 'Just plain text' }
256
+ ]
257
+ }
258
+ expect(result).to parse_as(expected)
259
+ end
260
+
261
+ it 'parses only erb' do
262
+ result = parser.parse('<%= "only erb" %>')
263
+ expected = {
264
+ text: [
265
+ { expression: { ruby: ' "only erb" ' } }
266
+ ]
267
+ }
268
+ expect(result).to parse_as(expected)
269
+ end
270
+ end
271
+
272
+ describe 'error handling' do
273
+ it 'fails on unclosed erb tags' do
274
+ expect { parser.parse('Hello <%= world') }.to raise_error(Parslet::ParseFailed)
275
+ end
276
+
277
+ it 'fails on malformed erb tags' do
278
+ expect { parser.parse('Hello <% world >') }.to raise_error(Parslet::ParseFailed)
279
+ end
280
+
281
+ it 'parses nested erb tags as ruby code and text' do
282
+ result = parser.parse('<%= <% nested %> %>')
283
+ expected = {
284
+ text: [
285
+ { expression: { ruby: ' <% nested ' } },
286
+ { text: ' %>' }
287
+ ]
288
+ }
289
+ expect(result).to parse_as(expected)
290
+ end
291
+ end
292
+ end
293
+
294
+ describe ErbTransform do
295
+ let(:binding_context) do
296
+ # Create a binding with some variables for testing
297
+ x = 42
298
+ name = "World"
299
+ binding
300
+ end
301
+
302
+ let(:transform_with_context) { ErbTransform.new(binding_context) }
303
+
304
+ describe 'code transformation' do
305
+ it 'executes code and returns empty string' do
306
+ parsed = { code: { ruby: 'a = 2' } }
307
+ result = transform_with_context.apply(parsed)
308
+ expect(result).to eq('')
309
+ end
310
+
311
+ it 'executes method calls and returns empty string' do
312
+ parsed = { code: { ruby: 'puts "hello"' } }
313
+ # Capture stdout to avoid printing during tests
314
+ allow($stdout).to receive(:puts)
315
+ result = transform_with_context.apply(parsed)
316
+ expect(result).to eq('')
317
+ end
318
+ end
319
+
320
+ describe 'expression transformation' do
321
+ it 'evaluates expressions and returns result' do
322
+ parsed = { expression: { ruby: 'x' } }
323
+ result = transform_with_context.apply(parsed)
324
+ expect(result).to eq(42)
325
+ end
326
+
327
+ it 'evaluates string expressions' do
328
+ parsed = { expression: { ruby: '"Hello"' } }
329
+ result = transform_with_context.apply(parsed)
330
+ expect(result).to eq('Hello')
331
+ end
332
+
333
+ it 'evaluates arithmetic expressions' do
334
+ parsed = { expression: { ruby: '2 + 3' } }
335
+ result = transform_with_context.apply(parsed)
336
+ expect(result).to eq(5)
337
+ end
338
+
339
+ it 'evaluates variable references' do
340
+ parsed = { expression: { ruby: 'name' } }
341
+ result = transform_with_context.apply(parsed)
342
+ expect(result).to eq('World')
343
+ end
344
+ end
345
+
346
+ describe 'comment transformation' do
347
+ it 'returns empty string for comments' do
348
+ parsed = { comment: { ruby: ' this is a comment' } }
349
+ result = transform_with_context.apply(parsed)
350
+ expect(result).to eq('')
351
+ end
352
+
353
+ it 'does not evaluate comment content' do
354
+ parsed = { comment: { ruby: ' x = 999' } }
355
+ result = transform_with_context.apply(parsed)
356
+ expect(result).to eq('')
357
+ # x should still be 42, not 999
358
+ expect(binding_context.local_variable_get(:x)).to eq(42)
359
+ end
360
+ end
361
+
362
+ describe 'text transformation' do
363
+ it 'returns text as-is for simple text' do
364
+ parsed = { text: 'Hello World' }
365
+ result = transform_with_context.apply(parsed)
366
+ expect(result).to eq('Hello World')
367
+ end
368
+
369
+ it 'joins text sequences' do
370
+ parsed = { text: ['Hello', ' ', 'World'] }
371
+ result = transform_with_context.apply(parsed)
372
+ expect(result).to eq('Hello World')
373
+ end
374
+ end
375
+
376
+ describe 'integration transformation' do
377
+ it 'transforms complete erb template' do
378
+ input = 'The value of x is <%= x %>.'
379
+ parsed = parser.parse(input)
380
+ result = transform_with_context.apply(parsed)
381
+ expect(result).to eq('The value of x is 42.')
382
+ end
383
+
384
+ it 'handles code blocks that set variables' do
385
+ input = 'Start<% y = 10 %>The value is <%= y %>.'
386
+ parsed = parser.parse(input)
387
+ result = transform_with_context.apply(parsed)
388
+ expect(result).to eq('StartThe value is 10.')
389
+ end
390
+
391
+ it 'handles comments that are ignored' do
392
+ input = 'Hello<%# this is ignored %>World'
393
+ parsed = parser.parse(input)
394
+ result = transform_with_context.apply(parsed)
395
+ expect(result).to eq('HelloWorld')
396
+ end
397
+
398
+ it 'handles complex template with multiple erb blocks' do
399
+ input = 'Hello <% greeting = "Hi" %><%=greeting%> <%= name %>!'
400
+ parsed = parser.parse(input)
401
+ result = transform_with_context.apply(parsed)
402
+ expect(result).to eq('Hello Hi World!')
403
+ end
404
+
405
+ it 'handles multiline templates' do
406
+ input = <<~ERB.chomp
407
+ Name: <%= name %>
408
+ Value: <%= x %>
409
+ ERB
410
+
411
+ parsed = parser.parse(input)
412
+ result = transform_with_context.apply(parsed)
413
+ expected = "Name: World\nValue: 42"
414
+ expect(result).to eq(expected)
415
+ end
416
+ end
417
+
418
+ describe 'error handling in transformation' do
419
+ it 'raises error for undefined variables in expressions' do
420
+ parsed = { expression: { ruby: 'undefined_var' } }
421
+ expect { transform_with_context.apply(parsed) }.to raise_error(NameError)
422
+ end
423
+
424
+ it 'raises error for syntax errors in ruby code' do
425
+ parsed = { expression: { ruby: 'invalid syntax !' } }
426
+ expect { transform_with_context.apply(parsed) }.to raise_error(SyntaxError)
427
+ end
428
+ end
429
+ end
430
+
431
+ describe 'parser and transformer integration' do
432
+ let(:binding_context) do
433
+ x = 42
434
+ name = "World"
435
+ binding
436
+ end
437
+
438
+ let(:transform_with_context) { ErbTransform.new(binding_context) }
439
+
440
+ it 'processes simple erb template end-to-end' do
441
+ input = 'Hello <%= name %>!'
442
+ parsed = parser.parse(input)
443
+ result = transform_with_context.apply(parsed)
444
+ expect(result).to eq('Hello World!')
445
+ end
446
+
447
+ it 'processes erb with code blocks end-to-end' do
448
+ input = 'The <% a = 2 %>value of a is <%= a %>.'
449
+ parsed = parser.parse(input)
450
+ result = transform_with_context.apply(parsed)
451
+ expect(result).to eq('The value of a is 2.')
452
+ end
453
+
454
+ it 'processes erb with comments end-to-end' do
455
+ input = 'Hello<%# comment %>World'
456
+ parsed = parser.parse(input)
457
+ result = transform_with_context.apply(parsed)
458
+ expect(result).to eq('HelloWorld')
459
+ end
460
+
461
+ it 'processes complex erb template end-to-end' do
462
+ input = <<~ERB.chomp
463
+ The <% a = 2 %>not printed result of "a = 2".
464
+ The <%# a = 1 %>not printed non-evaluated comment "a = 1", see the value of a below.
465
+ The <%= 'nicely' %> printed result.
466
+ The <% b = 3 %>value of a is <%= a %>, and b is <%= b %>.
467
+ ERB
468
+
469
+ parsed = parser.parse(input)
470
+ result = transform_with_context.apply(parsed)
471
+
472
+ expected = <<~RESULT.chomp
473
+ The not printed result of "a = 2".
474
+ The not printed non-evaluated comment "a = 1", see the value of a below.
475
+ The nicely printed result.
476
+ The value of a is 2, and b is 3.
477
+ RESULT
478
+
479
+ expect(result).to eq(expected)
480
+ end
481
+ end
482
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+ require_relative '../fixtures/examples/ip_address'
3
+
4
+ RSpec.describe 'IP Address Parser Example' do
5
+ let(:parser) { IpAddressExample::Parser.new }
6
+
7
+ describe IpAddressExample::IPv4 do
8
+ describe '#dec_octet' do
9
+ it 'parses single digits' do
10
+ parser_with_ipv4 = Class.new { include IpAddressExample::IPv4 }.new
11
+ expect(parser_with_ipv4.dec_octet.parse('0')).to parse_as('0')
12
+ expect(parser_with_ipv4.dec_octet.parse('9')).to parse_as('9')
13
+ end
14
+
15
+ it 'parses two digit numbers' do
16
+ parser_with_ipv4 = Class.new { include IpAddressExample::IPv4 }.new
17
+ expect(parser_with_ipv4.dec_octet.parse('10')).to parse_as('10')
18
+ expect(parser_with_ipv4.dec_octet.parse('99')).to parse_as('99')
19
+ end
20
+
21
+ it 'parses three digit numbers' do
22
+ parser_with_ipv4 = Class.new { include IpAddressExample::IPv4 }.new
23
+ expect(parser_with_ipv4.dec_octet.parse('100')).to parse_as('100')
24
+ expect(parser_with_ipv4.dec_octet.parse('199')).to parse_as('199')
25
+ expect(parser_with_ipv4.dec_octet.parse('255')).to parse_as('255')
26
+ end
27
+
28
+ it 'rejects numbers over 255' do
29
+ parser_with_ipv4 = Class.new { include IpAddressExample::IPv4 }.new
30
+ expect { parser_with_ipv4.dec_octet.parse('256') }.to raise_error(Parslet::ParseFailed)
31
+ expect { parser_with_ipv4.dec_octet.parse('300') }.to raise_error(Parslet::ParseFailed)
32
+ end
33
+ end
34
+
35
+ describe '#ipv4' do
36
+ it 'parses valid IPv4 addresses' do
37
+ parser_with_ipv4 = Class.new { include IpAddressExample::IPv4 }.new
38
+ result = parser_with_ipv4.ipv4.parse('192.168.1.1')
39
+ expect(result).to parse_as({ ipv4: '192.168.1.1' })
40
+ end
41
+
42
+ it 'parses edge case IPv4 addresses' do
43
+ parser_with_ipv4 = Class.new { include IpAddressExample::IPv4 }.new
44
+
45
+ result = parser_with_ipv4.ipv4.parse('0.0.0.0')
46
+ expect(result).to parse_as({ ipv4: '0.0.0.0' })
47
+
48
+ result = parser_with_ipv4.ipv4.parse('255.255.255.255')
49
+ expect(result).to parse_as({ ipv4: '255.255.255.255' })
50
+ end
51
+
52
+ it 'rejects invalid IPv4 addresses' do
53
+ parser_with_ipv4 = Class.new { include IpAddressExample::IPv4 }.new
54
+ expect { parser_with_ipv4.ipv4.parse('256.1.1.1') }.to raise_error(Parslet::ParseFailed)
55
+ expect { parser_with_ipv4.ipv4.parse('1.1.1') }.to raise_error(Parslet::ParseFailed)
56
+ expect { parser_with_ipv4.ipv4.parse('1.1.1.1.1') }.to raise_error(Parslet::ParseFailed)
57
+ end
58
+ end
59
+ end
60
+
61
+ # Note: IPv6 module depends on IPv4's digit rule, so we test through the main Parser class
62
+
63
+ describe IpAddressExample::Parser do
64
+ describe '#parse' do
65
+ it 'parses valid IPv4 addresses' do
66
+ result = parser.parse('0.0.0.0')
67
+ expect(result).to parse_as({ ipv4: '0.0.0.0' })
68
+
69
+ result = parser.parse('255.255.255.255')
70
+ expect(result).to parse_as({ ipv4: '255.255.255.255' })
71
+ end
72
+
73
+ it 'parses valid IPv6 addresses' do
74
+ result = parser.parse('1:2:3:4:5:6:7:8')
75
+ expect(result).to parse_as({ ipv6: '1:2:3:4:5:6:7:8' })
76
+
77
+ result = parser.parse('12AD:34FC:A453:1922::')
78
+ expect(result).to parse_as({ ipv6: '12AD:34FC:A453:1922::' })
79
+
80
+ result = parser.parse('12AD::34FC')
81
+ expect(result).to parse_as({ ipv6: '12AD::34FC' })
82
+
83
+ result = parser.parse('12AD::')
84
+ expect(result).to parse_as({ ipv6: '12AD::' })
85
+
86
+ result = parser.parse('::')
87
+ expect(result).to parse_as({ ipv6: '::' })
88
+ end
89
+
90
+ it 'handles some IPv6 addresses but not all short ones' do
91
+ # This actually fails in the original example - the IPv6 grammar is strict
92
+ expect { parser.parse('1:2') }.to raise_error(Parslet::ParseFailed)
93
+ end
94
+
95
+ it 'rejects invalid addresses' do
96
+ expect { parser.parse('255.255.255') }.to raise_error(Parslet::ParseFailed)
97
+ expect { parser.parse('256.1.1.1') }.to raise_error(Parslet::ParseFailed)
98
+ expect { parser.parse('invalid') }.to raise_error(Parslet::ParseFailed)
99
+ end
100
+ end
101
+ end
102
+
103
+ describe 'integration test' do
104
+ it 'processes all the successful example addresses correctly' do
105
+ test_cases = [
106
+ { input: '0.0.0.0', expected: { ipv4: '0.0.0.0' } },
107
+ { input: '255.255.255.255', expected: { ipv4: '255.255.255.255' } },
108
+ { input: '1:2:3:4:5:6:7:8', expected: { ipv6: '1:2:3:4:5:6:7:8' } },
109
+ { input: '12AD:34FC:A453:1922::', expected: { ipv6: '12AD:34FC:A453:1922::' } },
110
+ { input: '12AD::34FC', expected: { ipv6: '12AD::34FC' } },
111
+ { input: '12AD::', expected: { ipv6: '12AD::' } },
112
+ { input: '::', expected: { ipv6: '::' } }
113
+ ]
114
+
115
+ test_cases.each do |test_case|
116
+ result = parser.parse(test_case[:input])
117
+ expect(result).to parse_as(test_case[:expected])
118
+ end
119
+ end
120
+
121
+ it 'handles failing cases correctly' do
122
+ failing_cases = ['255.255.255', '1:2']
123
+
124
+ failing_cases.each do |address|
125
+ expect { parser.parse(address) }.to raise_error(Parslet::ParseFailed)
126
+ end
127
+ end
128
+
129
+ it 'produces the expected output from the example file' do
130
+ # Test the successful cases from the example
131
+ successful_cases = {
132
+ '0.0.0.0' => { ipv4: '0.0.0.0' },
133
+ '255.255.255.255' => { ipv4: '255.255.255.255' },
134
+ '1:2:3:4:5:6:7:8' => { ipv6: '1:2:3:4:5:6:7:8' },
135
+ '12AD:34FC:A453:1922::' => { ipv6: '12AD:34FC:A453:1922::' },
136
+ '12AD::34FC' => { ipv6: '12AD::34FC' },
137
+ '12AD::' => { ipv6: '12AD::' },
138
+ '::' => { ipv6: '::' }
139
+ }
140
+
141
+ successful_cases.each do |input, expected|
142
+ result = parser.parse(input)
143
+ expect(result).to parse_as(expected)
144
+ end
145
+
146
+ # Test the failing cases from the example
147
+ failing_cases = ['255.255.255', '1:2']
148
+ failing_cases.each do |address|
149
+ expect { parser.parse(address) }.to raise_error(Parslet::ParseFailed)
150
+ end
151
+ end
152
+ end
153
+ end