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,278 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../fixtures/examples/calc'
|
3
|
+
|
4
|
+
RSpec.describe 'Calculator Parser Example' do
|
5
|
+
let(:parser) { CalcExample::CalcParser.new }
|
6
|
+
let(:transformer) { CalcExample::CalcTransform.new }
|
7
|
+
|
8
|
+
describe CalcExample::CalcParser do
|
9
|
+
describe '#integer' do
|
10
|
+
it 'parses single digits' do
|
11
|
+
result = parser.integer.parse('1')
|
12
|
+
expect(result).to parse_as({ i: '1' })
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'parses multi-digit numbers' do
|
16
|
+
result = parser.integer.parse('123')
|
17
|
+
expect(result).to parse_as({ i: '123' })
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'consumes trailing whitespace' do
|
21
|
+
result = parser.integer.parse('123 ')
|
22
|
+
expect(result).to parse_as({ i: '123' })
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'fails to parse floats' do
|
26
|
+
expect { parser.integer.parse('1.3') }.to raise_error(Parslet::ParseFailed)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'fails to parse letters' do
|
30
|
+
expect { parser.integer.parse('abc') }.to raise_error(Parslet::ParseFailed)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#mult_op' do
|
35
|
+
it 'parses multiplication operator' do
|
36
|
+
result = parser.mult_op.parse('*')
|
37
|
+
expect(result).to parse_as({ o: '*' })
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'parses division operator' do
|
41
|
+
result = parser.mult_op.parse('/')
|
42
|
+
expect(result).to parse_as({ o: '/' })
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'consumes trailing whitespace' do
|
46
|
+
result = parser.mult_op.parse('* ')
|
47
|
+
expect(result).to parse_as({ o: '*' })
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#add_op' do
|
52
|
+
it 'parses addition operator' do
|
53
|
+
result = parser.add_op.parse('+')
|
54
|
+
expect(result).to parse_as({ o: '+' })
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'parses subtraction operator' do
|
58
|
+
result = parser.add_op.parse('-')
|
59
|
+
expect(result).to parse_as({ o: '-' })
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#multiplication' do
|
64
|
+
it 'parses simple multiplication' do
|
65
|
+
result = parser.multiplication.parse('1*2')
|
66
|
+
expected = [
|
67
|
+
{ l: { i: '1' } },
|
68
|
+
{ o: '*', r: { i: '2' } }
|
69
|
+
]
|
70
|
+
expect(result).to parse_as(expected)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'parses simple division' do
|
74
|
+
result = parser.multiplication.parse('1/2')
|
75
|
+
expected = [
|
76
|
+
{ l: { i: '1' } },
|
77
|
+
{ o: '/', r: { i: '2' } }
|
78
|
+
]
|
79
|
+
expect(result).to parse_as(expected)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'parses single integer as multiplication' do
|
83
|
+
result = parser.multiplication.parse('42')
|
84
|
+
expected = { i: '42' }
|
85
|
+
expect(result).to parse_as(expected)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'parses chained multiplication' do
|
89
|
+
result = parser.multiplication.parse('2*3*4')
|
90
|
+
expected = [
|
91
|
+
{ l: { i: '2' } },
|
92
|
+
{ o: '*', r: { i: '3' } },
|
93
|
+
{ o: '*', r: { i: '4' } }
|
94
|
+
]
|
95
|
+
expect(result).to parse_as(expected)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#addition' do
|
100
|
+
it 'parses simple addition' do
|
101
|
+
result = parser.addition.parse('1+2')
|
102
|
+
expected = [
|
103
|
+
{ l: { i: '1' } },
|
104
|
+
{ o: '+', r: { i: '2' } }
|
105
|
+
]
|
106
|
+
expect(result).to parse_as(expected)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'parses chained addition and subtraction' do
|
110
|
+
result = parser.addition.parse('1+2+3-4')
|
111
|
+
expected = [
|
112
|
+
{ l: { i: '1' } },
|
113
|
+
{ o: '+', r: { i: '2' } },
|
114
|
+
{ o: '+', r: { i: '3' } },
|
115
|
+
{ o: '-', r: { i: '4' } }
|
116
|
+
]
|
117
|
+
expect(result).to parse_as(expected)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'parses single integer as addition' do
|
121
|
+
result = parser.addition.parse('42')
|
122
|
+
expected = { i: '42' }
|
123
|
+
expect(result).to parse_as(expected)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe 'root parser (addition)' do
|
128
|
+
it 'parses complex expressions with precedence' do
|
129
|
+
result = parser.parse('1+2*3')
|
130
|
+
expected = [
|
131
|
+
{ l: { i: '1' } },
|
132
|
+
{
|
133
|
+
o: '+',
|
134
|
+
r: [
|
135
|
+
{ l: { i: '2' } },
|
136
|
+
{ o: '*', r: { i: '3' } }
|
137
|
+
]
|
138
|
+
}
|
139
|
+
]
|
140
|
+
expect(result).to parse_as(expected)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe CalcExample::CalcTransform do
|
146
|
+
it 'transforms integer slices to Int objects' do
|
147
|
+
input = { i: Parslet::Slice.new(Parslet::Position.new("123", 0), "123") }
|
148
|
+
result = transformer.apply(input)
|
149
|
+
expect(result).to be_a(CalcExample::Int)
|
150
|
+
expect(result.int).to eq(123)
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'transforms operation structures to LeftOp objects' do
|
154
|
+
input = { o: Parslet::Slice.new(Parslet::Position.new("+", 0), "+"), r: CalcExample::Int.new(5) }
|
155
|
+
result = transformer.apply(input)
|
156
|
+
expect(result).to be_a(CalcExample::LeftOp)
|
157
|
+
expect(result.operation.to_s).to eq('+')
|
158
|
+
expect(result.right).to be_a(CalcExample::Int)
|
159
|
+
expect(result.right.int).to eq(5)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'unwraps left operand' do
|
163
|
+
input = { l: CalcExample::Int.new(42) }
|
164
|
+
result = transformer.apply(input)
|
165
|
+
expect(result).to be_a(CalcExample::Int)
|
166
|
+
expect(result.int).to eq(42)
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'transforms sequences to Seq objects' do
|
170
|
+
input = [CalcExample::LeftOp.new('+', CalcExample::Int.new(2)), CalcExample::LeftOp.new('*', CalcExample::Int.new(3))]
|
171
|
+
result = transformer.apply(input)
|
172
|
+
expect(result).to be_a(CalcExample::Seq)
|
173
|
+
expect(result.sequence).to be_an(Array)
|
174
|
+
expect(result.sequence.length).to eq(2)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe 'AST evaluation' do
|
179
|
+
describe CalcExample::Int do
|
180
|
+
it 'evaluates to itself' do
|
181
|
+
int = CalcExample::Int.new(42)
|
182
|
+
expect(int.eval).to eq(int)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'performs addition operation' do
|
186
|
+
left = CalcExample::Int.new(5)
|
187
|
+
right = CalcExample::Int.new(3)
|
188
|
+
result = left.op('+', right)
|
189
|
+
expect(result).to be_a(CalcExample::Int)
|
190
|
+
expect(result.int).to eq(8)
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'performs subtraction operation' do
|
194
|
+
left = CalcExample::Int.new(5)
|
195
|
+
right = CalcExample::Int.new(3)
|
196
|
+
result = left.op('-', right)
|
197
|
+
expect(result).to be_a(CalcExample::Int)
|
198
|
+
expect(result.int).to eq(2)
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'performs multiplication operation' do
|
202
|
+
left = CalcExample::Int.new(5)
|
203
|
+
right = CalcExample::Int.new(3)
|
204
|
+
result = left.op('*', right)
|
205
|
+
expect(result).to be_a(CalcExample::Int)
|
206
|
+
expect(result.int).to eq(15)
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'performs division operation' do
|
210
|
+
left = CalcExample::Int.new(6)
|
211
|
+
right = CalcExample::Int.new(3)
|
212
|
+
result = left.op('/', right)
|
213
|
+
expect(result).to be_a(CalcExample::Int)
|
214
|
+
expect(result.int).to eq(2)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe CalcExample::LeftOp do
|
219
|
+
it 'applies operation to left operand' do
|
220
|
+
left = CalcExample::Int.new(5)
|
221
|
+
op = CalcExample::LeftOp.new('+', CalcExample::Int.new(3))
|
222
|
+
result = op.call(left)
|
223
|
+
expect(result).to be_a(CalcExample::Int)
|
224
|
+
expect(result.int).to eq(8)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe CalcExample::Seq do
|
229
|
+
it 'reduces sequence of operations' do
|
230
|
+
seq = CalcExample::Seq.new([
|
231
|
+
CalcExample::LeftOp.new('+', CalcExample::Int.new(2)),
|
232
|
+
CalcExample::LeftOp.new('*', CalcExample::Int.new(3))
|
233
|
+
])
|
234
|
+
# Starting with some initial value, this would be: (init + 2) * 3
|
235
|
+
# But we need to provide an initial value to test this properly
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe 'integration tests' do
|
241
|
+
describe 'calculate function' do
|
242
|
+
it 'calculates simple addition' do
|
243
|
+
expect(CalcExample.calculate('1+1')).to eq(2)
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'calculates left-associative subtraction' do
|
247
|
+
expect(CalcExample.calculate('1-1-1')).to eq(-1)
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'calculates with proper precedence' do
|
251
|
+
expect(CalcExample.calculate('1+1+3*5/2')).to eq(9)
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'calculates simple multiplication' do
|
255
|
+
expect(CalcExample.calculate('123*2')).to eq(246)
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'calculates complex expressions' do
|
259
|
+
expect(CalcExample.calculate('2+3*4')).to eq(14)
|
260
|
+
expect(CalcExample.calculate('10-2*3')).to eq(4)
|
261
|
+
expect(CalcExample.calculate('20/4+1')).to eq(6)
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'handles single numbers' do
|
265
|
+
expect(CalcExample.calculate('42')).to eq(42)
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'handles whitespace' do
|
269
|
+
expect(CalcExample.calculate('1 + 2 * 3')).to eq(7)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'produces the expected output for the example' do
|
274
|
+
# This tests the actual example execution
|
275
|
+
expect(CalcExample.calculate('123*2')).to eq(246)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../fixtures/examples/capture'
|
3
|
+
|
4
|
+
RSpec.describe 'Capture Parser Example' do
|
5
|
+
let(:parser) { CaptureExample::CapturingParser.new }
|
6
|
+
|
7
|
+
describe CaptureExample::CapturingParser do
|
8
|
+
describe '#marker' do
|
9
|
+
it 'parses uppercase letter sequences' do
|
10
|
+
result = parser.marker.parse('CAPTURE')
|
11
|
+
expect(result).to parse_as('CAPTURE')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'parses single uppercase letters' do
|
15
|
+
result = parser.marker.parse('A')
|
16
|
+
expect(result).to parse_as('A')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'fails on lowercase letters' do
|
20
|
+
expect { parser.marker.parse('capture') }.to raise_error(Parslet::ParseFailed)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'fails on empty input' do
|
24
|
+
expect { parser.marker.parse('') }.to raise_error(Parslet::ParseFailed)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Note: doc_start and text_line cannot be tested in isolation as they depend on scope context
|
29
|
+
# from the document parser. Testing them through integration tests instead.
|
30
|
+
|
31
|
+
describe 'root parser (document)' do
|
32
|
+
it 'parses simple documents' do
|
33
|
+
input = "<CAPTURE\nText1\nCAPTURE"
|
34
|
+
result = parser.parse(input)
|
35
|
+
|
36
|
+
expected = [
|
37
|
+
{ line: "Text1\n" }
|
38
|
+
]
|
39
|
+
expect(result).to parse_as(expected)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'parses documents with multiple lines' do
|
43
|
+
input = "<CAPTURE\nText1\nText2\nCAPTURE"
|
44
|
+
result = parser.parse(input)
|
45
|
+
|
46
|
+
expected = [
|
47
|
+
{ line: "Text1\n" },
|
48
|
+
{ line: "Text2\n" }
|
49
|
+
]
|
50
|
+
expect(result).to parse_as(expected)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'parses nested documents' do
|
54
|
+
input = "<CAPTURE\nText1\n<FOOBAR\nText3\nText4\nFOOBAR\nText2\nCAPTURE"
|
55
|
+
result = parser.parse(input)
|
56
|
+
|
57
|
+
expected = [
|
58
|
+
{ line: "Text1\n" },
|
59
|
+
{ doc: [
|
60
|
+
{ line: "Text3\n" },
|
61
|
+
{ line: "Text4\n" }
|
62
|
+
]},
|
63
|
+
{ line: "\nText2\n" } # Note: includes the newline before Text2
|
64
|
+
]
|
65
|
+
expect(result).to parse_as(expected)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'handles different marker names' do
|
69
|
+
input = "<FOOBAR\nSome text\nFOOBAR"
|
70
|
+
result = parser.parse(input)
|
71
|
+
|
72
|
+
expected = [
|
73
|
+
{ line: "Some text\n" }
|
74
|
+
]
|
75
|
+
expect(result).to parse_as(expected)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'fails when end marker does not match start marker' do
|
79
|
+
input = "<CAPTURE\nText1\nWRONG"
|
80
|
+
expect { parser.parse(input) }.to raise_error(Parslet::ParseFailed)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'integration test' do
|
86
|
+
it 'processes the main example correctly' do
|
87
|
+
input = "<CAPTURE\nText1\n<FOOBAR\nText3\nText4\nFOOBAR\nText2\nCAPTURE"
|
88
|
+
result = parser.parse(input)
|
89
|
+
|
90
|
+
# Verify the structure matches the expected nested document format
|
91
|
+
expect(result).to be_an(Array)
|
92
|
+
expect(result.length).to eq(3)
|
93
|
+
|
94
|
+
# First line
|
95
|
+
expect(result[0]).to have_key(:line)
|
96
|
+
expect(result[0][:line]).to parse_as("Text1\n")
|
97
|
+
|
98
|
+
# Nested document
|
99
|
+
expect(result[1]).to have_key(:doc)
|
100
|
+
expect(result[1][:doc]).to be_an(Array)
|
101
|
+
expect(result[1][:doc].length).to eq(2)
|
102
|
+
expect(result[1][:doc][0][:line]).to parse_as("Text3\n")
|
103
|
+
expect(result[1][:doc][1][:line]).to parse_as("Text4\n")
|
104
|
+
|
105
|
+
# Last line (includes newline before Text2)
|
106
|
+
expect(result[2]).to have_key(:line)
|
107
|
+
expect(result[2][:line]).to parse_as("\nText2\n")
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'handles simple single-level documents' do
|
111
|
+
input = "<TEST\nHello\nWorld\nTEST"
|
112
|
+
result = parser.parse(input)
|
113
|
+
|
114
|
+
expected = [
|
115
|
+
{ line: "Hello\n" },
|
116
|
+
{ line: "World\n" }
|
117
|
+
]
|
118
|
+
expect(result).to parse_as(expected)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'produces the expected output from the example file' do
|
122
|
+
# This matches the exact output shown when running the example
|
123
|
+
input = "<CAPTURE\nText1\n<FOOBAR\nText3\nText4\nFOOBAR\nText2\nCAPTURE"
|
124
|
+
result = parser.parse(input)
|
125
|
+
|
126
|
+
expected = [
|
127
|
+
{ line: "Text1\n" },
|
128
|
+
{ doc: [
|
129
|
+
{ line: "Text3\n" },
|
130
|
+
{ line: "Text4\n" }
|
131
|
+
]},
|
132
|
+
{ line: "\nText2\n" }
|
133
|
+
]
|
134
|
+
expect(result).to parse_as(expected)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../fixtures/examples/comments'
|
3
|
+
|
4
|
+
RSpec.describe 'Comments Parser Example' do
|
5
|
+
let(:parser) { CommentsExample::ALanguage.new }
|
6
|
+
|
7
|
+
describe CommentsExample::ALanguage do
|
8
|
+
describe '#line_comment' do
|
9
|
+
it 'parses line comments' do
|
10
|
+
result = parser.line_comment.parse('// this is a comment')
|
11
|
+
expect(result).to parse_as({ line: '// this is a comment' })
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'parses empty line comments' do
|
15
|
+
result = parser.line_comment.parse('//')
|
16
|
+
expect(result).to parse_as({ line: '//' })
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'stops at newlines' do
|
20
|
+
result = parser.line_comment.parse('// comment')
|
21
|
+
expect(result).to parse_as({ line: '// comment' })
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#multiline_comment' do
|
26
|
+
it 'parses simple multiline comments' do
|
27
|
+
result = parser.multiline_comment.parse('/* comment */')
|
28
|
+
expect(result).to parse_as({ multi: '/* comment */' })
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'parses multiline comments with newlines' do
|
32
|
+
result = parser.multiline_comment.parse("/* line1\nline2 */")
|
33
|
+
expect(result).to parse_as({ multi: "/* line1\nline2 */" })
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'parses empty multiline comments' do
|
37
|
+
result = parser.multiline_comment.parse('/**/')
|
38
|
+
expect(result).to parse_as({ multi: '/**/' })
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#expression' do
|
43
|
+
it 'parses single a' do
|
44
|
+
result = parser.expression.parse('a')
|
45
|
+
expect(result).to parse_as({ exp: { a: 'a' } })
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'parses a with trailing spaces' do
|
49
|
+
result = parser.expression.parse('a ')
|
50
|
+
expect(result).to parse_as({ exp: { a: 'a' } })
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'root parser (lines)' do
|
55
|
+
it 'parses simple code' do
|
56
|
+
input = "a\na\n"
|
57
|
+
result = parser.parse(input)
|
58
|
+
|
59
|
+
expected = [
|
60
|
+
{ exp: { a: 'a' } },
|
61
|
+
{ exp: { a: 'a' } }
|
62
|
+
]
|
63
|
+
expect(result).to parse_as(expected)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'parses code with line comments' do
|
67
|
+
input = "a\n// line comment\na\n"
|
68
|
+
result = parser.parse(input)
|
69
|
+
|
70
|
+
expected = [
|
71
|
+
{ exp: { a: 'a' } },
|
72
|
+
{ line: '// line comment' },
|
73
|
+
{ exp: { a: 'a' } }
|
74
|
+
]
|
75
|
+
expect(result).to parse_as(expected)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'parses code with multiline comments' do
|
79
|
+
input = "a\n/* multiline\ncomment */\na\n"
|
80
|
+
result = parser.parse(input)
|
81
|
+
|
82
|
+
expected = [
|
83
|
+
{ exp: { a: 'a' } },
|
84
|
+
{ multi: "/* multiline\ncomment */" },
|
85
|
+
{ exp: { a: 'a' } }
|
86
|
+
]
|
87
|
+
expect(result).to parse_as(expected)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'handles mixed comments and expressions on same line' do
|
91
|
+
input = "a a a // line comment\na /* inline comment */ a\n"
|
92
|
+
result = parser.parse(input)
|
93
|
+
|
94
|
+
expected = [
|
95
|
+
{ exp: { a: 'a' } },
|
96
|
+
{ exp: { a: 'a' } },
|
97
|
+
{ exp: [{ a: 'a' }, { line: '// line comment' }] },
|
98
|
+
{ exp: [{ a: 'a' }, { multi: '/* inline comment */' }] },
|
99
|
+
{ exp: { a: 'a' } }
|
100
|
+
]
|
101
|
+
expect(result).to parse_as(expected)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe 'integration test' do
|
107
|
+
it 'processes the main example correctly' do
|
108
|
+
code = %q(
|
109
|
+
a
|
110
|
+
// line comment
|
111
|
+
a a a // line comment
|
112
|
+
a /* inline comment */ a
|
113
|
+
/* multiline
|
114
|
+
comment */
|
115
|
+
)
|
116
|
+
result = parser.parse(code)
|
117
|
+
|
118
|
+
# Verify the structure matches the actual output
|
119
|
+
expect(result).to be_an(Array)
|
120
|
+
expect(result.length).to eq(8)
|
121
|
+
|
122
|
+
# First element: single a
|
123
|
+
expect(result[0]).to parse_as({ exp: { a: 'a' } })
|
124
|
+
|
125
|
+
# Second element: line comment
|
126
|
+
expect(result[1]).to parse_as({ line: '// line comment' })
|
127
|
+
|
128
|
+
# Third element: first a
|
129
|
+
expect(result[2]).to parse_as({ exp: { a: 'a' } })
|
130
|
+
|
131
|
+
# Fourth element: second a
|
132
|
+
expect(result[3]).to parse_as({ exp: { a: 'a' } })
|
133
|
+
|
134
|
+
# Fifth element: third a with line comment
|
135
|
+
expect(result[4]).to parse_as({ exp: [{ a: 'a' }, { line: '// line comment' }] })
|
136
|
+
|
137
|
+
# Sixth element: a with inline comment
|
138
|
+
expect(result[5]).to parse_as({ exp: [{ a: 'a' }, { multi: '/* inline comment */' }] })
|
139
|
+
|
140
|
+
# Seventh element: final a
|
141
|
+
expect(result[6]).to parse_as({ exp: { a: 'a' } })
|
142
|
+
|
143
|
+
# Eighth element: multiline comment
|
144
|
+
expect(result[7]).to parse_as({ multi: "/* multiline\n comment */" })
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'handles various comment scenarios' do
|
148
|
+
input = "// start comment\na /* mid */ a // end\n/* block\ncomment */ a\n"
|
149
|
+
result = parser.parse(input)
|
150
|
+
|
151
|
+
expected = [
|
152
|
+
{ line: '// start comment' },
|
153
|
+
{ exp: [{ a: 'a' }, { multi: '/* mid */' }] },
|
154
|
+
{ exp: [{ a: 'a' }, { line: '// end' }] },
|
155
|
+
{ multi: "/* block\ncomment */" },
|
156
|
+
{ exp: { a: 'a' } }
|
157
|
+
]
|
158
|
+
expect(result).to parse_as(expected)
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'produces the expected output from the example file' do
|
162
|
+
# This matches the exact output shown when running the example
|
163
|
+
code = %q(
|
164
|
+
a
|
165
|
+
// line comment
|
166
|
+
a a a // line comment
|
167
|
+
a /* inline comment */ a
|
168
|
+
/* multiline
|
169
|
+
comment */
|
170
|
+
)
|
171
|
+
result = parser.parse(code)
|
172
|
+
|
173
|
+
expected = [
|
174
|
+
{ exp: { a: 'a' } },
|
175
|
+
{ line: '// line comment' },
|
176
|
+
{ exp: { a: 'a' } },
|
177
|
+
{ exp: { a: 'a' } },
|
178
|
+
{ exp: [{ a: 'a' }, { line: '// line comment' }] },
|
179
|
+
{ exp: [{ a: 'a' }, { multi: '/* inline comment */' }] },
|
180
|
+
{ exp: { a: 'a' } },
|
181
|
+
{ multi: "/* multiline\n comment */" }
|
182
|
+
]
|
183
|
+
expect(result).to parse_as(expected)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|