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,525 @@
1
+ require 'spec_helper'
2
+
3
+ # Load the example file to get the classes
4
+ $:.unshift File.dirname(__FILE__) + "/../../example"
5
+
6
+ # Define the classes directly to avoid eval issues
7
+ require 'pp'
8
+ require 'parslet'
9
+ require 'parslet/rig/rspec'
10
+ require 'parslet/convenience'
11
+
12
+ include Parslet
13
+
14
+ class InfixExpressionParser < Parslet::Parser
15
+ root :variable_assignment_list
16
+
17
+ rule(:space) { match[' '] }
18
+
19
+ def cts atom
20
+ atom >> space.repeat
21
+ end
22
+ def infix *args
23
+ Infix.new(*args)
24
+ end
25
+
26
+ # This is the heart of the infix expression parser: real simple definitions
27
+ # for all the pieces we need.
28
+ rule(:mul_op) { cts match['*/'] }
29
+ rule(:add_op) { cts match['+-'] }
30
+ rule(:digit) { match['0-9'] }
31
+ rule(:integer) { cts digit.repeat(1).as(:int) }
32
+
33
+ rule(:expression) { infix_expression(integer,
34
+ [mul_op, 2, :left],
35
+ [add_op, 1, :right]) }
36
+
37
+ # And now adding variable assignments to that, just to a) demonstrate this
38
+ # embedded in a bigger parser, and b) make the example interesting.
39
+ rule(:variable_assignment_list) {
40
+ variable_assignment.repeat(1) }
41
+ rule(:variable_assignment) {
42
+ identifier.as(:ident) >> equal_sign >> expression.as(:exp) >> eol }
43
+ rule(:identifier) {
44
+ cts (match['a-z'] >> match['a-zA-Z0-9'].repeat) }
45
+ rule(:equal_sign) {
46
+ cts str('=') }
47
+ rule(:eol) {
48
+ cts(str("\n")) | any.absent? }
49
+ end
50
+
51
+ class InfixInterpreter < Parslet::Transform
52
+ rule(int: simple(:int)) { Integer(int) }
53
+ rule(ident: simple(:ident), exp: simple(:result)) { |d|
54
+ d[:doc][d[:ident].to_s.strip.to_sym] = d[:result] }
55
+
56
+ rule(l: simple(:l), o: /^\*/, r: simple(:r)) { l * r }
57
+ rule(l: simple(:l), o: /^\+/, r: simple(:r)) { l + r }
58
+ end
59
+
60
+ RSpec.describe 'Precedence Calculator Example' do
61
+ let(:parser) { InfixExpressionParser.new }
62
+ let(:interpreter) { InfixInterpreter.new }
63
+
64
+ describe InfixExpressionParser do
65
+ describe '#digit' do
66
+ it 'parses single digits' do
67
+ result = parser.digit.parse('5')
68
+ expect(result).to eq('5')
69
+ end
70
+
71
+ it 'fails on non-digits' do
72
+ expect { parser.digit.parse('a') }.to raise_error(Parslet::ParseFailed)
73
+ end
74
+
75
+ it 'fails on empty input' do
76
+ expect { parser.digit.parse('') }.to raise_error(Parslet::ParseFailed)
77
+ end
78
+ end
79
+
80
+ describe '#integer' do
81
+ it 'parses single digit integers' do
82
+ result = parser.integer.parse('5')
83
+ expect(result).to eq({:int => '5'})
84
+ end
85
+
86
+ it 'parses multi-digit integers' do
87
+ result = parser.integer.parse('123')
88
+ expect(result).to eq({:int => '123'})
89
+ end
90
+
91
+ it 'parses integers with trailing spaces' do
92
+ result = parser.integer.parse('123 ')
93
+ expect(result).to eq({:int => '123'})
94
+ end
95
+
96
+ it 'fails on non-numeric input' do
97
+ expect { parser.integer.parse('abc') }.to raise_error(Parslet::ParseFailed)
98
+ end
99
+ end
100
+
101
+ describe '#mul_op' do
102
+ it 'parses multiplication operator' do
103
+ result = parser.mul_op.parse('*')
104
+ expect(result.to_s).to eq('*')
105
+ end
106
+
107
+ it 'parses division operator' do
108
+ result = parser.mul_op.parse('/')
109
+ expect(result.to_s).to eq('/')
110
+ end
111
+
112
+ it 'parses operators with trailing spaces' do
113
+ result = parser.mul_op.parse('* ')
114
+ expect(result.to_s.strip).to eq('*')
115
+ end
116
+
117
+ it 'fails on other operators' do
118
+ expect { parser.mul_op.parse('+') }.to raise_error(Parslet::ParseFailed)
119
+ end
120
+ end
121
+
122
+ describe '#add_op' do
123
+ it 'parses addition operator' do
124
+ result = parser.add_op.parse('+')
125
+ expect(result.to_s).to eq('+')
126
+ end
127
+
128
+ it 'parses subtraction operator' do
129
+ result = parser.add_op.parse('-')
130
+ expect(result.to_s).to eq('-')
131
+ end
132
+
133
+ it 'parses operators with trailing spaces' do
134
+ result = parser.add_op.parse('+ ')
135
+ expect(result.to_s.strip).to eq('+')
136
+ end
137
+
138
+ it 'fails on other operators' do
139
+ expect { parser.add_op.parse('*') }.to raise_error(Parslet::ParseFailed)
140
+ end
141
+ end
142
+
143
+ describe '#identifier' do
144
+ it 'parses simple identifiers' do
145
+ result = parser.identifier.parse('a')
146
+ expect(result).to eq('a')
147
+ end
148
+
149
+ it 'parses multi-character identifiers' do
150
+ result = parser.identifier.parse('variable')
151
+ expect(result).to eq('variable')
152
+ end
153
+
154
+ it 'parses identifiers with numbers' do
155
+ result = parser.identifier.parse('var123')
156
+ expect(result).to eq('var123')
157
+ end
158
+
159
+ it 'parses identifiers with trailing spaces' do
160
+ result = parser.identifier.parse('abc ')
161
+ expect(result.to_s.strip).to eq('abc')
162
+ end
163
+
164
+ it 'fails on identifiers starting with numbers' do
165
+ expect { parser.identifier.parse('123abc') }.to raise_error(Parslet::ParseFailed)
166
+ end
167
+
168
+ it 'fails on identifiers starting with uppercase' do
169
+ expect { parser.identifier.parse('Abc') }.to raise_error(Parslet::ParseFailed)
170
+ end
171
+ end
172
+
173
+ describe '#equal_sign' do
174
+ it 'parses equal sign' do
175
+ result = parser.equal_sign.parse('=')
176
+ expect(result).to eq('=')
177
+ end
178
+
179
+ it 'parses equal sign with trailing spaces' do
180
+ result = parser.equal_sign.parse('= ')
181
+ expect(result.to_s.strip).to eq('=')
182
+ end
183
+
184
+ it 'fails on other characters' do
185
+ expect { parser.equal_sign.parse('+') }.to raise_error(Parslet::ParseFailed)
186
+ end
187
+ end
188
+
189
+ describe '#eol' do
190
+ it 'parses newline' do
191
+ result = parser.eol.parse("\n")
192
+ expect(result).to eq("\n")
193
+ end
194
+
195
+ it 'parses newline with trailing spaces' do
196
+ result = parser.eol.parse("\n ")
197
+ expect(result.to_s).to eq("\n ")
198
+ end
199
+
200
+ it 'parses end of input' do
201
+ result = parser.eol.parse('')
202
+ expect(result).to be_nil
203
+ end
204
+ end
205
+
206
+ describe '#expression' do
207
+ it 'parses simple integers' do
208
+ result = parser.expression.parse('42')
209
+ expect(result).to eq({:int => '42'})
210
+ end
211
+
212
+ it 'parses simple addition' do
213
+ result = parser.expression.parse('1 + 2')
214
+ expect(result).to eq({l: {:int => '1'}, o: '+ ', r: {:int => '2'}})
215
+ end
216
+
217
+ it 'parses simple multiplication' do
218
+ result = parser.expression.parse('3 * 4')
219
+ expect(result).to eq({l: {:int => '3'}, o: '* ', r: {:int => '4'}})
220
+ end
221
+
222
+ it 'parses expressions with correct precedence (multiplication first)' do
223
+ result = parser.expression.parse('1 + 2 * 3')
224
+ # Should parse as 1 + (2 * 3) due to precedence
225
+ expect(result).to eq({
226
+ l: {:int => '1'},
227
+ o: '+ ',
228
+ r: {l: {:int => '2'}, o: '* ', r: {:int => '3'}}
229
+ })
230
+ end
231
+
232
+ it 'parses complex expressions' do
233
+ result = parser.expression.parse('100 + 3*4')
234
+ expect(result).to eq({
235
+ l: {:int => '100'},
236
+ o: '+ ',
237
+ r: {l: {:int => '3'}, o: '*', r: {:int => '4'}}
238
+ })
239
+ end
240
+ end
241
+
242
+ describe '#variable_assignment' do
243
+ it 'parses simple variable assignment' do
244
+ result = parser.variable_assignment.parse("a = 1\n")
245
+ expect(result).to have_key(:ident)
246
+ expect(result).to have_key(:exp)
247
+ expect(result[:ident].to_s.strip).to eq('a')
248
+ expect(result[:exp]).to eq({:int => '1'})
249
+ end
250
+
251
+ it 'parses assignment with expression' do
252
+ result = parser.variable_assignment.parse("result = 2 + 3\n")
253
+ expect(result).to have_key(:ident)
254
+ expect(result).to have_key(:exp)
255
+ expect(result[:ident].to_s.strip).to eq('result')
256
+ expect(result[:exp]).to have_key(:l)
257
+ expect(result[:exp]).to have_key(:o)
258
+ expect(result[:exp]).to have_key(:r)
259
+ end
260
+
261
+ it 'parses assignment at end of input' do
262
+ result = parser.variable_assignment.parse("x = 42")
263
+ expect(result).to have_key(:ident)
264
+ expect(result).to have_key(:exp)
265
+ expect(result[:ident].to_s.strip).to eq('x')
266
+ expect(result[:exp]).to eq({:int => '42'})
267
+ end
268
+ end
269
+
270
+ describe '#variable_assignment_list (root)' do
271
+ it 'parses single assignment' do
272
+ result = parser.parse("a = 1\n")
273
+ expect(result).to be_an(Array)
274
+ expect(result.length).to eq(1)
275
+ expect(result[0]).to have_key(:ident)
276
+ expect(result[0]).to have_key(:exp)
277
+ expect(result[0][:ident].to_s.strip).to eq('a')
278
+ expect(result[0][:exp]).to eq({:int => '1'})
279
+ end
280
+
281
+ it 'parses multiple assignments' do
282
+ input = "a = 1\nb = 2\n"
283
+ result = parser.parse(input)
284
+ expect(result).to be_an(Array)
285
+ expect(result.length).to eq(2)
286
+ expect(result[0][:ident].to_s.strip).to eq('a')
287
+ expect(result[0][:exp]).to eq({:int => '1'})
288
+ expect(result[1][:ident].to_s.strip).to eq('b')
289
+ expect(result[1][:exp]).to eq({:int => '2'})
290
+ end
291
+
292
+ it 'parses assignments with complex expressions' do
293
+ input = "a = 1\nb = 2\nc = 3 * 25\n"
294
+ result = parser.parse(input)
295
+ expect(result).to be_an(Array)
296
+ expect(result.length).to eq(3)
297
+ expect(result[0][:ident].to_s.strip).to eq('a')
298
+ expect(result[0][:exp]).to eq({:int => '1'})
299
+ expect(result[1][:ident].to_s.strip).to eq('b')
300
+ expect(result[1][:exp]).to eq({:int => '2'})
301
+ expect(result[2]).to have_key(:ident)
302
+ expect(result[2]).to have_key(:exp)
303
+ expect(result[2][:ident].to_s.strip).to eq('c')
304
+ expect(result[2][:exp]).to have_key(:l)
305
+ expect(result[2][:exp]).to have_key(:o)
306
+ expect(result[2][:exp]).to have_key(:r)
307
+ end
308
+ end
309
+ end
310
+
311
+ describe InfixInterpreter do
312
+ describe 'integer transformation' do
313
+ it 'transforms integer nodes to Ruby integers' do
314
+ result = interpreter.apply({int: '42'})
315
+ expect(result).to eq(42)
316
+ expect(result).to be_a(Integer)
317
+ end
318
+
319
+ it 'transforms string integers correctly' do
320
+ result = interpreter.apply({int: '123'})
321
+ expect(result).to eq(123)
322
+ end
323
+ end
324
+
325
+ describe 'arithmetic operations' do
326
+ it 'performs multiplication' do
327
+ result = interpreter.apply({l: 3, o: '*', r: 4})
328
+ expect(result).to eq(12)
329
+ end
330
+
331
+ it 'performs addition' do
332
+ result = interpreter.apply({l: 5, o: '+', r: 7})
333
+ expect(result).to eq(12)
334
+ end
335
+
336
+ it 'handles nested operations' do
337
+ # Represents 2 + (3 * 4)
338
+ nested = {
339
+ l: 2,
340
+ o: '+',
341
+ r: {l: 3, o: '*', r: 4}
342
+ }
343
+ result = interpreter.apply(nested)
344
+ expect(result).to eq(14)
345
+ end
346
+ end
347
+
348
+ describe 'variable assignment transformation' do
349
+ it 'assigns simple values to variables' do
350
+ doc = {}
351
+ result = interpreter.apply({ident: 'a', exp: 42}, doc: doc)
352
+ expect(doc[:a]).to eq(42)
353
+ end
354
+
355
+ it 'assigns expression results to variables' do
356
+ doc = {}
357
+ result = interpreter.apply({ident: 'result', exp: 15}, doc: doc)
358
+ expect(doc[:result]).to eq(15)
359
+ end
360
+
361
+ it 'handles multiple assignments' do
362
+ doc = {}
363
+ assignments = [
364
+ {ident: 'a', exp: 1},
365
+ {ident: 'b', exp: 2}
366
+ ]
367
+ interpreter.apply(assignments, doc: doc)
368
+ expect(doc[:a]).to eq(1)
369
+ expect(doc[:b]).to eq(2)
370
+ end
371
+ end
372
+ end
373
+
374
+ describe 'integration test with example input' do
375
+ let(:input) do
376
+ <<~ASSIGNMENTS
377
+ a = 1
378
+ b = 2
379
+ c = 3 * 25
380
+ d = 100 + 3*4
381
+ ASSIGNMENTS
382
+ end
383
+
384
+ it 'parses the example input correctly' do
385
+ result = parser.parse(input)
386
+ expect(result).to be_an(Array)
387
+ expect(result.length).to eq(4)
388
+
389
+ # Check each assignment structure
390
+ expect(result[0]).to have_key(:ident)
391
+ expect(result[0]).to have_key(:exp)
392
+ expect(result[0][:ident].to_s.strip).to eq('a')
393
+ expect(result[0][:exp]).to eq({:int => '1'})
394
+
395
+ expect(result[1]).to have_key(:ident)
396
+ expect(result[1]).to have_key(:exp)
397
+ expect(result[1][:ident].to_s.strip).to eq('b')
398
+ expect(result[1][:exp]).to eq({:int => '2'})
399
+
400
+ # c = 3 * 25
401
+ expect(result[2][:ident].to_s.strip).to eq('c')
402
+ expect(result[2][:exp]).to have_key(:l)
403
+ expect(result[2][:exp]).to have_key(:o)
404
+ expect(result[2][:exp]).to have_key(:r)
405
+
406
+ # d = 100 + 3*4 (should respect precedence)
407
+ expect(result[3][:ident].to_s.strip).to eq('d')
408
+ expect(result[3][:exp]).to have_key(:l)
409
+ expect(result[3][:exp]).to have_key(:o)
410
+ expect(result[3][:exp]).to have_key(:r)
411
+ end
412
+
413
+ it 'transforms and evaluates the example correctly' do
414
+ parse_tree = parser.parse(input)
415
+ bindings = {}
416
+ interpreter.apply(parse_tree, doc: bindings)
417
+
418
+ expect(bindings[:a]).to eq(1)
419
+ expect(bindings[:b]).to eq(2)
420
+ expect(bindings[:c]).to eq(75) # 3 * 25
421
+ expect(bindings[:d]).to eq(112) # 100 + (3*4) = 100 + 12
422
+ end
423
+
424
+ it 'reproduces the example behavior' do
425
+ # This test reproduces what the example file does
426
+ int_tree = parser.parse(input)
427
+ bindings = {}
428
+ result = interpreter.apply(int_tree, doc: bindings)
429
+
430
+ # Verify the final bindings match expected values
431
+ expected_bindings = {
432
+ a: 1,
433
+ b: 2,
434
+ c: 75,
435
+ d: 112
436
+ }
437
+
438
+ expect(bindings).to eq(expected_bindings)
439
+ end
440
+ end
441
+
442
+ describe 'precedence and associativity' do
443
+ it 'handles left associativity for addition' do
444
+ # Test that 1 + 2 + 3 is parsed as (1 + 2) + 3
445
+ result = parser.expression.parse('1 + 2 + 3')
446
+ # Due to right associativity setting, this should be 1 + (2 + 3)
447
+ expect(result).to eq({
448
+ l: {:int => '1'},
449
+ o: '+ ',
450
+ r: {l: {:int => '2'}, o: '+ ', r: {:int => '3'}}
451
+ })
452
+ end
453
+
454
+ it 'handles multiplication precedence over addition' do
455
+ # Test that 2 + 3 * 4 is parsed as 2 + (3 * 4)
456
+ result = parser.expression.parse('2 + 3 * 4')
457
+ expect(result).to eq({
458
+ l: {:int => '2'},
459
+ o: '+ ',
460
+ r: {l: {:int => '3'}, o: '* ', r: {:int => '4'}}
461
+ })
462
+ end
463
+
464
+ it 'evaluates precedence correctly in complex expressions' do
465
+ input = "result = 1 + 2 * 3 + 4\n"
466
+ parse_tree = parser.parse(input)
467
+ bindings = {}
468
+ interpreter.apply(parse_tree, doc: bindings)
469
+
470
+ # Should be 1 + (2 * 3) + 4 = 1 + 6 + 4 = 11
471
+ # But with right associativity: 1 + (2 * 3 + 4) = 1 + (6 + 4) = 1 + 10 = 11
472
+ expect(bindings[:result]).to eq(11)
473
+ end
474
+ end
475
+
476
+ describe 'error handling' do
477
+ it 'fails on invalid variable names' do
478
+ expect { parser.parse("123invalid = 1\n") }.to raise_error(Parslet::ParseFailed)
479
+ end
480
+
481
+ it 'fails on missing equal sign' do
482
+ expect { parser.parse("a 1\n") }.to raise_error(Parslet::ParseFailed)
483
+ end
484
+
485
+ it 'fails on incomplete expressions' do
486
+ expect { parser.parse("a = 1 +\n") }.to raise_error(Parslet::ParseFailed)
487
+ end
488
+
489
+ it 'fails on invalid operators' do
490
+ expect { parser.parse("a = 1 % 2\n") }.to raise_error(Parslet::ParseFailed)
491
+ end
492
+
493
+ it 'handles empty input' do
494
+ expect { parser.parse("") }.to raise_error(Parslet::ParseFailed)
495
+ end
496
+ end
497
+
498
+ describe 'edge cases' do
499
+ it 'handles single character variable names' do
500
+ result = parser.parse("x = 1\n")
501
+ expect(result[0][:ident].to_s.strip).to eq('x')
502
+ end
503
+
504
+ it 'handles large numbers' do
505
+ result = parser.parse("big = 999999\n")
506
+ bindings = {}
507
+ interpreter.apply(result, doc: bindings)
508
+ expect(bindings[:big]).to eq(999999)
509
+ end
510
+
511
+ it 'handles expressions without spaces' do
512
+ result = parser.parse("compact=1+2*3\n")
513
+ bindings = {}
514
+ interpreter.apply(result, doc: bindings)
515
+ expect(bindings[:compact]).to eq(7) # 1 + (2 * 3)
516
+ end
517
+
518
+ it 'handles assignments at end of input without newline' do
519
+ result = parser.parse("final = 42")
520
+ bindings = {}
521
+ interpreter.apply(result, doc: bindings)
522
+ expect(bindings[:final]).to eq(42)
523
+ end
524
+ end
525
+ end