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,299 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fixtures/examples/optimized_erb'
|
3
|
+
|
4
|
+
RSpec.describe 'Optimized ERB Example' do
|
5
|
+
include OptimizedErbExample
|
6
|
+
|
7
|
+
describe 'OptimizedErbExample::ErbParser' do
|
8
|
+
let(:parser) { OptimizedErbExample::ErbParser.new }
|
9
|
+
|
10
|
+
describe 'basic parsing components' do
|
11
|
+
describe '#ruby' do
|
12
|
+
it 'parses ruby code until %>' do
|
13
|
+
result = parser.ruby.parse('puts "hello"')
|
14
|
+
expect(result).to eq({ ruby: 'puts "hello"' })
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'stops at %>' do
|
18
|
+
result = parser.ruby.parse('puts "hello"')
|
19
|
+
expect(result[:ruby].to_s).to eq('puts "hello"')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'handles empty ruby code' do
|
23
|
+
result = parser.ruby.parse('')
|
24
|
+
expect(result).to eq({ ruby: [] })
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#expression' do
|
29
|
+
it 'parses ERB expression syntax' do
|
30
|
+
result = parser.expression.parse('= @user.name')
|
31
|
+
expect(result).to eq({ expression: { ruby: ' @user.name' } })
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'parses complex expressions' do
|
35
|
+
result = parser.expression.parse('= items.map(&:name).join(", ")')
|
36
|
+
expect(result[:expression][:ruby].to_s).to eq(' items.map(&:name).join(", ")')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#comment' do
|
41
|
+
it 'parses ERB comment syntax' do
|
42
|
+
result = parser.comment.parse('# This is a comment')
|
43
|
+
expect(result).to eq({ comment: { ruby: ' This is a comment' } })
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'parses empty comments' do
|
47
|
+
result = parser.comment.parse('#')
|
48
|
+
expect(result).to eq({ comment: { ruby: [] } })
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#code' do
|
53
|
+
it 'parses plain ruby code' do
|
54
|
+
result = parser.code.parse('if condition')
|
55
|
+
expect(result).to eq({ code: { ruby: 'if condition' } })
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'parses multi-statement code' do
|
59
|
+
result = parser.code.parse('x = 1; y = 2')
|
60
|
+
expect(result[:code][:ruby].to_s).to eq('x = 1; y = 2')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#erb' do
|
65
|
+
it 'parses expressions' do
|
66
|
+
result = parser.erb.parse('= @name')
|
67
|
+
expect(result).to eq({ expression: { ruby: ' @name' } })
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'parses comments' do
|
71
|
+
result = parser.erb.parse('# comment')
|
72
|
+
expect(result).to eq({ comment: { ruby: ' comment' } })
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'parses code' do
|
76
|
+
result = parser.erb.parse('puts "hello"')
|
77
|
+
expect(result).to eq({ code: { ruby: 'puts "hello"' } })
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#erb_with_tags' do
|
82
|
+
it 'parses ERB expression with tags' do
|
83
|
+
result = parser.erb_with_tags.parse('<%= @user.name %>')
|
84
|
+
expect(result).to eq({ expression: { ruby: ' @user.name ' } })
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'parses ERB comment with tags' do
|
88
|
+
result = parser.erb_with_tags.parse('<%# This is a comment %>')
|
89
|
+
expect(result).to eq({ comment: { ruby: ' This is a comment ' } })
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'parses ERB code with tags' do
|
93
|
+
result = parser.erb_with_tags.parse('<% if condition %>')
|
94
|
+
expect(result).to eq({ code: { ruby: ' if condition ' } })
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#text' do
|
99
|
+
it 'parses plain text' do
|
100
|
+
result = parser.text.parse('Hello World')
|
101
|
+
expect(result.to_s).to eq('Hello World')
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'parses text with special characters' do
|
105
|
+
result = parser.text.parse('Hello! @#$%^&*()')
|
106
|
+
expect(result.to_s).to eq('Hello! @#$%^&*()')
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'stops at ERB tags' do
|
110
|
+
result = parser.text.parse('Hello')
|
111
|
+
expect(result.to_s).to eq('Hello')
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'fails on empty string' do
|
115
|
+
expect { parser.text.parse('') }.to raise_error(Parslet::ParseFailed)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe 'integration parsing' do
|
121
|
+
describe '#text_with_ruby (root)' do
|
122
|
+
it 'parses plain text only' do
|
123
|
+
result = parser.parse('Hello World')
|
124
|
+
expect(result).to eq({ text: [{ text: 'Hello World' }] })
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'parses ERB expression only' do
|
128
|
+
result = parser.parse('<%= @name %>')
|
129
|
+
expect(result).to eq({ text: [{ expression: { ruby: ' @name ' } }] })
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'parses mixed text and ERB' do
|
133
|
+
result = parser.parse('Hello <%= @name %>!')
|
134
|
+
expect(result).to eq({
|
135
|
+
text: [
|
136
|
+
{ text: 'Hello ' },
|
137
|
+
{ expression: { ruby: ' @name ' } },
|
138
|
+
{ text: '!' }
|
139
|
+
]
|
140
|
+
})
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'parses multiple ERB tags' do
|
144
|
+
result = parser.parse('<%= @first %> and <%= @second %>')
|
145
|
+
expect(result).to eq({
|
146
|
+
text: [
|
147
|
+
{ expression: { ruby: ' @first ' } },
|
148
|
+
{ text: ' and ' },
|
149
|
+
{ expression: { ruby: ' @second ' } }
|
150
|
+
]
|
151
|
+
})
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'parses ERB comments' do
|
155
|
+
result = parser.parse('Before <%# comment %> After')
|
156
|
+
expect(result).to eq({
|
157
|
+
text: [
|
158
|
+
{ text: 'Before ' },
|
159
|
+
{ comment: { ruby: ' comment ' } },
|
160
|
+
{ text: ' After' }
|
161
|
+
]
|
162
|
+
})
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'parses ERB code blocks' do
|
166
|
+
result = parser.parse('Before <% code %> After')
|
167
|
+
expect(result).to eq({
|
168
|
+
text: [
|
169
|
+
{ text: 'Before ' },
|
170
|
+
{ code: { ruby: ' code ' } },
|
171
|
+
{ text: ' After' }
|
172
|
+
]
|
173
|
+
})
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe 'complex examples' do
|
179
|
+
it 'parses a simple ERB template' do
|
180
|
+
template = <<~ERB
|
181
|
+
<h1>Welcome <%= @user.name %>!</h1>
|
182
|
+
<p>You have <%= @messages.count %> messages.</p>
|
183
|
+
ERB
|
184
|
+
|
185
|
+
result = parser.parse(template)
|
186
|
+
expect(result[:text]).to be_an(Array)
|
187
|
+
expect(result[:text].length).to be > 3
|
188
|
+
|
189
|
+
# Check that we have the expected ERB expressions
|
190
|
+
erb_expressions = result[:text].select { |item| item.key?(:expression) }
|
191
|
+
expect(erb_expressions.length).to eq(2)
|
192
|
+
expect(erb_expressions[0][:expression][:ruby].to_s).to include('@user.name')
|
193
|
+
expect(erb_expressions[1][:expression][:ruby].to_s).to include('@messages.count')
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'parses ERB with different tag types' do
|
197
|
+
template = <<~ERB
|
198
|
+
<%# This is a comment %>
|
199
|
+
<% if @user %>
|
200
|
+
Hello <%= @user.name %>!
|
201
|
+
<% end %>
|
202
|
+
ERB
|
203
|
+
|
204
|
+
result = parser.parse(template)
|
205
|
+
expect(result[:text]).to be_an(Array)
|
206
|
+
|
207
|
+
# Should contain comment, code, expression, and text elements
|
208
|
+
has_comment = result[:text].any? { |item| item.key?(:comment) }
|
209
|
+
has_code = result[:text].any? { |item| item.key?(:code) }
|
210
|
+
has_expression = result[:text].any? { |item| item.key?(:expression) }
|
211
|
+
has_text = result[:text].any? { |item| item.key?(:text) }
|
212
|
+
|
213
|
+
expect(has_comment).to be true
|
214
|
+
expect(has_code).to be true
|
215
|
+
expect(has_expression).to be true
|
216
|
+
expect(has_text).to be true
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe 'big.erb file parsing' do
|
221
|
+
it 'successfully parses the big.erb file' do
|
222
|
+
result = OptimizedErbExample.parse_big_erb_file
|
223
|
+
expect(result).to be_a(Hash)
|
224
|
+
expect(result[:text]).to be_an(Array)
|
225
|
+
expect(result[:text].length).to be > 1
|
226
|
+
|
227
|
+
# Should contain the ERB expression from the file
|
228
|
+
erb_expressions = result[:text].select { |item| item.key?(:expression) }
|
229
|
+
expect(erb_expressions.length).to eq(1)
|
230
|
+
expect(erb_expressions[0][:expression][:ruby].to_s).to include('erb tag')
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'handles large text content efficiently' do
|
234
|
+
# This test demonstrates the optimization aspect
|
235
|
+
expect { OptimizedErbExample.parse_big_erb_file }.not_to raise_error
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe 'error handling' do
|
240
|
+
it 'handles malformed ERB tags' do
|
241
|
+
expect { parser.parse('<%') }.to raise_error(Parslet::ParseFailed)
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'handles unclosed ERB tags' do
|
245
|
+
expect { parser.parse('<% code') }.to raise_error(Parslet::ParseFailed)
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'provides meaningful error messages' do
|
249
|
+
begin
|
250
|
+
parser.parse('<%')
|
251
|
+
fail 'Expected ParseFailed to be raised'
|
252
|
+
rescue Parslet::ParseFailed => e
|
253
|
+
expect(e.message).to include('Extra input after last repetition')
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
describe 'edge cases' do
|
259
|
+
it 'handles empty input' do
|
260
|
+
result = parser.parse('')
|
261
|
+
expect(result).to eq({ text: [] })
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'handles whitespace only' do
|
265
|
+
result = parser.parse(' ')
|
266
|
+
expect(result).to eq({ text: [{ text: ' ' }] })
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'handles nested angle brackets in text' do
|
270
|
+
result = parser.parse('This < that > other')
|
271
|
+
expect(result).to eq({ text: [{ text: 'This < that > other' }] })
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'handles percent signs in text' do
|
275
|
+
result = parser.parse('100% complete')
|
276
|
+
expect(result).to eq({ text: [{ text: '100% complete' }] })
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
describe 'module methods' do
|
282
|
+
describe '.parse_erb_content' do
|
283
|
+
it 'parses ERB content using the parser' do
|
284
|
+
content = 'Hello <%= @world %>!'
|
285
|
+
result = OptimizedErbExample.parse_erb_content(content)
|
286
|
+
expect(result[:text]).to be_an(Array)
|
287
|
+
expect(result[:text].length).to eq(3)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe '.parse_big_erb_file' do
|
292
|
+
it 'reads and parses the big.erb file' do
|
293
|
+
result = OptimizedErbExample.parse_big_erb_file
|
294
|
+
expect(result).to be_a(Hash)
|
295
|
+
expect(result[:text]).to be_an(Array)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../fixtures/examples/parens'
|
3
|
+
|
4
|
+
RSpec.describe 'Parens Example' do
|
5
|
+
let(:parser) { LISP::Parser.new }
|
6
|
+
let(:transform) { LISP::Transform.new }
|
7
|
+
|
8
|
+
describe LISP::Parser do
|
9
|
+
describe '#balanced (root)' do
|
10
|
+
it 'parses empty parentheses' do
|
11
|
+
result = parser.parse('()')
|
12
|
+
expected = {
|
13
|
+
l: '(',
|
14
|
+
m: nil,
|
15
|
+
r: ')'
|
16
|
+
}
|
17
|
+
expect(result).to parse_as(expected)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'parses nested parentheses' do
|
21
|
+
result = parser.parse('(())')
|
22
|
+
expected = {
|
23
|
+
l: '(',
|
24
|
+
m: {
|
25
|
+
l: '(',
|
26
|
+
m: nil,
|
27
|
+
r: ')'
|
28
|
+
},
|
29
|
+
r: ')'
|
30
|
+
}
|
31
|
+
expect(result).to parse_as(expected)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'parses deeply nested parentheses' do
|
35
|
+
result = parser.parse('((((()))))')
|
36
|
+
expected = {
|
37
|
+
l: '(',
|
38
|
+
m: {
|
39
|
+
l: '(',
|
40
|
+
m: {
|
41
|
+
l: '(',
|
42
|
+
m: {
|
43
|
+
l: '(',
|
44
|
+
m: {
|
45
|
+
l: '(',
|
46
|
+
m: nil,
|
47
|
+
r: ')'
|
48
|
+
},
|
49
|
+
r: ')'
|
50
|
+
},
|
51
|
+
r: ')'
|
52
|
+
},
|
53
|
+
r: ')'
|
54
|
+
},
|
55
|
+
r: ')'
|
56
|
+
}
|
57
|
+
expect(result).to parse_as(expected)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'fails on unbalanced parentheses' do
|
61
|
+
expect { parser.parse('((())') }.to raise_error(Parslet::ParseFailed)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'fails on mismatched parentheses' do
|
65
|
+
expect { parser.parse('()(') }.to raise_error(Parslet::ParseFailed)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'fails on extra closing parentheses' do
|
69
|
+
expect { parser.parse('())') }.to raise_error(Parslet::ParseFailed)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'fails on just opening parenthesis' do
|
73
|
+
expect { parser.parse('(') }.to raise_error(Parslet::ParseFailed)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'fails on just closing parenthesis' do
|
77
|
+
expect { parser.parse(')') }.to raise_error(Parslet::ParseFailed)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'fails on empty string' do
|
81
|
+
expect { parser.parse('') }.to raise_error(Parslet::ParseFailed)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'parses triple nested parentheses' do
|
85
|
+
result = parser.parse('((()))')
|
86
|
+
expected = {
|
87
|
+
l: '(',
|
88
|
+
m: {
|
89
|
+
l: '(',
|
90
|
+
m: {
|
91
|
+
l: '(',
|
92
|
+
m: nil,
|
93
|
+
r: ')'
|
94
|
+
},
|
95
|
+
r: ')'
|
96
|
+
},
|
97
|
+
r: ')'
|
98
|
+
}
|
99
|
+
expect(result).to parse_as(expected)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe LISP::Transform do
|
105
|
+
describe 'counting parentheses levels' do
|
106
|
+
it 'counts single level as 1' do
|
107
|
+
tree = { l: '(', m: nil, r: ')' }
|
108
|
+
result = transform.apply(tree)
|
109
|
+
expect(result).to eq(1)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'counts two levels as 2' do
|
113
|
+
tree = {
|
114
|
+
l: '(',
|
115
|
+
m: { l: '(', m: nil, r: ')' },
|
116
|
+
r: ')'
|
117
|
+
}
|
118
|
+
result = transform.apply(tree)
|
119
|
+
expect(result).to eq(2)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'counts three levels as 3' do
|
123
|
+
tree = {
|
124
|
+
l: '(',
|
125
|
+
m: {
|
126
|
+
l: '(',
|
127
|
+
m: { l: '(', m: nil, r: ')' },
|
128
|
+
r: ')'
|
129
|
+
},
|
130
|
+
r: ')'
|
131
|
+
}
|
132
|
+
result = transform.apply(tree)
|
133
|
+
expect(result).to eq(3)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'counts five levels as 5' do
|
137
|
+
tree = {
|
138
|
+
l: '(',
|
139
|
+
m: {
|
140
|
+
l: '(',
|
141
|
+
m: {
|
142
|
+
l: '(',
|
143
|
+
m: {
|
144
|
+
l: '(',
|
145
|
+
m: { l: '(', m: nil, r: ')' },
|
146
|
+
r: ')'
|
147
|
+
},
|
148
|
+
r: ')'
|
149
|
+
},
|
150
|
+
r: ')'
|
151
|
+
},
|
152
|
+
r: ')'
|
153
|
+
}
|
154
|
+
result = transform.apply(tree)
|
155
|
+
expect(result).to eq(5)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe 'integration tests' do
|
161
|
+
it 'processes () correctly' do
|
162
|
+
tree = parser.parse('()')
|
163
|
+
count = transform.apply(tree)
|
164
|
+
expect(count).to eq(1)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'processes (()) correctly' do
|
168
|
+
tree = parser.parse('(())')
|
169
|
+
count = transform.apply(tree)
|
170
|
+
expect(count).to eq(2)
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'processes (((((()))))) correctly' do
|
174
|
+
tree = parser.parse('((((()))))')
|
175
|
+
count = transform.apply(tree)
|
176
|
+
expect(count).to eq(5)
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'fails on unbalanced ((()) correctly' do
|
180
|
+
expect { parser.parse('((())') }.to raise_error(Parslet::ParseFailed, /Failed to match/)
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'produces the expected outputs from the example file' do
|
184
|
+
# Test case 1: ()
|
185
|
+
tree1 = parser.parse('()')
|
186
|
+
expected1 = { l: '(', m: nil, r: ')' }
|
187
|
+
expect(tree1).to parse_as(expected1)
|
188
|
+
expect(transform.apply(tree1)).to eq(1)
|
189
|
+
|
190
|
+
# Test case 2: (())
|
191
|
+
tree2 = parser.parse('(())')
|
192
|
+
expected2 = {
|
193
|
+
l: '(',
|
194
|
+
m: { l: '(', m: nil, r: ')' },
|
195
|
+
r: ')'
|
196
|
+
}
|
197
|
+
expect(tree2).to parse_as(expected2)
|
198
|
+
expect(transform.apply(tree2)).to eq(2)
|
199
|
+
|
200
|
+
# Test case 3: (((((())))))
|
201
|
+
tree3 = parser.parse('((((()))))')
|
202
|
+
expected3 = {
|
203
|
+
l: '(',
|
204
|
+
m: {
|
205
|
+
l: '(',
|
206
|
+
m: {
|
207
|
+
l: '(',
|
208
|
+
m: {
|
209
|
+
l: '(',
|
210
|
+
m: { l: '(', m: nil, r: ')' },
|
211
|
+
r: ')'
|
212
|
+
},
|
213
|
+
r: ')'
|
214
|
+
},
|
215
|
+
r: ')'
|
216
|
+
},
|
217
|
+
r: ')'
|
218
|
+
}
|
219
|
+
expect(tree3).to parse_as(expected3)
|
220
|
+
expect(transform.apply(tree3)).to eq(5)
|
221
|
+
|
222
|
+
# Test case 4: ((()) - should fail
|
223
|
+
expect { parser.parse('((())') }.to raise_error(Parslet::ParseFailed)
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'demonstrates the power of tree pattern matching' do
|
227
|
+
# Simple case
|
228
|
+
simple_tree = parser.parse('()')
|
229
|
+
expect(transform.apply(simple_tree)).to eq(1)
|
230
|
+
|
231
|
+
# Complex nested case
|
232
|
+
complex_tree = parser.parse('(((())))')
|
233
|
+
expect(transform.apply(complex_tree)).to eq(4)
|
234
|
+
|
235
|
+
# The transform correctly counts nesting levels
|
236
|
+
# by recursively applying the rule that adds 1 for each level
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|