parsby 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3313d5b66907eba101433827e5cff5823b5736d1
4
+ data.tar.gz: d4aa6d380a96be184afd6344627679ce5cd6ca27
5
+ SHA512:
6
+ metadata.gz: 16680e582808dcbaec3f5c9d98bdbf477359d65a6448d0e48a6c3cf173d575b399b14cbeb4ccdbd754d3be4fdcd865a23a85707058c9e6b7a140f6c61598668d
7
+ data.tar.gz: b8e78360be81c54cb29a8c5ebad112488c2b4f90930bd56d8b029361192bfd0530914df8f2789031f4cf35adf5d60f3e9004113d8d04ac2e20127ed45ac75f1a
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ /README.md.html
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1 @@
1
+ 2.4.1
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.4.1
7
+ before_install: gem install bundler -v 1.17.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in parsby.gemspec
6
+ gemspec
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ parsby (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.2)
10
+ diff-lcs (1.3)
11
+ method_source (1.0.0)
12
+ pry (0.13.1)
13
+ coderay (~> 1.1)
14
+ method_source (~> 1.0)
15
+ rake (10.5.0)
16
+ rspec (3.9.0)
17
+ rspec-core (~> 3.9.0)
18
+ rspec-expectations (~> 3.9.0)
19
+ rspec-mocks (~> 3.9.0)
20
+ rspec-core (3.9.2)
21
+ rspec-support (~> 3.9.3)
22
+ rspec-expectations (3.9.2)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.9.0)
25
+ rspec-mocks (3.9.1)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.9.0)
28
+ rspec-support (3.9.3)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bundler (~> 1.17)
35
+ parsby!
36
+ pry
37
+ rake (~> 10.0)
38
+ rspec (~> 3.0)
39
+
40
+ BUNDLED WITH
41
+ 1.17.1
@@ -0,0 +1,607 @@
1
+ # Parsby
2
+
3
+ Parser combinator library for Ruby, based on Haskell's Parsec.
4
+
5
+ - [Installation](#installation)
6
+ - [Examples](#examples)
7
+ - [Introduction](#introduction)
8
+ - [Defining combinators](#defining-combinators)
9
+ - [`Parsby.new`](#parsbynew)
10
+ - [Defining parsers as modules](#defining-parsers-as-modules)
11
+ - [`ExpectationFailed`](#expectationfailed)
12
+ - [Cleaning up the parse tree for the trace](#cleaning-up-the-parse-tree-for-the-trace)
13
+ - [`splicer.start` combinator](#splicerstart-combinator)
14
+ - [Recursive parsers with `lazy`](#recursive-parsers-with-lazy)
15
+ - [Parsing left-recursive languages with `reduce` combinator](#parsing-leftrecursive-languages-with-reduce-combinator)
16
+ - [Parsing from a string, a file, a pipe, a socket, ...](#parsing-from-a-string-a-file-a-pipe-a-socket-)
17
+ - [Comparing with Haskell's Parsec](#comparing-with-haskells-parsec)
18
+ - [Development](#development)
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ ```ruby
25
+ gem 'parsby'
26
+ ```
27
+
28
+ And then execute:
29
+
30
+ $ bundle
31
+
32
+ Or install it yourself as:
33
+
34
+ $ gem install parsby
35
+
36
+ ## Examples
37
+
38
+ If you'd like to jump right into example parsers that use this library,
39
+ there's a few in this source:
40
+
41
+ - [CSV (RFC 4180 compliant)](lib/parsby/example/csv_parser.rb)
42
+ - [JSON](lib/parsby/example/json_parser.rb)
43
+ - [Lisp](lib/parsby/example/lisp_parser.rb)
44
+ - [Arithmetic expressions](lib/parsby/example/arithmetic_parser.rb)
45
+
46
+ ## Introduction
47
+
48
+ This is a library used to define parsers by declaratively describing a
49
+ syntax using what's commonly referred to as combinators. Parser combinators
50
+ are functions that take parsers as inputs and/or return parsers as outputs,
51
+ i.e. they *combine* parsers into new parsers.
52
+
53
+ As an example, `between` is a combinator with 3 parameters: a parser for
54
+ what's to the left, one for what's to the right, and lastly one for what's
55
+ in-between them, and it returns a parser that, after parsing, returns the
56
+ result of the in-between parser:
57
+
58
+ ```ruby
59
+ between(lit("<"), lit(">"), decimal).parse "<100>"
60
+ #=> 100
61
+ ```
62
+
63
+ `lit` is a combinator that takes a string and returns a parser for
64
+ `lit`erally that string.
65
+
66
+ ## Defining combinators
67
+
68
+ If you look at the examples in this source, you'll notice that all
69
+ combinators are defined with `define_combinator`. Strictly speaking, it's
70
+ not necessary to use that to define combinators. You can do it with
71
+ variable assignment or `def` syntax. Nevertheless, `define_combinator` is
72
+ preferred because it automates the assignment of a label to the combinator.
73
+ Consider this example:
74
+
75
+ ```ruby
76
+ define_combinator :between do |left, right, p|
77
+ left > p < right
78
+ end
79
+
80
+ between(lit("<"), lit(">"), lit("foo")).label
81
+ #=> 'between(lit("<"), lit(">"), lit("foo"))'
82
+ ```
83
+
84
+ If we use `def` instead of `define_combinator`, then the label would be
85
+ that of its definition. In the following case, it would be that assigned by
86
+ `<`.
87
+
88
+ ```ruby
89
+ def between(left, right, p)
90
+ left > p < right
91
+ end
92
+
93
+ between(lit("<"), lit(">"), lit("foo")).label
94
+ => '((lit("<") > lit("foo")) < lit(">"))'
95
+ ```
96
+
97
+ If we're to wrap that parser in a new one, then the label would be simply
98
+ unknown.
99
+
100
+ ```ruby
101
+ def between(left, right, p)
102
+ Parsby.new {|c| (left > p < right).parse c }
103
+ end
104
+
105
+ between(lit("<"), lit(">"), lit("foo")).label.to_s
106
+ => "<unknown>"
107
+ ```
108
+
109
+ ## `Parsby.new`
110
+
111
+ Now, normally one ought to be able to define parsers using just
112
+ combinators, but there are times when one might need more control. For
113
+ those times, the most raw way to define a parser is using `Parsby.new`.
114
+
115
+ Here's `lit` as an example:
116
+
117
+ ```ruby
118
+ define_combinator :lit, wrap: false do |e, case_sensitive: true|
119
+ Parsby.new do |c|
120
+ a = c.bio.read e.length
121
+ if case_sensitive ? a == e : a.to_s.downcase == e.downcase
122
+ a
123
+ else
124
+ raise ExpectationFailed.new c
125
+ end
126
+ end
127
+ end
128
+ ```
129
+
130
+ It takes a string argument for what it `e`xpects to parse, and returns what
131
+ was `a`ctually parsed if it matches the expectation.
132
+
133
+ The block parameter `c` is a `Parsby::Context`. `c.bio` holds a
134
+ `Parsby::BackedIO`. The `parse` method of `Parsby` objects accepts ideally
135
+ any `IO` (and `String`s, which it turns into `StringIO`) and then wraps
136
+ them with `BackedIO` to give the `IO` the ability to backtrack.
137
+
138
+ ## Defining parsers as modules
139
+
140
+ The typical pattern I use is something like this:
141
+
142
+ ```ruby
143
+ module FoobarParser
144
+ include Parsby::Combinators
145
+ extend self
146
+
147
+ # Entrypoint nicety
148
+ def parse(s)
149
+ foobar.parse s
150
+ end
151
+
152
+ define_combinator :foobar do
153
+ foo + bar
154
+ end
155
+
156
+ define_combinator :foo do
157
+ lit("foo")
158
+ end
159
+
160
+ define_combinator :bar do
161
+ lit("bar")
162
+ end
163
+ end
164
+ ```
165
+
166
+ From that, you can use it directly as:
167
+
168
+ ```ruby
169
+ FoobarParser.parse "foobar"
170
+ #=> "foobar"
171
+ FoobarParser.foo.parse "foo"
172
+ #=> "foo"
173
+ ```
174
+
175
+ Being able to use subparsers directly is useful for when you want to e.g.
176
+ parse JSON array, instead of any JSON value.
177
+
178
+ Writing the parser as a module like that also makes it easy to make a new
179
+ parser based on it:
180
+
181
+ ```ruby
182
+ module FoobarbazParser
183
+ include FoobarParser
184
+ extend self
185
+
186
+ def parse(s)
187
+ foobarbaz.parse s
188
+ end
189
+
190
+ define_combinator :foobarbaz do
191
+ foobar + baz
192
+ end
193
+
194
+ define_combinator :baz do
195
+ lit("baz")
196
+ end
197
+ end
198
+ ```
199
+
200
+ You can also define such a module to hold your own project's combinators to
201
+ use in multiple parsers.
202
+
203
+ ## `ExpectationFailed`
204
+
205
+ Here's an example of an error, when parsing fails:
206
+
207
+ ```
208
+ pry(main)> Parsby::Example::LispParser.sexp.parse "(foo `(foo ,bar) 2.3 . . nil)"
209
+ Parsby::ExpectationFailed: line 1:
210
+ (foo `(foo ,bar) 2.3 . . nil)
211
+ | * failure: char_in("([")
212
+ | * failure: list
213
+ | *| failure: symbol
214
+ | *|| failure: nil
215
+ | *||| failure: string
216
+ | *|||| failure: number
217
+ \\\||
218
+ | *| failure: atom
219
+ | *|| failure: abbrev
220
+ \\|
221
+ | * failure: sexp
222
+ V *| success: lit(".")
223
+ \-/ *|| success: sexp
224
+ \---------/ *||| success: sexp
225
+ \-/ *|||| success: sexp
226
+ V *||||| success: char_in("([")
227
+ \\\\\|
228
+ | * failure: list
229
+ | * failure: sexp
230
+ ```
231
+
232
+ As can be seen, Parsby manages a tree structure representing parsers and
233
+ their subparsers, with the information of where a particular parser began
234
+ parsing, where it ended, whether it succeeded or failed, and the label of
235
+ the parser.
236
+
237
+ It might be worth mentioning that when debugging a parser from an
238
+ unexpected `ExpectationFailed` error, the backtrace isn't really useful.
239
+ That's because the backtrace points to the code involved in parsing, not
240
+ the code involved in constructing the parsers, which succeeded, but is
241
+ where the problem typically lies. The tree-looking exception message above
242
+ is meant to somewhat substitute the utility of the backtrace in these
243
+ cases.
244
+
245
+ Relating to that, the right-most text are the labels of the corresponding
246
+ parsers. I find that labels that resemble the source code are quite useful,
247
+ just like the code location descriptions that appear right-most in
248
+ backtraces. It's because of this that I consider the use of
249
+ `define_combinator` more preferable than using `def` and explicitly
250
+ assigning labels.
251
+
252
+ ### Cleaning up the parse tree for the trace
253
+
254
+ If you look at the source of the example lisp parser, you might note that
255
+ there are a lot more parsers in between those shown in the tree above.
256
+ `sexp` is not a direct child of `list`, for example, despite it appearing
257
+ as so. There are at least 6 ancestors/descendant parsers between `list` and
258
+ `sexp`. It'd be very much pointless to show them all. They convey little
259
+ additional information and their labels are very verbose.
260
+
261
+ ### `splicer.start` combinator
262
+
263
+ The reason why they don't appear is because `splicer` is used to make the
264
+ tree look a little cleaner.
265
+
266
+ The name comes from JS's `Array.prototype.splice`, to which you can give a
267
+ starting position, and a count specifying the end, and it'll remove the
268
+ specified elements from an Array. We use `splicer` likewise, only it works
269
+ on parse trees. To show an example, here's a simplified definition of
270
+ `choice`:
271
+
272
+ ```ruby
273
+ define_combinator :choice do |*ps|
274
+ ps = ps.flatten
275
+
276
+ ps.reduce(unparseable) do |a, p|
277
+ a | p
278
+ end
279
+ end
280
+ ```
281
+
282
+ Let's fail it:
283
+
284
+ ```
285
+ pry(main)> choice(lit("foo"), lit("bar"), lit("baz")).parse "qux"
286
+ Parsby::ExpectationFailed: line 1:
287
+ qux
288
+ \-/ * failure: lit("baz")
289
+ \-/ *| failure: lit("bar")
290
+ \-/ *|| failure: lit("foo")
291
+ | *||| failure: unparseable
292
+ \|||
293
+ | *|| failure: (unparseable | lit("foo"))
294
+ \||
295
+ | *| failure: ((unparseable | lit("foo")) | lit("bar"))
296
+ \|
297
+ | * failure: (((unparseable | lit("foo")) | lit("bar")) | lit("baz"))
298
+ | * failure: choice(lit("foo"), lit("bar"), lit("baz"))
299
+ ```
300
+
301
+ Those parser intermediaries that use `|` aren't really making things any
302
+ clearer. Let's use `splicer` to remove those:
303
+
304
+ ```ruby
305
+ define_combinator :choice do |*ps|
306
+ ps = ps.flatten
307
+
308
+ splicer.start do |m|
309
+ ps.reduce(unparseable) do |a, p|
310
+ a | m.end(p)
311
+ end
312
+ end
313
+ end
314
+ ```
315
+
316
+ Let's fail it, again:
317
+
318
+ ```
319
+ pry(main)> choice(lit("foo"), lit("bar"), lit("baz")).parse "qux"
320
+ Parsby::ExpectationFailed: line 1:
321
+ qux
322
+ \-/ * failure: lit("baz")
323
+ \-/ *| failure: lit("bar")
324
+ \-/ *|| failure: lit("foo")
325
+ \\|
326
+ | * failure: splicer.start((((unparseable | splicer.end(lit("foo"))) | splicer.end(lit("bar"))) | splicer.end(lit("baz"))))
327
+ | * failure: choice(lit("foo"), lit("bar"), lit("baz"))
328
+ ```
329
+
330
+ Now, the only issue left is that `define_combinator` wraps the result of
331
+ the parser in another parser. Let's disable that wrapping by passing `wrap:
332
+ false` to it:
333
+
334
+ ```ruby
335
+ define_combinator :choice, wrap: false do |*ps|
336
+ ps = ps.flatten
337
+
338
+ splicer.start do |m|
339
+ ps.reduce(unparseable) do |a, p|
340
+ a | m.end(p)
341
+ end
342
+ end
343
+ end
344
+ ```
345
+
346
+ Let's fail it, again:
347
+
348
+ ```
349
+ pry(main)> choice(lit("foo"), lit("bar"), lit("baz")).parse "qux"
350
+ Parsby::ExpectationFailed: line 1:
351
+ qux
352
+ \-/ * failure: lit("baz")
353
+ \-/ *| failure: lit("bar")
354
+ \-/ *|| failure: lit("foo")
355
+ \\|
356
+ | * failure: choice(lit("foo"), lit("bar"), lit("baz"))
357
+ ```
358
+
359
+ ## Recursive parsers with `lazy`
360
+
361
+ If we try to define a recursive parser using combinators like so:
362
+
363
+ ```ruby
364
+ define_combinator :value do
365
+ list | lit("foo")
366
+ end
367
+
368
+ define_combinator :list do
369
+ between(lit("["), lit("]"), sep_by(lit(","), spaced(value)))
370
+ end
371
+
372
+ value
373
+ #=> SystemStackError: stack level too deep
374
+ ```
375
+
376
+ We get a stack overflow.
377
+
378
+ This isn't a problem in Haskell because the language evaluates lazily by
379
+ default. This allows it to define recursive parsers without even thinking
380
+ about it.
381
+
382
+ In Ruby's case, we need to be explicit about our laziness. For that,
383
+ there's `lazy`. We just need to wrap one of the expressions in the
384
+ recursive loop with it. It could be the `value` call in `list`; it could be
385
+ `list` call in `value`; it could be the whole of `value`. It really doesn't
386
+ matter.
387
+
388
+ ```ruby
389
+ define_combinator :value do
390
+ lazy { list | lit("foo") }
391
+ end
392
+
393
+ define_combinator :list do
394
+ between(lit("["), lit("]"), sep_by(lit(","), spaced(value)))
395
+ end
396
+
397
+ value.parse "[[[[foo, foo]]]]"
398
+ #=> [[[["foo", "foo"]]]]
399
+ ```
400
+
401
+ ## Parsing left-recursive languages with `reduce` combinator
402
+
403
+ Here's a little arithmetic parser based on the
404
+ `Parsby::Example::ArithmeticParser`:
405
+
406
+ ```ruby
407
+ define_combinator :div_op {|left, right| group(left, spaced(lit("/")), right) }
408
+ define_combinator :mul_op {|left, right| group(left, spaced(lit("*")), right) }
409
+ define_combinator :add_op {|left, right| group(left, spaced(lit("+")), right) }
410
+ define_combinator :sub_op {|left, right| group(left, spaced(lit("-")), right) }
411
+
412
+ def scope(x, &b)
413
+ b.call x
414
+ end
415
+
416
+ define_combinator :expr do
417
+ lazy do
418
+ e = decimal
419
+
420
+ # hpe -- higher precedence level expression
421
+ # spe -- same precedence level expression
422
+
423
+ e = scope e do |hpe|
424
+ recursive do |spe|
425
+ choice(
426
+ mul_op(hpe, spe),
427
+ div_op(hpe, spe),
428
+ hpe,
429
+ )
430
+ end
431
+ end
432
+
433
+ e = scope e do |hpe|
434
+ recursive do |spe|
435
+ choice(
436
+ add_op(hpe, spe),
437
+ sub_op(hpe, spe),
438
+ hpe,
439
+ )
440
+ end
441
+ end
442
+ end
443
+ end
444
+
445
+ expr.parse "5 - 4 - 3"
446
+ #=> [5, "-", [4, "-", 3]]
447
+ ```
448
+
449
+ Now, that's incorrectly right-associative because we made the
450
+ precedence-level parsers right-recursive. See how the block parameter of
451
+ `recursive` is used for the right operands and not the left ones?
452
+
453
+ Let's fix that by switching the parsers used for the operands:
454
+
455
+ ```ruby
456
+ define_combinator :expr do
457
+ lazy do
458
+ e = decimal
459
+
460
+ # hpe -- higher precedence level expression
461
+ # spe -- same precedence level expression
462
+
463
+ e = scope e do |hpe|
464
+ recursive do |spe|
465
+ choice(
466
+ mul_op(spe, hpe),
467
+ div_op(spe, hpe),
468
+ hpe,
469
+ )
470
+ end
471
+ end
472
+
473
+ e = scope e do |hpe|
474
+ recursive do |spe|
475
+ choice(
476
+ add_op(spe, hpe),
477
+ sub_op(spe, hpe),
478
+ hpe,
479
+ )
480
+ end
481
+ end
482
+ end
483
+ end
484
+
485
+ expr.parse "5 - 4 - 3"
486
+ # ...
487
+ ```
488
+
489
+ If you ran this, it might take a while, but eventually you'll have a bunch
490
+ of `SystemStackError: stack level too deep` errors.
491
+
492
+ What's happening is that e.g. while trying to check whether the expression
493
+ is a subtraction, it needs to first resolve the left operand, and as part
494
+ of that it needs to check whether *that's* a subtraction, and so on and so
495
+ forth. In other words, this causes infinite recursion. It can't even read a
496
+ single character of the input because of this.
497
+
498
+ Our problem is that we're parsing top-down. We're trying to understand what
499
+ the whole thing is before looking at the parts. We need to parse bottom-up.
500
+ Successfully parse a small piece, then figure out what the whole thing is
501
+ as we keep reading. To do that while keeping our definitions declarative,
502
+ we can use the `reduce` combinator (in combination with `pure`):
503
+
504
+ ```ruby
505
+ define_combinator :expr do
506
+ lazy do
507
+ e = decimal
508
+
509
+ # hpe -- higher precedence level expression
510
+ # spe -- same precedence level expression
511
+
512
+ e = scope e do |hpe|
513
+ reduce hpe do |left_result|
514
+ choice(
515
+ mul_op(pure(left_result), hpe),
516
+ div_op(pure(left_result), hpe),
517
+ )
518
+ end
519
+ end
520
+
521
+ e = scope e do |hpe|
522
+ reduce hpe do |left_result|
523
+ choice(
524
+ add_op(pure(left_result), hpe),
525
+ sub_op(pure(left_result), hpe),
526
+ )
527
+ end
528
+ end
529
+ end
530
+ end
531
+
532
+ expr.parse "5 - 4 - 3"
533
+ #=> [[5, "-", 4], "-", 3]
534
+ ```
535
+
536
+ `reduce` starts parsing with its argument, in this case `hpe`, then passes
537
+ the result to the block, which uses it for the resolved left operand.
538
+ `reduce` then parses with the parser returned by the block and passes the
539
+ result again to the block, and so on and so forth until parsing fails,
540
+ returning the result of the last successful parse.
541
+
542
+ In effect, we're parsing left operands bottom-up and right operands
543
+ top-down.
544
+
545
+ ## Parsing from a string, a file, a pipe, a socket, ...
546
+
547
+ Any `IO` ought to work (unit tests currently have only checked pipes,
548
+ though). When you pass a string to `Parsby#parse` it wraps it with
549
+ `StringIO` before using it.
550
+
551
+ ## Comparing with Haskell's Parsec
552
+
553
+ If you're already familiar with Parsec, here are some similarities:
554
+
555
+ ```ruby
556
+ # Parsby # -- Parsec
557
+ #
558
+ lit("foo") # string "foo"
559
+ #
560
+ foo | bar # foo <|> bar
561
+ #
562
+ pure "foo" # pure "foo"
563
+ #
564
+ foo.then {|x| bar x } # foo >>= \x -> bar x
565
+ #
566
+ foobar = Parsby.new do |c| # foobar = do
567
+ x = foo.parse c # x <- foo
568
+ bar(x).parse c # bar x
569
+ end #
570
+ #
571
+ lit("(") > foo < lit(")") # string "(" *> foo <* string ")"
572
+ #
573
+ lit("5").fmap {|n| n.to_i + 1 } # fmap (\n -> read n + 1) (string "5")
574
+ #
575
+ group(x, y, z) # (,,) <$> x <*> y <*> z
576
+ #
577
+ group( #
578
+ w, #
579
+ group(x, y), #
580
+ z, #
581
+ ).fmap do |(wr, (xr, yr), zr)| #
582
+ Foo.new(wr, Bar.new(xr, yr), zr) # Foo <$> w <*> (Bar <$> x <*> y) <*> z
583
+ end #
584
+ #
585
+ # -- Means the same, but this
586
+ # -- raises an error in Haskell
587
+ # -- because it requires an
588
+ # -- infinite type [[[[...]]]]
589
+ recursive do |p| # fix $ \p ->
590
+ between(lit("("), lit(")"), # between (string "(") (string ")") $
591
+ single(p) | pure([]) # ((:[]) <$> p) <|> pure []
592
+ end #
593
+ end #
594
+ ```
595
+
596
+ ## Development
597
+
598
+ After checking out the repo, run `bin/setup` to install dependencies. Then,
599
+ run `rake spec` to run the tests. You can also run `bin/console` for an
600
+ interactive prompt that will allow you to experiment.
601
+
602
+ `bin/console` includes `Parsby::Combinators` into the top-level so the
603
+ combinators and `define_combinator` are available directly from the prompt.
604
+ It also defines `reload!` to quickly load changes made to the source.
605
+
606
+ To install this gem onto your local machine, run `bundle exec rake
607
+ install`.