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,329 @@
1
+ require 'spec_helper'
2
+
3
+ require 'parslet'
4
+
5
+ describe 'Regressions from real examples' do
6
+ # This parser piece produces on the left a subtree that is keyed (a hash)
7
+ # and on the right a subtree that is a repetition of such subtrees. I've
8
+ # for now decided that these would merge into the repetition such that the
9
+ # return value is an array. This avoids maybe loosing keys/values in a
10
+ # hash merge.
11
+ #
12
+ class ArgumentListParser
13
+ include Parslet
14
+
15
+ rule :argument_list do
16
+ expression.as(:argument) >>
17
+ (comma >> expression.as(:argument)).repeat
18
+ end
19
+ rule :expression do
20
+ string
21
+ end
22
+ rule :string do
23
+ str('"') >>
24
+ (
25
+ str('\\') >> any |
26
+ str('"').absent? >> any
27
+ ).repeat.as(:string) >>
28
+ str('"') >> space?
29
+ end
30
+ rule :comma do
31
+ str(',') >> space?
32
+ end
33
+ rule :space? do
34
+ space.maybe
35
+ end
36
+ rule :space do
37
+ match("[ \t]").repeat(1)
38
+ end
39
+
40
+ def parse(str)
41
+ argument_list.parse(str)
42
+ end
43
+ end
44
+ describe ArgumentListParser do
45
+ let(:instance) { ArgumentListParser.new }
46
+
47
+ it 'has method expression' do
48
+ expect(instance).to respond_to(:expression)
49
+ end
50
+
51
+ it 'parses "arg1", "arg2"' do
52
+ result = ArgumentListParser.new.parse('"arg1", "arg2"')
53
+
54
+ expect(result.size).to eq(2)
55
+ result.each do |r|
56
+ r[:argument]
57
+ end
58
+ end
59
+
60
+ it 'parses "arg1", "arg2", "arg3"' do
61
+ result = ArgumentListParser.new.parse('"arg1", "arg2", "arg3"')
62
+
63
+ expect(result.size).to eq(3)
64
+ result.each do |r|
65
+ r[:argument]
66
+ end
67
+ end
68
+ end
69
+
70
+ class ParensParser < Parslet::Parser
71
+ rule(:balanced) do
72
+ str('(').as(:l) >> balanced.maybe.as(:m) >> str(')').as(:r)
73
+ end
74
+
75
+ root(:balanced)
76
+ end
77
+ describe ParensParser do
78
+ let(:instance) { ParensParser.new }
79
+
80
+ context 'statefulness: trying several expressions in sequence' do
81
+ it 'is not stateful' do
82
+ # NOTE: Since you've come here to read this, I'll explain why
83
+ # this is broken and not fixed: You're looking at the tuning branch,
84
+ # which rewrites a bunch of stuff - so I have failing tests to
85
+ # remind me of what is left to be done. And to remind you not to
86
+ # trust this code.
87
+ instance.parse('(())')
88
+ expect do
89
+ instance.parse('((()))')
90
+ instance.parse('(((())))')
91
+ end.not_to raise_error
92
+ end
93
+ end
94
+
95
+ context "expression '(())'" do
96
+ let(:result) { instance.parse('(())') }
97
+
98
+ it 'yields a doubly nested hash' do
99
+ expect(result).to be_a(Hash)
100
+ expect(result).to have_key(:m)
101
+ expect(result[:m]).to be_a(Hash) # This was an array earlier
102
+ end
103
+
104
+ context 'inner hash' do
105
+ let(:inner) { result[:m] }
106
+
107
+ it 'has nil as :m' do
108
+ expect(inner[:m]).to be_nil
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ class ALanguage < Parslet::Parser
115
+ root(:expressions)
116
+
117
+ rule(:expressions) { (line >> eol).repeat(1) | line }
118
+ rule(:line) { space? >> an_expression.as(:exp).repeat }
119
+ rule(:an_expression) { str('a').as(:a) >> space? }
120
+
121
+ rule(:eol) { space? >> match["\n\r"].repeat(1) >> space? }
122
+
123
+ rule(:space?) { space.repeat }
124
+ rule(:space) { multiline_comment.as(:multi) | line_comment.as(:line) | str(' ') }
125
+
126
+ rule(:line_comment) { str('//') >> (match["\n\r"].absent? >> any).repeat }
127
+ rule(:multiline_comment) { str('/*') >> (str('*/').absent? >> any).repeat >> str('*/') }
128
+ end
129
+ describe ALanguage do
130
+ def remove_indent(s)
131
+ s.to_s.lines.map { |l| l.chomp.strip }.join("\n")
132
+ end
133
+
134
+ it 'counts lines correctly' do
135
+ cause = catch_failed_parse do
136
+ subject.parse('a
137
+ a a a
138
+ aaa // ff
139
+ /*
140
+ a
141
+ */
142
+ b
143
+ ')
144
+ end
145
+
146
+ expect(remove_indent(cause.ascii_tree)).to eq(remove_indent(%q(
147
+ Expected one of [(LINE EOL){1, }, LINE] at line 1 char 1.
148
+ |- Extra input after last repetition at line 7 char 11.
149
+ | `- Failed to match sequence (LINE EOL) at line 7 char 11.
150
+ | `- Failed to match sequence (SPACE? [\n\r]{1, } SPACE?) at line 7 char 11.
151
+ | `- Expected at least 1 of [\n\r] at line 7 char 11.
152
+ | `- Failed to match [\n\r] at line 7 char 11.
153
+ `- Don't know what to do with "\n " at line 1 char 2.).strip))
154
+ end
155
+ end
156
+
157
+ class BLanguage < Parslet::Parser
158
+ root :expression
159
+ rule(:expression) { b.as(:one) >> b.as(:two) }
160
+ rule(:b) { str('b') }
161
+ end
162
+ describe BLanguage do
163
+ it "parses 'bb'" do
164
+ expect(subject).to parse('bb').as(one: 'b', two: 'b')
165
+ end
166
+
167
+ it 'transforms with binding constraint' do
168
+ transform = Parslet::Transform.new do |t|
169
+ t.rule(one: simple(:b), two: simple(:b)) { :ok }
170
+ end
171
+ expect(transform.apply(subject.parse('bb'))).to eq(:ok)
172
+ end
173
+ end
174
+
175
+ class UnicodeLanguage < Parslet::Parser
176
+ root :gobble
177
+ rule(:gobble) { any.repeat }
178
+ end
179
+ describe UnicodeLanguage do
180
+ it 'parses UTF-8 strings' do
181
+ expect(subject).to parse('éèäöü').as('éèäöü')
182
+ expect(subject).to parse('RubyKaigi2009のテーマは、「変わる/変える」です。 前回の').as('RubyKaigi2009のテーマは、「変わる/変える」です。 前回の')
183
+ end
184
+ end
185
+
186
+ class UnicodeSentenceLanguage < Parslet::Parser
187
+ rule(:sentence) { (match('[^。]').repeat(1) >> str('。')).as(:sentence) }
188
+ rule(:sentences) { sentence.repeat }
189
+ root(:sentences)
190
+ end
191
+ describe UnicodeSentenceLanguage do
192
+ let(:string) do
193
+ 'RubyKaigi2009のテーマは、「変わる/変える」です。 前回の' +
194
+ 'RubyKaigi2008のテーマであった「多様性」の言葉の通り、 ' +
195
+ '2008年はRubyそのものに関しても、またRubyの活躍する舞台に関しても、 ' +
196
+ 'ますます多様化が進みつつあります。RubyKaigi2008は、そのような ' +
197
+ 'Rubyの生態系をあらためて認識する場となりました。 しかし、' +
198
+ 'こうした多様化が進む中、異なる者同士が単純に距離を 置いたままでは、' +
199
+ 'その違いを認識したところであまり意味がありません。 異なる実装、' +
200
+ '異なる思想、異なる背景といった、様々な多様性を理解しつつ、 ' +
201
+ 'すり合わせるべきものをすり合わせ、変えていくべきところを ' +
202
+ '変えていくことが、豊かな未来へとつながる道に違いありません。'
203
+ end
204
+
205
+ it 'parses sentences' do
206
+ expect(subject).to parse(string)
207
+ end
208
+ end
209
+
210
+ class TwoCharLanguage < Parslet::Parser
211
+ root :twochar
212
+ rule(:twochar) { any >> str('2') }
213
+ end
214
+ describe TwoCharLanguage do
215
+ def di(s)
216
+ s.strip.to_s.lines.map { |l| l.chomp.strip }.join("\n")
217
+ end
218
+
219
+ it 'raises an error' do
220
+ error = catch_failed_parse do
221
+ subject.parse('123')
222
+ end
223
+ expect(di(error.ascii_tree)).to eq(di(%q(
224
+ Failed to match sequence (. '2') at line 1 char 2.
225
+ `- Don't know what to do with "3" at line 1 char 3.
226
+ )))
227
+ end
228
+ end
229
+
230
+ # Issue #68: Extra input reporting, written by jmettraux
231
+ class RepetitionParser < Parslet::Parser
232
+ rule(:nl) { match('[\s]').repeat(1) }
233
+ rule(:nl?) { nl.maybe }
234
+ rule(:sp) { str(' ').repeat(1) }
235
+ rule(:sp?) { str(' ').repeat(0) }
236
+ rule(:line) { sp >> str('line') }
237
+ rule(:body) { ((line | block) >> nl).repeat(0) }
238
+ rule(:block) do
239
+ sp? >> str('begin') >> sp >> match('[a-z]') >> nl >>
240
+ body >> sp? >> str('end')
241
+ end
242
+ rule(:blocks) { nl? >> block >> (nl >> block).repeat(0) >> nl? }
243
+
244
+ root(:blocks)
245
+ end
246
+ describe RepetitionParser do
247
+ def di(s)
248
+ s.strip.to_s.lines.map { |l| l.chomp.strip }.join("\n")
249
+ end
250
+
251
+ it 'parses a block' do
252
+ subject.parse('
253
+ begin a
254
+ end
255
+ ')
256
+ end
257
+
258
+ it 'parses nested blocks' do
259
+ subject.parse('
260
+ begin a
261
+ begin b
262
+ end
263
+ end
264
+ ')
265
+ end
266
+
267
+ it 'parses successive blocks' do
268
+ subject.parse('
269
+ begin a
270
+ end
271
+ begin b
272
+ end
273
+ ')
274
+ end
275
+
276
+ it 'fails gracefully on a missing end' do
277
+ error = catch_failed_parse do
278
+ subject.parse('
279
+ begin a
280
+ begin b
281
+ end
282
+ ')
283
+ end
284
+
285
+ expect(di(error.ascii_tree)).to eq(di("
286
+ Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 2 char 11.
287
+ `- Failed to match sequence (SP? 'begin' SP [a-z] NL BODY SP? 'end') at line 5 char 9.
288
+ `- Premature end of input at line 5 char 9.
289
+ "))
290
+ end
291
+
292
+ it 'fails gracefully on a missing end (2)' do
293
+ error = catch_failed_parse do
294
+ subject.parse('
295
+ begin a
296
+ end
297
+ begin b
298
+ begin c
299
+ end
300
+ ')
301
+ end
302
+
303
+ expect(di(error.ascii_tree)).to eq(di(%q(
304
+ Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 3 char 14.
305
+ `- Don't know what to do with "begin b\n " at line 4 char 11.
306
+ )))
307
+ end
308
+
309
+ it 'fails gracefully on a missing end (deepest reporter)' do
310
+ error = catch_failed_parse do
311
+ subject.parse('
312
+ begin a
313
+ end
314
+ begin b
315
+ begin c
316
+ li
317
+ end
318
+ end
319
+ ',
320
+ reporter: Parslet::ErrorReporter::Deepest.new)
321
+ end
322
+
323
+ expect(di(error.ascii_tree)).to eq(di(%q(
324
+ Failed to match sequence (NL? BLOCK (NL BLOCK){0, } NL?) at line 3 char 16.
325
+ `- Expected "end", but got "li\n" at line 6 char 17.
326
+ )))
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Tree output' do
4
+ extend Parslet
5
+
6
+ def self.hash_examples(h)
7
+ h.each do |atom, expected|
8
+ it "converts #{atom} to #{expected.inspect}" do
9
+ expect(atom.parse(input)).to eq(expected)
10
+ end
11
+ end
12
+ end
13
+
14
+ context 'when parsing the empty string' do
15
+ let(:input) { '' }
16
+
17
+ hash_examples(
18
+ # No naming
19
+ str('a').maybe => '',
20
+ str('a').repeat => '',
21
+
22
+ # Named contents: maybe yields nil
23
+ str('a').maybe.as(:f) => { f: nil },
24
+ str('a').repeat.as(:f) => { f: [] },
25
+
26
+ # Contents that aren't simple strings
27
+ (str('a') >> str('b')).maybe.as(:f) => { f: nil },
28
+ (str('a') >> str('b')).repeat.as(:f) => { f: [] },
29
+
30
+ # The other way around: Contents would be tagged, but nil result isn't
31
+ (str('a') >> str('b')).as(:f).maybe => '',
32
+ (str('a') >> str('b')).as(:f).repeat => '',
33
+ )
34
+ end
35
+
36
+ context "when parsing 'aa'" do
37
+ let(:input) { 'aa' }
38
+
39
+ hash_examples(
40
+ # since they're not named, repetitions get merged together.
41
+ str('a').as(:a).repeat >> str('a').as(:a).repeat => [{ a: 'a' }, { a: 'a' }],
42
+ )
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Unconsumed input:" do
4
+ class RepeatingBlockParser < Parslet::Parser
5
+ root :expressions
6
+ rule(:expressions) { expression.repeat }
7
+ rule(:expression) { str('(') >> aab >> str(')') }
8
+ rule(:aab) { str('a').repeat(1) >> str('b') }
9
+ end
10
+ describe RepeatingBlockParser do
11
+ let(:parser) { described_class.new }
12
+ it "throws annotated error" do
13
+ error = catch_failed_parse { parser.parse('(aaac)') }
14
+ end
15
+ it "doesn't error out if prefix is true" do
16
+ expect {
17
+ parser.parse('(aaac)', :prefix => true)
18
+ }.not_to raise_error
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,257 @@
1
+ require 'spec_helper'
2
+ require_relative '../fixtures/examples/boolean_algebra'
3
+
4
+ RSpec.describe 'Boolean Algebra Parser Example' do
5
+ let(:parser) { MyParser.new }
6
+ let(:transformer) { Transformer.new }
7
+
8
+ describe MyParser do
9
+ describe '#var' do
10
+ it 'parses variable names' do
11
+ result = parser.var.parse('var1')
12
+ expect(result).to parse_as({ var: '1' })
13
+ end
14
+
15
+ it 'parses multi-digit variables' do
16
+ result = parser.var.parse('var123')
17
+ expect(result).to parse_as({ var: '123' })
18
+ end
19
+
20
+ it 'handles trailing space' do
21
+ result = parser.var.parse('var1 ')
22
+ expect(result).to parse_as({ var: '1' })
23
+ end
24
+ end
25
+
26
+ describe '#primary' do
27
+ it 'parses variables as primary expressions' do
28
+ result = parser.primary.parse('var1')
29
+ expect(result).to parse_as({ var: '1' })
30
+ end
31
+
32
+ it 'parses parenthesized expressions' do
33
+ result = parser.primary.parse('(var1)')
34
+ expect(result).to parse_as({ var: '1' })
35
+ end
36
+
37
+ it 'parses complex parenthesized expressions' do
38
+ result = parser.primary.parse('(var1 or var2)')
39
+ expected = {
40
+ or: {
41
+ left: { var: '1' },
42
+ right: { var: '2' }
43
+ }
44
+ }
45
+ expect(result).to parse_as(expected)
46
+ end
47
+ end
48
+
49
+ describe '#and_operation' do
50
+ it 'parses simple AND operations' do
51
+ result = parser.and_operation.parse('var1 and var2')
52
+ expected = {
53
+ and: {
54
+ left: { var: '1' },
55
+ right: { var: '2' }
56
+ }
57
+ }
58
+ expect(result).to parse_as(expected)
59
+ end
60
+
61
+ it 'parses chained AND operations (right-associative)' do
62
+ result = parser.and_operation.parse('var1 and var2 and var3')
63
+ expected = {
64
+ and: {
65
+ left: { var: '1' },
66
+ right: {
67
+ and: {
68
+ left: { var: '2' },
69
+ right: { var: '3' }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ expect(result).to parse_as(expected)
75
+ end
76
+
77
+ it 'parses single variables' do
78
+ result = parser.and_operation.parse('var1')
79
+ expect(result).to parse_as({ var: '1' })
80
+ end
81
+ end
82
+
83
+ describe '#or_operation' do
84
+ it 'parses simple OR operations' do
85
+ result = parser.or_operation.parse('var1 or var2')
86
+ expected = {
87
+ or: {
88
+ left: { var: '1' },
89
+ right: { var: '2' }
90
+ }
91
+ }
92
+ expect(result).to parse_as(expected)
93
+ end
94
+
95
+ it 'parses chained OR operations (right-associative)' do
96
+ result = parser.or_operation.parse('var1 or var2 or var3')
97
+ expected = {
98
+ or: {
99
+ left: { var: '1' },
100
+ right: {
101
+ or: {
102
+ left: { var: '2' },
103
+ right: { var: '3' }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ expect(result).to parse_as(expected)
109
+ end
110
+
111
+ it 'handles operator precedence (AND binds tighter than OR)' do
112
+ result = parser.or_operation.parse('var1 or var2 and var3')
113
+ expected = {
114
+ or: {
115
+ left: { var: '1' },
116
+ right: {
117
+ and: {
118
+ left: { var: '2' },
119
+ right: { var: '3' }
120
+ }
121
+ }
122
+ }
123
+ }
124
+ expect(result).to parse_as(expected)
125
+ end
126
+ end
127
+
128
+ describe 'root parser (or_operation)' do
129
+ it 'parses the main example: var1 and (var2 or var3)' do
130
+ input = "var1 and (var2 or var3)"
131
+ result = parser.parse(input)
132
+ expected = {
133
+ and: {
134
+ left: { var: '1' },
135
+ right: {
136
+ or: {
137
+ left: { var: '2' },
138
+ right: { var: '3' }
139
+ }
140
+ }
141
+ }
142
+ }
143
+ expect(result).to parse_as(expected)
144
+ end
145
+
146
+ it 'handles complex expressions with precedence and parentheses' do
147
+ result = parser.parse('(var1 and var2) or var3')
148
+ expected = {
149
+ or: {
150
+ left: {
151
+ and: {
152
+ left: { var: '1' },
153
+ right: { var: '2' }
154
+ }
155
+ },
156
+ right: { var: '3' }
157
+ }
158
+ }
159
+ expect(result).to parse_as(expected)
160
+ end
161
+ end
162
+ end
163
+
164
+ describe Transformer do
165
+ it 'transforms variables to DNF arrays' do
166
+ input = { var: Parslet::Slice.new(Parslet::Position.new("var1", 3), "1") }
167
+ result = transformer.apply(input)
168
+ expect(result).to eq([["1"]])
169
+ end
170
+
171
+ it 'transforms OR operations by concatenating arrays' do
172
+ input = {
173
+ or: {
174
+ left: [["1"]],
175
+ right: [["2"]]
176
+ }
177
+ }
178
+ result = transformer.apply(input)
179
+ expect(result).to eq([["1"], ["2"]])
180
+ end
181
+
182
+ it 'transforms AND operations by creating cartesian product' do
183
+ input = {
184
+ and: {
185
+ left: [["1"]],
186
+ right: [["2"]]
187
+ }
188
+ }
189
+ result = transformer.apply(input)
190
+ expect(result).to eq([["1", "2"]])
191
+ end
192
+
193
+ it 'handles complex AND operations with multiple terms' do
194
+ input = {
195
+ and: {
196
+ left: [["1"], ["2"]],
197
+ right: [["3"]]
198
+ }
199
+ }
200
+ result = transformer.apply(input)
201
+ expect(result).to eq([["1", "3"], ["2", "3"]])
202
+ end
203
+
204
+ it 'handles complex OR operations with AND sub-expressions' do
205
+ input = {
206
+ and: {
207
+ left: [["1"]],
208
+ right: [["2"], ["3"]]
209
+ }
210
+ }
211
+ result = transformer.apply(input)
212
+ expect(result).to eq([["1", "2"], ["1", "3"]])
213
+ end
214
+ end
215
+
216
+ describe 'integration test' do
217
+ it 'processes the main example correctly: var1 and (var2 or var3)' do
218
+ input = "var1 and (var2 or var3)"
219
+ tree = parser.parse(input)
220
+ result = transformer.apply(tree)
221
+
222
+ # Expected result: [["1", "2"], ["1", "3"]]
223
+ # This represents: (var1 AND var2) OR (var1 AND var3)
224
+ expect(result).to eq([["1", "2"], ["1", "3"]])
225
+ end
226
+
227
+ it 'handles simple OR expressions' do
228
+ input = "var1 or var2"
229
+ tree = parser.parse(input)
230
+ result = transformer.apply(tree)
231
+ expect(result).to eq([["1"], ["2"]])
232
+ end
233
+
234
+ it 'handles simple AND expressions' do
235
+ input = "var1 and var2"
236
+ tree = parser.parse(input)
237
+ result = transformer.apply(tree)
238
+ expect(result).to eq([["1", "2"]])
239
+ end
240
+
241
+ it 'handles complex expressions with multiple operators' do
242
+ input = "(var1 or var2) and (var3 or var4)"
243
+ tree = parser.parse(input)
244
+ result = transformer.apply(tree)
245
+ # Should produce: [["1", "3"], ["1", "4"], ["2", "3"], ["2", "4"]]
246
+ expect(result).to eq([["1", "3"], ["1", "4"], ["2", "3"], ["2", "4"]])
247
+ end
248
+
249
+ it 'produces the expected output from the example file' do
250
+ # This matches the exact output shown in the example file
251
+ input = "var1 and (var2 or var3)"
252
+ tree = parser.parse(input)
253
+ result = transformer.apply(tree)
254
+ expect(result).to eq([["1", "2"], ["1", "3"]])
255
+ end
256
+ end
257
+ end