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,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
|