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