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