birman 0.2.1 → 0.3.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.
data/README.md CHANGED
@@ -20,7 +20,7 @@ bundle add birman
20
20
  or in your Gemfile:
21
21
 
22
22
  ```ruby
23
- gem "birman", "~> 0.2.1"
23
+ gem "birman", "~> 0.3.0"
24
24
  ```
25
25
 
26
26
  ```ruby
@@ -77,8 +77,41 @@ end
77
77
  ```
78
78
 
79
79
  See `examples/calc.rb` for a complete small language with an
80
- interpreter, `examples/json.rb` for a full JSON parser (tested against
81
- `JSON.parse`), and `Grammar.to_ebnf` to print any grammar.
80
+ interpreter, `examples/leftcalc.rb` for its arithmetic written as left
81
+ recursion instead of `infix`, `examples/json.rb` for a full JSON parser (tested against
82
+ `JSON.parse`), `examples/toml.rb` for TOML 1.0 (tested against the
83
+ official toml-test suite), `examples/query.rb` for a GitHub-style
84
+ search query (lookahead, implicit AND, a custom error formatter) with
85
+ an evaluator that filters records and a compiler to SQL for
86
+ ActiveRecord's `where`, and `Grammar.to_ebnf` to print any grammar.
87
+
88
+ ## Why not a regex?
89
+
90
+ A Ruby regex can match nested input with `\g<name>`, but it can't
91
+ build the structure (a capture keeps only its last match), and it
92
+ can't say what's wrong when the match fails.
93
+
94
+ ```ruby
95
+ re = /\A(?<l>\[\s*(?:(?:\d+|\g<l>)(?:\s*,\s*(?:\d+|\g<l>))*)?\s*\])\z/
96
+ re.match?("[1, [2, 3], []]") # => true
97
+ re.match("[1, [2, 3], []]")[:l] # => "[1, [2, 3], []]" (just the string back)
98
+ re.match?("[1, [2, 3]") # => false (no hint why)
99
+
100
+ class Lists
101
+ include Birman::Grammar
102
+ include Birman::Rules::Whitespace
103
+ ignore :spacing
104
+ root :list
105
+ rule(:list) { seq("[", commit, only(sep_by(item, ",")), "]") }
106
+ rule(:item) { either(match(/\d+/).map(&:to_i), list) }
107
+ end
108
+
109
+ Lists.parse("[1, [2, 3], []]") # => Success([1, [2, 3], []])
110
+ puts Lists.parse("[1, [2, x], []]").message
111
+ # line 1, column 9: expected /\d+/ or "[", found "x"
112
+ # [1, [2, x], []]
113
+ # ^
114
+ ```
82
115
 
83
116
  ## Building blocks
84
117
 
@@ -94,15 +127,43 @@ interpreter, `examples/json.rb` for a full JSON parser (tested against
94
127
  | `infix(operand, "+", "-", node: BinOp)` | left-associative binary operators; `BinOp` needs `op`, `left`, `right` fields |
95
128
  | `parens(x)`, `between(o, c, x)` | wrapped |
96
129
  | `sep_by(x, ",")`, `sep_by1` | separated lists |
130
+ | `sep_by(x, ",", trailing: true)` | and one more `","` may end the list (not an empty one) |
131
+ | `match(re).captures` | a hash of `re`'s named groups (`{date: "1979-05-27", time: nil}`; `nil` for a group that didn't match) |
97
132
  | `.rep(min, max)`, `.maybe` | repetition, optional |
98
133
  | `.absent`, `.present` | negative / positive lookahead |
99
- | `.map { }` | transform the value |
134
+ | `.map(:to_i)` | call that method on the value |
135
+ | `.action(:name)` | pass the value to the grammar's method `name` |
136
+ | `.check(:name, "label")` | fail as "expected label" unless the grammar's method `name` accepts the value |
137
+ | `.means(v)` | the value is `v` (`lit("null").means(nil)`) |
138
+ | `.map { }` | transform the value with a block (prefer the above) |
100
139
  | `.label("name")` | "expected name" in errors |
101
140
  | `.quiet` | never mention in errors |
102
141
 
