birman 0.2.2 → 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.2"
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
@@ -166,7 +410,8 @@ has one.
166
410
  - `Grammar.parse` returns `Success`/`Failure`; `parse!` raises `ParseError`
167
411
  (`parse!(input, formatter: f)` words it your way).
168
412
  Input nested too deeply for Ruby's stack is a `Failure` too, with
169
- `reason` set (see Limits).
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,8 +420,118 @@ 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.
180
535
 
181
536
  ## Limits
182
537
 
@@ -194,6 +549,12 @@ has one.
194
549
  That took the same grammar to about 12,000 levels on a thread. The
195
550
  main thread gets less (about 2,000), because its machine stack comes
196
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)).
197
558
  - **Changing rules after parsing.** `Grammar.parse` reuses one
198
559
  instance per class, so each rule is built once. Defining a rule or
199
560
  changing a setting on a class resets that class's instance, but a
@@ -203,10 +564,9 @@ has one.
203
564
 
204
565
  ## Not yet
205
566
 
206
- - Left recursion. A rule that can reach itself without consuming input
207
- raises `ArgumentError` naming the rule; use `infix`, or
208
- `term (op term)*` with a fold, instead.
209
- - 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`.
210
570
  - Railroad diagrams from `to_ast`
211
571
 
212
572
  ## Development
@@ -214,9 +574,16 @@ has one.
214
574
  ```sh
215
575
  bin/setup # install dependencies
216
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)
217
579
  ruby examples/calc.rb
580
+ ruby examples/leftcalc.rb
218
581
  ruby examples/json.rb
219
- 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
220
587
  ```
221
588
 
222
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)
@@ -57,7 +57,7 @@ module Birman
57
57
  # params = ["word: String"]
58
58
  # returns = "Matcher for word when not followed by a word character"
59
59
  # value = "word"
60
- def keyword(word) = (str(word) >> match(/\w/).absent).map(&:first).label(word.inspect)
60
+ def keyword(word) = Pick.new([str(word), match(/\w/).absent], 0).label(word.inspect)
61
61
 
62
62
  # --- Literals and the readable forms ---
63
63
 
@@ -70,8 +70,8 @@ module Birman
70
70
  def lit(x) = Birman.coerce(x)
71
71
 
72
72
  # The matcher a literal stands for: "let" is a keyword, "=" an exact
73
- # string, either way followed by the grammar's ignore rule. Built
74
- # once per grammar instance.
73
+ # string, either way followed by the ignore rule of the grammar
74
+ # running it. Built once per grammar instance.
75
75
  # [method.literal_matcher]
76
76
  # params = ["text: String"]
77
77
  # returns = "The matcher a literal stands for: keyword or exact string, then the ignore rule"
@@ -80,7 +80,7 @@ module Birman
80
80
  def literal_matcher(text)
81
81
  (@literal_matchers ||= {})[text] ||= begin
82
82
  base = text.match?(/\A\w+\z/) ? keyword(text) : str(text)
83
- Lexeme.new(base, ignore_rule, text)
83
+ Lexeme.new(base, text)
84
84
  end
85
85
  end
86
86
 
@@ -90,7 +90,8 @@ module Birman
90
90
  # params = ["*items: String | Regexp | Matcher | commit | only(...)"]
91
91
  # returns = "Matcher for the items in order"
92
92
  # value = "Array of item values; a Hash of the .as-named parts if any; the only(...) part's value if used"
93
- # raises = ["ArgumentError (commit first; more than one only; only mixed with .as names)"]
93
+ # raises = ["ArgumentError (commit first; more than one only; only mixed with .as names)",
94
+ # "ArgumentError (a name used twice; a nested seq's names the value would drop)"]
94
95
  def seq(*items)
95
96
  commit_at = items.index(COMMIT)
96
97
  raise ArgumentError, "commit can't come first in a seq: nothing has matched yet" if commit_at&.zero?
@@ -118,7 +119,7 @@ module Birman
118
119
  # [method.either]
119
120
  # params = ["*alts: String | Regexp | Matcher"]
120
121
  # returns = "Ordered choice: the first alternative that matches wins"
121
- # value = "The winning alternative's value"
122
+ # value = "The winning alternative's value (a name all the alternatives share passes outward)"
122
123
  def either(*alts) = alts.size == 1 ? lit(alts.first) : Choice.new(alts.map { lit(_1) })
123
124
 
124
125
  # One of several literals. Given a hash, each literal produces its
@@ -131,7 +132,7 @@ module Birman
131
132
  def one_of(*choices)
132
133
  return either(*choices) unless choices in [Hash => table]
133
134
 
134
- either(*table.map { |text, value| lit(text).map { value } })
135
+ either(*table.map { |text, value| lit(text).means(value) })
135
136
  end
136
137
 
137
138
  # Left-associative binary operators, folded into nodes:
@@ -144,18 +145,21 @@ module Birman
144
145
  def infix(operand, *ops, node:) = Infix.new(Birman.coerce(operand), either(*ops).label("operator"), node)
145
146
 
146
147
  # One or more items separated by sep; value is an array of items.
148
+ # With trailing: true, one separator may follow the last item:
149
+ # seq("[", only(sep_by(value, ",", trailing: true)), "]") # [1, 2,]
147
150
  # [method.sep_by1]
148
- # params = ["item: String | Regexp | Matcher", "sep: String | Regexp | Matcher"]
149
- # returns = "Matcher for one or more items separated by sep"
151
+ # params = ["item: String | Regexp | Matcher", "sep: String | Regexp | Matcher", "trailing: Boolean = false"]
152
+ # returns = "Matcher for one or more items separated by sep (and, if trailing, maybe one more sep)"
150
153
  # value = "Array of item values (item's .as name passes outward)"
151
- def sep_by1(item, sep) = SepBy.new(lit(item), lit(sep))
154
+ def sep_by1(item, sep, trailing: false) = SepBy.new(lit(item), lit(sep), 1, trailing)
152
155
 
153
- # Zero or more items separated by sep.
156
+ # Zero or more items separated by sep. A trailing separator needs an
157
+ # item before it: "," alone isn't an empty list.
154
158
  # [method.sep_by]
155
- # params = ["item: String | Regexp | Matcher", "sep: String | Regexp | Matcher"]
156
- # returns = "Matcher for zero or more items separated by sep"
159
+ # params = ["item: String | Regexp | Matcher", "sep: String | Regexp | Matcher", "trailing: Boolean = false"]
160
+ # returns = "Matcher for zero or more items separated by sep (and, if trailing, maybe one more sep)"
157
161
  # value = "Array of item values, [] when there are none"
158
- def sep_by(item, sep) = sep_by1(item, sep).maybe.map { _1 || [] }
162
+ def sep_by(item, sep, trailing: false) = SepBy.new(lit(item), lit(sep), 0, trailing)
159
163
 
160
164
  # inner wrapped in open/close; value is inner's.
161
165
  # [method.between]
@@ -180,4 +184,11 @@ module Birman
180
184
 
181
185
  def ignore_rule = self.class.respond_to?(:ignore) ? self.class.ignore : nil
182
186
  end
187
+
188
+ # Birman.seq, Birman.str and the rest, for building matchers outside a
189
+ # grammar (see Birman.parse). literal_matcher stays private: it's what
190
+ # a grammar runs for a literal, and Birman isn't one.
191
+ extend Combinators
192
+
193
+ singleton_class.send(:private, :literal_matcher)
183
194
  end