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,407 @@
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
+
10
+ include Parslet
11
+
12
+ class LiteralsParser < Parslet::Parser
13
+ rule :space do
14
+ (match '[ ]').repeat(1)
15
+ end
16
+
17
+ rule :literals do
18
+ (literal >> eol).repeat
19
+ end
20
+
21
+ rule :literal do
22
+ (integer | string).as(:literal) >> space.maybe
23
+ end
24
+
25
+ rule :string do
26
+ str('"') >>
27
+ (
28
+ (str('\\') >> any) |
29
+ (str('"').absent? >> any)
30
+ ).repeat.as(:string) >>
31
+ str('"')
32
+ end
33
+
34
+ rule :integer do
35
+ match('[0-9]').repeat(1).as(:integer)
36
+ end
37
+
38
+ rule :eol do
39
+ line_end.repeat(1)
40
+ end
41
+
42
+ rule :line_end do
43
+ crlf >> space.maybe
44
+ end
45
+
46
+ rule :crlf do
47
+ match('[\r\n]').repeat(1)
48
+ end
49
+
50
+ root :literals
51
+ end
52
+
53
+ class StringParserLit < Struct.new(:text)
54
+ def to_s
55
+ text.inspect
56
+ end
57
+ end
58
+
59
+ class StringParserStringLit < StringParserLit
60
+ end
61
+
62
+ class StringParserIntLit < StringParserLit
63
+ def to_s
64
+ text
65
+ end
66
+ end
67
+
68
+ RSpec.describe 'String Parser Example' do
69
+ let(:parser) { LiteralsParser.new }
70
+ let(:transform) {
71
+ Parslet::Transform.new do
72
+ rule(:literal => {:integer => simple(:x)}) { StringParserIntLit.new(x) }
73
+ rule(:literal => {:string => simple(:s)}) { StringParserStringLit.new(s) }
74
+ end
75
+ }
76
+
77
+ describe LiteralsParser do
78
+ describe '#integer' do
79
+ it 'parses single digit integers' do
80
+ result = parser.integer.parse('5')
81
+ expect(result).to eq({:integer => '5'})
82
+ end
83
+
84
+ it 'parses multi-digit integers' do
85
+ result = parser.integer.parse('12345')
86
+ expect(result).to eq({:integer => '12345'})
87
+ end
88
+
89
+ it 'fails on non-numeric input' do
90
+ expect { parser.integer.parse('abc') }.to raise_error(Parslet::ParseFailed)
91
+ end
92
+
93
+ it 'fails on empty input' do
94
+ expect { parser.integer.parse('') }.to raise_error(Parslet::ParseFailed)
95
+ end
96
+ end
97
+
98
+ describe '#string' do
99
+ it 'parses simple quoted strings' do
100
+ result = parser.string.parse('"hello"')
101
+ expect(result).to eq({:string => 'hello'})
102
+ end
103
+
104
+ it 'parses empty strings' do
105
+ result = parser.string.parse('""')
106
+ expect(result).to eq({:string => []})
107
+ end
108
+
109
+ it 'parses strings with escaped quotes' do
110
+ result = parser.string.parse('"hello \"world\""')
111
+ expect(result).to eq({:string => 'hello \"world\"'})
112
+ end
113
+
114
+ it 'parses strings with other escaped characters' do
115
+ result = parser.string.parse('"hello\\nworld"')
116
+ expect(result).to eq({:string => 'hello\\nworld'})
117
+ end
118
+
119
+ it 'fails on unquoted strings' do
120
+ expect { parser.string.parse('hello') }.to raise_error(Parslet::ParseFailed)
121
+ end
122
+
123
+ it 'fails on unclosed strings' do
124
+ expect { parser.string.parse('"hello') }.to raise_error(Parslet::ParseFailed)
125
+ end
126
+ end
127
+
128
+ describe '#literal' do
129
+ it 'parses integer literals' do
130
+ result = parser.literal.parse('123')
131
+ expect(result).to eq({:literal => {:integer => '123'}})
132
+ end
133
+
134
+ it 'parses string literals' do
135
+ result = parser.literal.parse('"hello"')
136
+ expect(result).to eq({:literal => {:string => 'hello'}})
137
+ end
138
+
139
+ it 'parses literals with trailing spaces' do
140
+ result = parser.literal.parse('123 ')
141
+ expect(result).to eq({:literal => {:integer => '123'}})
142
+ end
143
+ end
144
+
145
+ describe '#space' do
146
+ it 'parses single space' do
147
+ result = parser.space.parse(' ')
148
+ expect(result.to_s).to eq(' ')
149
+ end
150
+
151
+ it 'parses multiple spaces' do
152
+ result = parser.space.parse(' ')
153
+ expect(result.to_s).to eq(' ')
154
+ end
155
+
156
+ it 'fails on empty input' do
157
+ expect { parser.space.parse('') }.to raise_error(Parslet::ParseFailed)
158
+ end
159
+
160
+ it 'fails on non-space characters' do
161
+ expect { parser.space.parse('a') }.to raise_error(Parslet::ParseFailed)
162
+ end
163
+ end
164
+
165
+ describe '#crlf' do
166
+ it 'parses carriage return' do
167
+ result = parser.crlf.parse("\r")
168
+ expect(result.to_s).to eq("\r")
169
+ end
170
+
171
+ it 'parses line feed' do
172
+ result = parser.crlf.parse("\n")
173
+ expect(result.to_s).to eq("\n")
174
+ end
175
+
176
+ it 'parses CRLF sequence' do
177
+ result = parser.crlf.parse("\r\n")
178
+ expect(result.to_s).to eq("\r\n")
179
+ end
180
+
181
+ it 'parses multiple newlines' do
182
+ result = parser.crlf.parse("\n\n")
183
+ expect(result.to_s).to eq("\n\n")
184
+ end
185
+ end
186
+
187
+ describe '#line_end' do
188
+ it 'parses newline without space' do
189
+ result = parser.line_end.parse("\n")
190
+ expect(result.to_s).to eq("\n")
191
+ end
192
+
193
+ it 'parses newline with trailing space' do
194
+ result = parser.line_end.parse("\n ")
195
+ expect(result.to_s).to eq("\n ")
196
+ end
197
+ end
198
+
199
+ describe '#eol' do
200
+ it 'parses single end of line' do
201
+ result = parser.eol.parse("\n")
202
+ expect(result.to_s).to eq("\n")
203
+ end
204
+
205
+ it 'parses multiple end of lines' do
206
+ result = parser.eol.parse("\n\n")
207
+ expect(result.to_s).to eq("\n\n")
208
+ end
209
+ end
210
+
211
+ describe '#literals (root)' do
212
+ it 'parses single integer literal' do
213
+ result = parser.parse("123\n")
214
+ expect(result).to eq([{:literal => {:integer => '123'}}])
215
+ end
216
+
217
+ it 'parses single string literal' do
218
+ result = parser.parse("\"hello\"\n")
219
+ expect(result).to eq([{:literal => {:string => 'hello'}}])
220
+ end
221
+
222
+ it 'parses multiple literals' do
223
+ input = "123\n\"hello\"\n456\n"
224
+ result = parser.parse(input)
225
+ expect(result).to eq([
226
+ {:literal => {:integer => '123'}},
227
+ {:literal => {:string => 'hello'}},
228
+ {:literal => {:integer => '456'}}
229
+ ])
230
+ end
231
+
232
+ it 'parses literals with spaces' do
233
+ input = "123 \n\"hello\" \n"
234
+ result = parser.parse(input)
235
+ expect(result).to eq([
236
+ {:literal => {:integer => '123'}},
237
+ {:literal => {:string => 'hello'}}
238
+ ])
239
+ end
240
+
241
+ it 'handles empty input' do
242
+ result = parser.parse("")
243
+ expect(result).to eq("")
244
+ end
245
+ end
246
+ end
247
+
248
+ describe 'Transform classes' do
249
+ describe StringParserLit do
250
+ it 'stores text and converts to inspect format' do
251
+ lit = StringParserLit.new('hello')
252
+ expect(lit.text).to eq('hello')
253
+ expect(lit.to_s).to eq('"hello"')
254
+ end
255
+ end
256
+
257
+ describe StringParserStringLit do
258
+ it 'inherits from StringParserLit' do
259
+ string_lit = StringParserStringLit.new('hello')
260
+ expect(string_lit).to be_a(StringParserLit)
261
+ expect(string_lit.text).to eq('hello')
262
+ expect(string_lit.to_s).to eq('"hello"')
263
+ end
264
+ end
265
+
266
+ describe StringParserIntLit do
267
+ it 'inherits from StringParserLit but shows text directly' do
268
+ int_lit = StringParserIntLit.new('123')
269
+ expect(int_lit).to be_a(StringParserLit)
270
+ expect(int_lit.text).to eq('123')
271
+ expect(int_lit.to_s).to eq('123')
272
+ end
273
+ end
274
+ end
275
+
276
+ describe 'Transform rules' do
277
+ it 'transforms integer literals to StringParserIntLit objects' do
278
+ parse_result = {:literal => {:integer => '123'}}
279
+ result = transform.apply(parse_result)
280
+ expect(result).to be_a(StringParserIntLit)
281
+ expect(result.text).to eq('123')
282
+ expect(result.to_s).to eq('123')
283
+ end
284
+
285
+ it 'transforms string literals to StringParserStringLit objects' do
286
+ parse_result = {:literal => {:string => 'hello'}}
287
+ result = transform.apply(parse_result)
288
+ expect(result).to be_a(StringParserStringLit)
289
+ expect(result.text).to eq('hello')
290
+ expect(result.to_s).to eq('"hello"')
291
+ end
292
+
293
+ it 'transforms arrays of literals' do
294
+ parse_result = [
295
+ {:literal => {:integer => '123'}},
296
+ {:literal => {:string => 'hello'}}
297
+ ]
298
+ result = transform.apply(parse_result)
299
+ expect(result).to be_an(Array)
300
+ expect(result.length).to eq(2)
301
+ expect(result[0]).to be_a(StringParserIntLit)
302
+ expect(result[1]).to be_a(StringParserStringLit)
303
+ end
304
+ end
305
+
306
+ describe 'integration test with simple.lit file' do
307
+ let(:simple_lit_content) { "123\n12345\n\" Some String with \\\"escapes\\\"\"\n" }
308
+
309
+ it 'parses the simple.lit file correctly' do
310
+ result = parser.parse(simple_lit_content)
311
+ expect(result).to eq([
312
+ {:literal => {:integer => '123'}},
313
+ {:literal => {:integer => '12345'}},
314
+ {:literal => {:string => ' Some String with \"escapes\"'}}
315
+ ])
316
+ end
317
+
318
+ it 'transforms the simple.lit file to AST objects' do
319
+ parse_result = parser.parse(simple_lit_content)
320
+ ast = transform.apply(parse_result)
321
+
322
+ expect(ast).to be_an(Array)
323
+ expect(ast.length).to eq(3)
324
+
325
+ # First integer
326
+ expect(ast[0]).to be_a(StringParserIntLit)
327
+ expect(ast[0].text).to eq('123')
328
+ expect(ast[0].to_s).to eq('123')
329
+
330
+ # Second integer
331
+ expect(ast[1]).to be_a(StringParserIntLit)
332
+ expect(ast[1].text).to eq('12345')
333
+ expect(ast[1].to_s).to eq('12345')
334
+
335
+ # String with escapes
336
+ expect(ast[2]).to be_a(StringParserStringLit)
337
+ expect(ast[2].text.to_s).to include('Some String with')
338
+ expect(ast[2].text.to_s).to include('escapes')
339
+ expect(ast[2].to_s).to include('Some String with')
340
+ expect(ast[2].to_s).to include('escapes')
341
+ end
342
+
343
+ it 'reproduces the example behavior' do
344
+ # This test reproduces what the example file does:
345
+ # 1. Parse the simple.lit file
346
+ # 2. Transform to AST objects
347
+ # 3. Verify the structure
348
+
349
+ parsetree = LiteralsParser.new.parse(simple_lit_content)
350
+
351
+ transform = Parslet::Transform.new do
352
+ rule(:literal => {:integer => simple(:x)}) { StringParserIntLit.new(x) }
353
+ rule(:literal => {:string => simple(:s)}) { StringParserStringLit.new(s) }
354
+ end
355
+
356
+ ast = transform.apply(parsetree)
357
+
358
+ # Verify we have the expected structure
359
+ expect(ast).to be_an(Array)
360
+ expect(ast.length).to eq(3)
361
+ expect(ast.all? { |item| item.is_a?(StringParserLit) }).to be true
362
+
363
+ # Verify the types and values
364
+ expect(ast[0]).to be_a(StringParserIntLit)
365
+ expect(ast[1]).to be_a(StringParserIntLit)
366
+ expect(ast[2]).to be_a(StringParserStringLit)
367
+ end
368
+ end
369
+
370
+ describe 'edge cases and error handling' do
371
+ it 'handles mixed content correctly' do
372
+ input = "42\n\"test\"\n999\n\"another\"\n"
373
+ result = parser.parse(input)
374
+ expect(result.length).to eq(4)
375
+ expect(result[0][:literal][:integer]).to eq('42')
376
+ expect(result[1][:literal][:string]).to eq('test')
377
+ expect(result[2][:literal][:integer]).to eq('999')
378
+ expect(result[3][:literal][:string]).to eq('another')
379
+ end
380
+
381
+ it 'handles strings with various escape sequences' do
382
+ input = "\"line1\\nline2\"\n\"quote: \\\"hello\\\"\"\n"
383
+ result = parser.parse(input)
384
+ expect(result.length).to eq(2)
385
+ expect(result[0][:literal][:string]).to eq('line1\\nline2')
386
+ expect(result[1][:literal][:string].to_s).to match(/quote: \\"hello\\"/)
387
+ end
388
+
389
+ it 'fails on malformed input' do
390
+ expect { parser.parse('123 "unclosed string') }.to raise_error(Parslet::ParseFailed)
391
+ expect { parser.parse('not_a_literal\n') }.to raise_error(Parslet::ParseFailed)
392
+ end
393
+
394
+ it 'handles large integers' do
395
+ input = "999999999999999999\n"
396
+ result = parser.parse(input)
397
+ expect(result[0][:literal][:integer]).to eq('999999999999999999')
398
+ end
399
+
400
+ it 'handles empty strings in mixed content' do
401
+ input = "123\n\"\"\n456\n"
402
+ result = parser.parse(input)
403
+ expect(result.length).to eq(3)
404
+ expect(result[1][:literal][:string]).to eq([])
405
+ end
406
+ end
407
+ end
@@ -0,0 +1,62 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+
3
+ require "parslet"
4
+ require "pp"
5
+
6
+ # Parses strings like "var1 and (var2 or var3)" respecting operator precedence
7
+ # and parentheses. After that transforms the parse tree into an array of
8
+ # arrays like this:
9
+ #
10
+ # [["1", "2"], ["1", "3"]]
11
+ #
12
+ # The array represents a DNF (disjunctive normal form). Elements of outer
13
+ # array are connected with "or" operator, while elements of inner arrays are
14
+ # joined with "and".
15
+ #
16
+ class MyParser < Parslet::Parser
17
+ rule(:space) { match[" "].repeat(1) }
18
+ rule(:space?) { space.maybe }
19
+
20
+ rule(:lparen) { str("(") >> space? }
21
+ rule(:rparen) { str(")") >> space? }
22
+
23
+ rule(:and_operator) { str("and") >> space? }
24
+ rule(:or_operator) { str("or") >> space? }
25
+
26
+ rule(:var) { str("var") >> match["0-9"].repeat(1).as(:var) >> space? }
27
+
28
+ # The primary rule deals with parentheses.
29
+ rule(:primary) { lparen >> or_operation >> rparen | var }
30
+
31
+ # Note that following rules are both right-recursive.
32
+ rule(:and_operation) {
33
+ (primary.as(:left) >> and_operator >>
34
+ and_operation.as(:right)).as(:and) |
35
+ primary }
36
+
37
+ rule(:or_operation) {
38
+ (and_operation.as(:left) >> or_operator >>
39
+ or_operation.as(:right)).as(:or) |
40
+ and_operation }
41
+
42
+ # We start at the lowest precedence rule.
43
+ root(:or_operation)
44
+ end
45
+
46
+ class Transformer < Parslet::Transform
47
+ rule(:var => simple(:var)) { [[String(var)]] }
48
+
49
+ rule(:or => { :left => subtree(:left), :right => subtree(:right) }) do
50
+ (left + right)
51
+ end
52
+
53
+ rule(:and => { :left => subtree(:left), :right => subtree(:right) }) do
54
+ res = []
55
+ left.each do |l|
56
+ right.each do |r|
57
+ res << (l + r)
58
+ end
59
+ end
60
+ res
61
+ end
62
+ end
@@ -0,0 +1,86 @@
1
+ # A simple integer calculator to answer the question about how to do
2
+ # left and right associativity in parslet (PEG) once and for all.
3
+
4
+ require 'parslet'
5
+
6
+ module CalcExample
7
+ # This is the parsing stage. It expresses left associativity by compiling
8
+ # list of things that have the same associativity.
9
+ class CalcParser < Parslet::Parser
10
+ root :addition
11
+
12
+ rule(:addition) {
13
+ multiplication.as(:l) >> (add_op >> multiplication.as(:r)).repeat(1) |
14
+ multiplication
15
+ }
16
+
17
+ rule(:multiplication) {
18
+ integer.as(:l) >> (mult_op >> integer.as(:r)).repeat(1) |
19
+ integer }
20
+
21
+ rule(:integer) { digit.repeat(1).as(:i) >> space? }
22
+
23
+ rule(:mult_op) { match['*/'].as(:o) >> space? }
24
+ rule(:add_op) { match['+-'].as(:o) >> space? }
25
+
26
+ rule(:digit) { match['0-9'] }
27
+ rule(:space?) { match['\s'].repeat }
28
+ end
29
+
30
+ # Classes for the abstract syntax tree.
31
+ Int = Struct.new(:int) do
32
+ def eval; self end
33
+ def op(operation, other)
34
+ left = int
35
+ right = other.int
36
+
37
+ Int.new(
38
+ case operation
39
+ when '+'
40
+ left + right
41
+ when '-'
42
+ left - right
43
+ when '*'
44
+ left * right
45
+ when '/'
46
+ left / right
47
+ end)
48
+ end
49
+ def to_i
50
+ int
51
+ end
52
+ end
53
+
54
+ Seq = Struct.new(:sequence) do
55
+ def eval
56
+ sequence.reduce { |accum, operation|
57
+ operation.call(accum) }
58
+ end
59
+ end
60
+
61
+ LeftOp = Struct.new(:operation, :right) do
62
+ def call(left)
63
+ left = left.eval
64
+ right = self.right.eval
65
+
66
+ left.op(operation, right)
67
+ end
68
+ end
69
+
70
+ # Transforming intermediary syntax tree into a real AST.
71
+ class CalcTransform < Parslet::Transform
72
+ rule(i: simple(:i)) { Int.new(Integer(i)) }
73
+ rule(o: simple(:o), r: simple(:i)) { LeftOp.new(o, i) }
74
+ rule(l: simple(:i)) { i }
75
+ rule(sequence(:seq)) { Seq.new(seq) }
76
+ end
77
+
78
+ # And this calls everything in the right order.
79
+ def self.calculate(str)
80
+ intermediary_tree = CalcParser.new.parse(str)
81
+ abstract_tree = CalcTransform.new.apply(intermediary_tree)
82
+ result = abstract_tree.eval
83
+
84
+ result.to_i
85
+ end
86
+ end
@@ -0,0 +1,36 @@
1
+ # This example demonstrates how pieces of input can be captured and matched
2
+ # against later on. Without this, you cannot match here-documents and other
3
+ # self-dependent grammars.
4
+
5
+ require 'parslet'
6
+
7
+ module CaptureExample
8
+ class CapturingParser < Parslet::Parser
9
+ root :document
10
+
11
+ # Introduce a scope for each document. This ensures that documents can be
12
+ # nested.
13
+ rule(:document) { scope { doc_start >> text >> doc_end } }
14
+
15
+ # Start of a document is a heredoc marker. This is captured in :marker
16
+ rule(:doc_start) { str('<') >> marker >> newline }
17
+ rule(:marker) { match['A-Z'].repeat(1).capture(:marker) }
18
+
19
+ # The content of a document can be either lines of text or another
20
+ # document, introduced by <HERE, where HERE is the doc marker.
21
+ rule(:text) { (document.as(:doc) | text_line.as(:line)).repeat(1) }
22
+ rule(:text_line) { captured_marker.absent? >> any >>
23
+ (newline.absent? >> any).repeat >> newline }
24
+
25
+ # The end of the document is marked by the marker that was at the beginning
26
+ # of the document, by itself on a line.
27
+ rule(:doc_end) { captured_marker }
28
+ rule(:captured_marker) {
29
+ dynamic { |source, context|
30
+ str(context.captures[:marker])
31
+ }
32
+ }
33
+
34
+ rule(:newline) { match["\n"] }
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ # A small example on how to parse common types of comments. The example
2
+ # started out with parser code from Stephen Waits.
3
+
4
+ require 'parslet'
5
+
6
+ module CommentsExample
7
+ class ALanguage < Parslet::Parser
8
+ root(:lines)
9
+
10
+ rule(:lines) { line.repeat }
11
+ rule(:line) { spaces >> expression.repeat >> newline }
12
+ rule(:newline) { str("\n") >> str("\r").maybe }
13
+
14
+ rule(:expression) { (str('a').as(:a) >> spaces).as(:exp) }
15
+
16
+ rule(:spaces) { space.repeat }
17
+ rule(:space) { multiline_comment | line_comment | str(' ') }
18
+
19
+ rule(:line_comment) { (str('//') >> (newline.absent? >> any).repeat).as(:line) }
20
+ rule(:multiline_comment) { (str('/*') >> (str('*/').absent? >> any).repeat >> str('*/')).as(:multi) }
21
+ end
22
+ end