142
+ Rules can be left-recursive, written the way a textbook grammar writes
143
+ left-associative operators (see [Left recursion](#left-recursion)):
144
+
145
+ ```ruby
146
+ rule(:expr) { either(seq(expr.as(:left), either("+", "-").as(:op), term.as(:right)).to(BinOp), term) }
147
+ ```
148
+
149
+ `1 - 2 - 3` parses as `(1 - 2) - 3`, the same tree
150
+ `infix(term, "+", "-", node: BinOp)` builds.
151
+
152
+ A name used twice in one `seq` raises `ArgumentError`, and so does a
153
+ `seq` of named parts (or `match(re).captures`) inside another whose
154
+ value would drop it, even through `maybe`, `label`, `either` and the
155
+ like: list the parts in the outer `seq`, or name the inner one,
156
+ `seq(...).as(:pair)`. A name every alternative of an `either` shares
157
+ passes outward; alternatives named differently pass on none. A rule
158
+ keeps its names to itself, so using one inside a `seq` is always fine.
159
+
103
160
  The operator forms still work for short rules: `a >> b` (sequence),
104
161
  `a | b` (choice), `a & b` (guard), `a > b` (commit). Precedence, tightest
105
- first: `>>`, `&`, `|`, `>`.
162
+ first: `>>`, `&`, `|`, `>`. A chain of `>>` is one sequence:
163
+ `a.as(:x) >> b.as(:y) >> c.as(:z)` is `seq(a.as(:x), b.as(:y), c.as(:z))`,
164
+ however it's grouped, and `a > b >> c` is `seq(a, commit, b, c)`. A seq
165
+ built any other way (by `seq(...)`, or with a commit or `only`) stays
166
+ one part, as it would in `seq`: `pair >> c` is `seq(pair, c)`.
106
167
 
107
168
  ## Literals, spacing, and lexemes
108
169
 
@@ -115,7 +176,11 @@ Declare `ignore :spacing` and whitespace (and comments, with
115
176
  word-like ones (`"let"`) are keywords, so `"let"` won't match the
116
177
  start of `letter`.
117
178
  - `str("...")` and `match(/.../)` are **raw**: exactly those characters,
118
- no skipping.
179
+ no skipping. `match(/.../).captures` is raw too, its value a hash of
180
+ the named groups with Symbol keys, every name always present. Each
181
+ name must be used once (`(?<n>a)|(?<n>b)` raises `ArgumentError`), and
182
+ unnamed groups aren't included: Ruby stops capturing them once a group
183
+ is named. `.to(Node)` builds a node from it like any hash.
119
184
  - Rules for tokens (numbers, names, strings) should be declared with
120
185
  **`lexeme`** instead of `rule`, so they skip trailing spacing too. The
121
186
  built-in `Birman::Rules` already do this.
@@ -132,6 +197,9 @@ of at whatever a looser alternative accepted.
132
197
  - `rep` and `maybe` don't stop a commit. `seq("else", commit, expr).maybe`
133
198
  rejects `else` followed by garbage rather than quietly treating it as
134
199
  "no else clause".
200
+ - Neither does a trailing separator. `sep_by(x, ",", trailing: true)`
201
+ ends the list at a last `","` only when the `x` after it fails
202
+ without committing.
135
203
  - Lookaheads do. `x.absent`, `x.present` and the guard in `a & b` only
136
204
  ask whether `x` would match, so a commit failure inside them is just
137
205
  "no".
@@ -141,6 +209,182 @@ of at whatever a looser alternative accepted.
141
209
  This is Parsec-style commitment, not the choice-local "cut" from the
142
210
  PEG literature.
143
211
 
212
+ ## Left recursion
213
+
214
+ A rule can call itself before consuming anything, as grammars usually
215
+ write left-associative operators:
216
+
217
+ ```ruby
218
+ rule(:expr) { either(seq(expr, "-", term).map { |l, _, r| l - r }, term) }
219
+ rule(:term) { either(seq(term, "*", integer).map { |l, _, r| l * r }, integer) }
220
+
221
+ parse!("5*4*2-5*9*2") # => -50, as ((5*4)*2) - ((5*9)*2)
222
+ ```
223
+
224
+ `expr op term` is the left-associative form, the same shape `infix`
225
+ builds. Birman grows the match from a seed (Warth et al., 2008): it
226
+ tries the rule with its own call failing, then again with that call
227
+ returning the last match, for as long as the match gets further. So
228
+ each level is a value or node of its own, `((3-4)-5)`, and a node's
229
+ span covers its whole subexpression. A long chain uses no more stack
230
+ than a short one, and errors say what the last attempt needed
231
+ (`1-` expects an integer after the `-`). `to_ruby` generates parsers
232
+ that do the same.
233
+
234
+ The recursion can go through other rules too (indirect left
235
+ recursion), as when each level of precedence is split in two:
236
+
237
+ ```ruby
238
+ rule(:sum) { terms }
239
+ rule(:terms) { either(seq(sum, "-", term).map { |l, _, r| l - r }, term) }
240
+ ```
241
+
242
+ Each cycle grows at one rule, its leader, as pegen does: the first rule
243
+ (in the order the grammar uses them) that every way round the cycle
244
+ passes through, here `sum`. While it grows, the cycle's other rules
245
+ are matched afresh each round, since what they match depends on how
246
+ far it has got.
247
+
248
+ - The rule needs an alternative that doesn't start with itself, its
249
+ base case (`term` above), and the alternatives that do start with it
250
+ come first. Tried first, `term` would win every round, so
251
+ `either(term, seq(expr, "-", term))` could never grow past `3`. Either
252
+ mistake in the rule's own `either` (not one nested inside a `seq`)
253
+ raises `ArgumentError` on the first parse, or `to_ruby`, saying which: ``in left-recursive rule :expr, the alternative `term`
254
+ comes before `expr "-" term`, so the rule can never grow past it``.
255
+ In a cycle, any of its rules counts as the rule itself: one of them
256
+ needs a base case, and in each, the alternatives that start with a
257
+ rule of the cycle come first.
258
+ - `expr op expr`, with the rule on both sides, comes out
259
+ right-associative: `3-4-5` is `3-(4-5)`. The call on the right grows
260
+ to the end of the input before the left one can. Write `expr op term`.
261
+ - A cycle is grown only at its leader, so a parse that enters it at
262
+ another rule gets that rule's match with the leader's grown one,
263
+ which may already have taken what that rule needed. In
264
+ `l ← p ".x" / "x"; p ← p "(n)" / l` (Medeiros et al., 2014), every
265
+ way round passes through `p`, so `p` leads even when the parse
266
+ starts at `l`; then `x.x` matches only `x`, since `p` has grown over
267
+ the `.x`. Starting at `p` parses it. Usually this doesn't come up: of
268
+ the rules that could lead, the one the grammar uses first does.
269
+ - Cycles with no rule in common (`a` and `b` each call themselves and
270
+ each other) have nowhere to grow both from, so they raise
271
+ `ArgumentError` on the first parse, or `to_ruby`, naming them:
272
+ `left recursion through cycles with no rule in common isn't
273
+ supported: :a -> :a; :b -> :b`.
274
+ - Left recursion through a custom matcher, which Birman can't see into,
275
+ is caught only when the parse gets there.
276
+
277
+ ## Checks
278
+
279
+ An action can only return a value, and raising escapes `parse`, so an
280
+ action can't turn a match down. `.check` can:
281
+
282
+ ```ruby
283
+ rule(:date) { match(/\d{4}-\d\d-\d\d/).check(:real_date?, "a real date") }
284
+
285
+ private
286
+
287
+ def real_date?(text) = Date.valid_date?(*text.split("-").map(&:to_i))
288
+ ```
289
+
290
+ The method (it may be private) gets the value and returns truthy to
291
+ accept it; the value passes through unchanged. A falsy result is an
292
+ ordinary failure, "expected a real date", where the match began, and
293
+ whatever the inner match recorded (a `rep`'s stopping point, say) is
294
+ dropped. (This example uses `Date` and `to_i`, so it won't go into a
295
+ generated parser; see below.)
296
+
297
+ - It backtracks like any failure. The furthest failure still wins, so
298
+ if a later alternative gets further (`1979` matching a number before
299
+ `-02-30` trips it up), that alternative owns the error.
300
+ - Inside `rep` it ends the loop, and inside `maybe` it gives `nil`.
301
+ - It commits only when an enclosing `seq` already has:
302
+ `seq("on", commit, date)` stops at the rejected date. To commit to the
303
+ check itself, look first: `pattern.present > pattern.check(...)`.
304
+ - An outer `.label` at the same position replaces its label.
305
+ - Like actions, the method must depend only on the value: it also runs
306
+ inside lookaheads, and its result is memoized with the rule.
307
+
308
+ Some checks need more than one value: a name defined twice, a `break`
309
+ outside a loop. Make those in a second pass over the AST, and report
310
+ one at the node it's about with `Birman::Failure.at(node.span, reason)`.
311
+ It's a `Failure` like `parse` returns, committed, with `reason` as
312
+ its message:
313
+
314
+ ```ruby
315
+ def self.check(program)
316
+ seen = {}
317
+ program.each do |let|
318
+ return Birman::Failure.at(let.span, "#{let.name} is already defined") if seen[let.name]
319
+
320
+ seen[let.name] = true
321
+ end
322
+ Birman::Success.new(program)
323
+ end
324
+
325
+ case Grammar.parse(source)
326
+ in Birman::Success(program) then check(program)
327
+ in failure then failure
328
+ end
329
+ # line 2, column 1: x is already defined
330
+ # let x = 2
331
+ # ^
332
+ ```
333
+
334
+ ## Without a grammar
335
+
336
+ For a one-off pattern, skip the class: the combinators are on `Birman`
337
+ too, and `Birman.parse` runs a matcher as a whole grammar.
338
+
339
+ ```ruby
340
+ B = Birman
341
+ date = B.seq(B.match(/\d{4}/), "-", B.match(/\d\d/)).map { _1.join }
342
+
343
+ B.parse(date, "2026-10") # => Success("2026-10")
344
+ B.parse!(date, "26-10") # raises Birman::ParseError
345
+
346
+ B.parse(B.seq("let", B.match(/\w+/)), "let x", ignore: /\s*/)
347
+ # => Success(["let", "x"])
348
+ ```
349
+
350
+ `parse` returns `Success`/`Failure` and never raises on bad input, as
351
+ `Grammar.parse` does; `parse!` takes `formatter:` too. `ignore:` is a
352
+ matcher (a regexp, a string or any matcher) rather than a rule name,
353
+ since there's no grammar for a name to refer to; literals skip it, as
354
+ in a grammar with `ignore`, but `str` and `match` stay raw, so skip
355
+ spacing after them yourself if you need to. Matchers are values, so the grammar built
356
+ for one is cached and reused for an equal one.
357
+
358
+ To drop the `Birman.` prefix, mix `Birman::Combinators` into a class
359
+ of your own: `extend` it for matchers held in constants, `include` it
360
+ for matchers built by instance methods.
361
+
362
+ ```ruby
363
+ class Dates
364
+ extend Birman::Combinators
365
+ DATE = seq(match(/\d{4}/), "-", match(/\d\d/))
366
+ def self.parse(s) = Birman.parse(DATE, s)
367
+ end
368
+ Dates.parse("2026-10") # => Success(["2026", "-", "10"])
369
+
370
+ class Inst
371
+ include Birman::Combinators
372
+ def date = seq(match(/\d{4}/).as(:y), "-", match(/\d\d/).as(:m))
373
+ end
374
+ Birman.parse(Inst.new.date, "2026-10") # => Success({y: "2026", m: "10"})
375
+ ```
376
+
377
+ The class is just a namespace, not a grammar: matchers have no `parse`
378
+ of their own, so run them with `Birman.parse`, and bare strings skip
379
+ spacing only when you pass `ignore:`.
380
+
381
+ `rule` and `lexeme` aren't in `Combinators`, so anything named or
382
+ recursive, and `Birman::Rules` (`number`, `identifier`, ...), still
383
+ needs `include Birman::Grammar`; so do `keywords` and `.action`/`.check`
384
+ with methods of your own (`large_integer` works). A rule reference,
385
+ or an action or check naming a method of your own, raises
386
+ `ArgumentError` before parsing starts.
387
+
144
388
  ## Nodes
145
389
 
146
390
  `node(:a, :b)` (in any grammar class, from `extend Birman::Nodes`, or
@@ -165,8 +409,9 @@ has one.
165
409
  them; pass `nil` to clear one (`ignore nil`, `keywords nil`)
166
410
  - `Grammar.parse` returns `Success`/`Failure`; `parse!` raises `ParseError`
167
411
  (`parse!(input, formatter: f)` words it your way).
168
- Input nested too deeply for Ruby's stack (several hundred levels)
169
- is a `Failure` too, with `reason` set.
412
+ Input nested too deeply for Ruby's stack is a `Failure` too, with
413
+ `reason` set (see Limits), and so is invalid UTF-8: `reason` is
414
+ "invalid UTF-8", at the first bad byte.
170
415
  - `Failure#message(formatter)` takes anything with `#call(failure)`
171
416
  - `Grammar.to_ast` and `Grammar.to_ebnf` for introspection
172
417
  - Rules are methods, so they compose and override through `include`.
@@ -175,15 +420,153 @@ has one.
175
420
  combinators; any class that includes it is a grammar. Settings
176
421
  (`root`, `ignore`, `keywords`) still go on the class.
177
422
  - A rule can't be named after a method the grammar relies on (`send`,
178
- `class`, `match`, `token`...). Defining one raises `ArgumentError`
179
- instead of silently breaking the grammar. Redefining a rule is fine.
423
+ `class`, `match`, `token`, or a private helper in one of Birman's
424
+ modules, like `Rules::Strings`'s `decode_escape`), nor after a public
425
+ method of your own. Defining one raises `ArgumentError` saying whose
426
+ method it would replace, instead of silently breaking the grammar.
427
+ Redefining a rule is fine, and so is replacing your grammar's own
428
+ private helpers.
429
+
430
+ ## Generating a parser
431
+
432
+ `Grammar.to_ruby` writes a standalone parser: Ruby source with no
433
+ dependency on Birman that gives exactly the results `parse` gives
434
+ (values, spans, error positions, expected lists and messages).
435
+
436
+ ```ruby
437
+ File.write("calc_parser.rb", Calc::Grammar.to_ruby(namespace: "CalcParser"))
438
+ require_relative "calc_parser"
439
+ CalcParser::Parser.new("print 1 + 2").parse # => Success or Failure
440
+ ```
441
+
442
+ The output is written in the Ruby subset that
443
+ [Spinel](https://github.com/matz/spinel) compiles to a native binary.
444
+ Add an entry point and compile it:
445
+
446
+ ```ruby
447
+ source = Calc::Grammar.to_ruby(namespace: "CalcParser") + <<~RUBY
448
+ result = CalcParser::Parser.new($stdin.read).parse
449
+ puts result.success? ? result.value.inspect : result.message
450
+ RUBY
451
+ File.write("calc_parser.rb", source) # then: spinel calc_parser.rb -o calc
452
+ ```
453
+
454
+ Nodes come out as plain classes under `<namespace>::BirmanRT::Node`,
455
+ with the same fields, `span`, `==` and `inspect`. Values transformed
456
+ with `.map(:sym)`, `.action(:name)` and `.means(v)` compile, as does
457
+ `.check(:name, "label")`: an action's (or check's) method is copied from
458
+ its source, with the grammar methods it calls
459
+ and the constants it reads (`Float::INFINITY` and `Float::NAN` too), so
460
+ copying needs the `prism` gem (bundled from Ruby 3.3). So is a node's
461
+ `initialize` that only gives fields defaults (see Nodes): each node the
462
+ generated parser builds gets the default for a field its value leaves
463
+ out, a new one each time, as in Ruby. The generator raises `Birman::Compiler::Error`, naming
464
+ the rule, for what it can't translate faithfully:
465
+
466
+ - `.map { }` blocks (their source is lost) and custom matcher classes
467
+ - regexps using `\A`, `^`, lookbehind, or `\b`/`\B` at their start:
468
+ the generated parser scans from the current position, where those
469
+ would see less of the input than the interpreter's do
470
+ - regexps with a wide `{m,n}` (more than 16 apart) alongside a
471
+ lookaround, atomic group, backreference or lazy interval: Spinel's
472
+ regexp engine tries every length on those, exponentially. Making the
473
+ repeat atomic on its own, `(?>[a-z]{1,63})`, is allowed
474
+ - `.to(Node)` that may leave out a field with no default: named parts
475
+ that miss one, or one value (or a value that may or may not be a Hash
476
+ of fields) for a node with a field past its first that has none
477
+ - node classes with methods of their own (a `node(...) { }` body),
478
+ except an `initialize` that takes exactly the fields as keywords,
479
+ gives some literal defaults (nil, true, false, numbers, strings,
480
+ symbols, and arrays and hashes of them) and only calls `super`, as
481
+ `def initialize(name:, args: []) = super` does; and
482
+ names that would clash in the generated code (a node called `Success`,
483
+ a copied constant called `FAIL`, two copied constants of one name,
484
+ rules `:a?` and `:a_3f`, which are both `a_3f` there)
485
+ - actions that call `super`, Birman's own API (`keywords`, rules), or
486
+ classes the generated parser won't have, or that read a constant path
487
+ other than `Float::INFINITY` and `Float::NAN` (`Math::PI`; copy it
488
+ into a constant of the grammar)
489
+ - actions that call `Integer(...)`, `.hex`, `.oct` or `.to_i` (on
490
+ anything but an Integer literal, since a receiver's type can't be
491
+ told), or name them (`map(&:to_i)`): past 63 bits, in a Spinel
492
+ binary they raise or, where Spinel can't tell the receiver is a
493
+ String, silently give 2**63 - 1. Use `large_integer(text)` or
494
+ `large_integer(text, base)` (`16` for `hex`, `8` for `oct`), which
495
+ Birman gives every grammar: it is `Integer(text, base)` in Ruby, and
496
+ in the generated parser it returns Bignums under Spinel too (from
497
+ Floats as well: exactly, as Ruby's `Integer` does). `.map(:to_i)` is
498
+ fine on a String, an optional String (`.maybe`; nil is 0) or an
499
+ Integer; `.map(:hex)`, `.map(:oct)`, and `.map(:to_i)` on anything
500
+ else are refused, as are `send` and `method` with a name that isn't
501
+ a literal symbol. This is a net for the common forms, not a proof:
502
+ `Float(text).round`, `text.to_f.to_int` and whatever else slips past
503
+ it may raise, or give a wrong number without a word. `large_integer`
504
+ takes Integer bases and Floats (truncated) as Ruby does; Rational and
505
+ NaN bases differ (`TypeError`, `FloatDomainError`), a base that isn't
506
+ a number raises `TypeError` as in Ruby 3.3+ (3.2's `Integer` ignores
507
+ it), and in a binary negative bases (Ruby's "prefix or this base")
508
+ raise
509
+
510
+ ```ruby
511
+ def json_number(text) = text.match?(/[.eE]/) ? Float(text) : large_integer(text)
512
+ ```
513
+
514
+ Actions and `.map(:name)` always run, as in the interpreter, even when
515
+ nothing reads their value. The generated parser reads UTF-8 or binary input (other
516
+ encodings raise `ArgumentError`). It also nests deeper before reporting
517
+ "input is nested too deeply": its methods use less stack than the
518
+ interpreter's matchers, and a Spinel binary has a 64 MB stack (JSON
519
+ arrays nest about 55,000 deep), so input that fails with the interpreter
520
+ can parse when compiled. One limit runs the other way: Spinel allows 64
521
+ `begin` blocks active at once (and exits the process past that), and
522
+ each lookahead (`absent`, `present`, `&`) holds one while its inner
523
+ matcher runs. So lookaheads nested more than 56 deep are a Failure,
524
+ "input is nested too deeply". To allow more, build with a bigger stack
525
+ and say so when generating:
526
+
527
+ ```ruby
528
+ File.write("p.rb", G.to_ruby(namespace: "P", lookahead_depth: 4000))
529
+ # spinel p.rb -o p --cc="cc -DSP_EXC_STACK_MAX=4096"
530
+ ```
531
+
532
+ Copied actions are exactly what you wrote. Under Spinel, a string that
533
+ isn't valid UTF-8 (JSON's lone `\ud83d`) has the same bytes but
534
+ `inspect`s differently.
535
+
536
+ ## Limits
537
+
538
+ - **Deep nesting.** Each level of nesting in the input uses Ruby
539
+ stack. With default settings a simple nested grammar handles about
540
+ 750 levels on the main thread, but only about 250 on other threads
541
+ (Puma, Sidekiq, `Thread.new`). Past that, `parse` returns a `Failure`
542
+ whose `reason` is "input is nested too deeply". To allow more, raise
543
+ Ruby's thread stack sizes when starting the process:
544
+
545
+ ```sh
546
+ RUBY_THREAD_VM_STACK_SIZE=16777216 RUBY_THREAD_MACHINE_STACK_SIZE=67108864 ruby app.rb
547
+ ```
548
+
549
+ That took the same grammar to about 12,000 levels on a thread. The
550
+ main thread gets less (about 2,000), because its machine stack comes
551
+ from the operating system (`ulimit -s`).
552
+ - **Left recursion on both sides.** `expr op expr` groups to the right
553
+ (see [Left recursion](#left-recursion)); write `expr op term` for
554
+ left-associative operators.
555
+ - **Entering a left-recursive cycle away from its leader** gets a
556
+ shorter match than growing from there would (see
557
+ [Left recursion](#left-recursion)).
558
+ - **Changing rules after parsing.** `Grammar.parse` reuses one
559
+ instance per class, so each rule is built once. Defining a rule or
560
+ changing a setting on a class resets that class's instance, but a
561
+ subclass that has already parsed keeps its own: it won't see a rule
562
+ redefined later on its parent. Define rules before parsing, or call
563
+ `Subclass.new.parse(input)` to get a fresh instance.
180
564
 
181
565
  ## Not yet
182
566
 
183
- - Left recursion. A rule that can reach itself without consuming input
184
- raises `ArgumentError` naming the rule; use `infix`, or
185
- `term (op term)*` with a fold, instead.
186
- - Compiling grammars to Ruby methods for speed
567
+ - Left recursion through cycles with no rule in common (see
568
+ [Left recursion](#left-recursion)); give them a rule in common, or
569
+ use `infix`.
187
570
  - Railroad diagrams from `to_ast`
188
571
 
189
572
  ## Development
@@ -191,9 +574,16 @@ has one.
191
574
  ```sh
192
575
  bin/setup # install dependencies
193
576
  bundle exec rake # tests + rubocop
577
+ bundle exec rake spinel # generated parsers compiled by Spinel vs the interpreter
578
+ # (SPINEL=/path/to/spinel; skipped if not found)
194
579
  ruby examples/calc.rb
580
+ ruby examples/leftcalc.rb
195
581
  ruby examples/json.rb
196
- ruby bench/json.rb # vs JSON.parse, Parslet, Treetop, Citrus (installs them)
582
+ ruby examples/email.rb
583
+ ruby examples/toml.rb
584
+ ruby examples/query.rb
585
+ ruby bench/json.rb # vs JSON.parse, Parslet, Treetop, Citrus (installs them);
586
+ # also to_ruby, and its Spinel binary if Spinel is found
197
587
  ```
198
588
 
199
589
  ## License
data/lib/birman/ast.rb CHANGED
@@ -7,7 +7,11 @@ module Birman
7
7
  # [:seq, commit_at, [parts...]] [:choice, [alts...]]
8
8
  # [:rep, min, max, inner] [:not, inner] [:and, inner]
9
9
  # [:both, left, right] [:action, inner] [:label, name, inner]
10
- # [:quiet, inner]
10
+ # [:check, name, inner] [:quiet, inner] [:lexeme, inner]
11
+ #
12
+ # A lexeme is inner, then the grammar's ignore rule. Literals are
13
+ # lexemes too, but show as [:str, text]: the ignore rule runs only
14
+ # after the text, so only an empty literal shows as [:lexeme, ...].
11
15
  module AST
12
16
  module_function
13
17
 
@@ -27,6 +31,9 @@ module Birman
27
31
  # a* a+ a? a{2,3} !a not &a lookahead / guard
28
32
  #
29
33
  # Actions and labels don't change what's accepted, so they're omitted.
34
+ # Checks do, but by running Ruby on the value, which EBNF can't show
35
+ # (`&{name}` would only repeat the error message), so they're omitted
36
+ # too: the EBNF accepts a superset of what the grammar does.
30
37
  module EBNF
31
38
  module_function
32
39
 
@@ -49,7 +56,9 @@ module Birman
49
56
  in [:ref, name] then name.to_s
50
57
  in [:action, inner] then expr(inner, prec)
51
58
  in [:label, _, inner] then expr(inner, prec)
59
+ in [:check, _, inner] then expr(inner, prec)
52
60
  in [:quiet, inner] then expr(inner, prec)
61
+ in [:lexeme, inner] then expr(inner, prec)
53
62
  in [:both, [:not, guard], right]
54
63
  wrap("!#{expr(guard, ATOM)} #{expr(right, PREFIX)}", prec > SEQ)
55
64
  in [:choice, alts] then wrap(alts.map { expr(_1, SEQ) }.join(" / "), prec > CHOICE)