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.
@@ -0,0 +1,409 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Birman
4
+ # Finds left recursion from a grammar's ASTs ({ rule_name => ast },
5
+ # as Grammar#to_ast returns), without parsing anything: a rule that
6
+ # can call itself, directly or through other rules, before consuming
7
+ # input would loop forever on some input. Grammar#left_recursion runs
8
+ # it on the first parse or to_ruby, and keeps it once it passes.
9
+ # Left recursion is supported, direct (a rule that calls itself) or
10
+ # indirect (through other rules), the way pegen does it: each cycle
11
+ # has a leader, a rule that every way round it passes through, and
12
+ # Context#memoize grows the leader's match from a seed. A cycle with
13
+ # no such rule, two cycles that share none, raises in check!.
14
+ class LeftRecursion
15
+ FLAGS = { "i" => Regexp::IGNORECASE, "x" => Regexp::EXTENDED, "m" => Regexp::MULTILINE,
16
+ "n" => Regexp::NOENCODING }.freeze
17
+
18
+ # Each cycle's rules (a strongly connected component of the left-call
19
+ # graph), in the order rules has them: first-use order, from
20
+ # Grammar#to_ast.
21
+ attr_reader :cycles
22
+
23
+ # { rule => its cycle's leader } for every rule in a cycle that has
24
+ # one. The leader is the first of its cycle's rules that every way
25
+ # round the cycle passes through: take it out and the rest can't
26
+ # reach each other in a loop. Only it grows; see Context#memoize.
27
+ attr_reader :leaders
28
+
29
+ # ignore: the grammar's ignore rule, which a lexeme calls after its
30
+ # token; at its start, if the token was empty.
31
+ def initialize(rules, ignore: nil)
32
+ @rules = rules
33
+ @ignore = ignore
34
+ @patterns = {}
35
+ @nullable = nullable_rules
36
+ @calls = rules.to_h { |name, ast| [name, calls(ast).uniq] }
37
+ @cycles = strongly_connected.select { cyclic?(_1) }
38
+ @cycle_of = @cycles.each_with_object({}) { |cycle, h| cycle.each { h[_1] = cycle } }
39
+ @leaders = @cycles.each_with_object({}) do |cycle, h|
40
+ leader = cycle.find { acyclic?(cycle - [_1]) } or next
41
+ cycle.each { h[_1] = leader }
42
+ end.freeze
43
+ end
44
+
45
+ # Raises ArgumentError naming the cycles of every component with no
46
+ # leader, or for the first cycle that could never grow (see
47
+ # check_growth!); returns self otherwise.
48
+ def check!
49
+ leaderless = cycles.reject { @leaders.key?(_1.first) }
50
+ unless leaderless.empty?
51
+ loops = leaderless.flat_map { cycles_with_no_rule_in_common(_1) }
52
+ names = loops.map { _1.map(&:inspect).join(" -> ") }.join("; ")
53
+ raise ArgumentError, "left recursion through cycles with no rule in common isn't supported: #{names} " \
54
+ "(each rule can call the next without consuming input)"
55
+ end
56
+ cycles.each { check_growth!(_1) }
57
+ self
58
+ end
59
+
60
+ # Can the rule match without consuming input? Undefined rules can't.
61
+ def nullable?(name) = @nullable.key?(name)
62
+
63
+ # The rules name can call at the position it starts at, before it
64
+ # consumes anything, in the order its AST mentions them.
65
+ def left_calls(name) = @calls.fetch(name, [])
66
+
67
+ def left_recursive?(name) = @cycle_of.key?(name)
68
+ def cycle(name) = @cycle_of[name]
69
+ def leader(name) = @leaders[name]
70
+
71
+ # Can node match without consuming input? Every node type gets an
72
+ # answer; anything else (a custom matcher's own AST) is assumed to
73
+ # consume, which can only hide left recursion, never invent it.
74
+ def nullable_node?(node) # rubocop:disable Metrics
75
+ case node
76
+ in [:str, s] then s.empty? # a literal's ignored spacing comes after the literal
77
+ in [:pat, source] then nullable_pattern?(source)
78
+ in [:ref, name] then nullable?(name)
79
+ in [:seq, _, parts] then parts.all? { nullable_node?(_1) }
80
+ in [:choice, alts] then alts.any? { nullable_node?(_1) }
81
+ in [:rep, min, _, inner] then min.zero? || nullable_node?(inner)
82
+ in [:not, _] | [:and, _] then true
83
+ in [:both, _, right] then nullable_node?(right) # the guard consumes nothing
84
+ in [:action, inner] then nullable_node?(inner)
85
+ in [:quiet, inner] then nullable_node?(inner)
86
+ in [:label, _, inner] then nullable_node?(inner)
87
+ in [:check, _, inner] then nullable_node?(inner) # it may reject, but it can match empty
88
+ in [:lexeme, inner] then nullable_node?(inner)
89
+ else false
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ # Least fixpoint: start with no rule nullable and add rules until
96
+ # nothing changes, so a rule that only calls itself stays non-nullable.
97
+ def nullable_rules
98
+ @nullable = {}
99
+ loop do
100
+ grown = @rules.reject { |name, ast| @nullable.key?(name) || !nullable_node?(ast) }
101
+ return @nullable if grown.empty?
102
+
103
+ grown.each_key { @nullable[_1] = true }
104
+ end
105
+ end
106
+
107
+ # Rules node calls at its start position. A seq's parts run there
108
+ # until one consumes input; lookaheads count too, since re-entering
109
+ # a rule at the same position loops just the same.
110
+ def calls(node) # rubocop:disable Metrics
111
+ case node
112
+ in [:ref, name] then [name]
113
+ in [:seq, _, parts] then leading(parts).flat_map { calls(_1) }
114
+ in [:choice, alts] then alts.flat_map { calls(_1) }
115
+ in [:both, left, right] then calls(left) + calls(right)
116
+ in [:rep, _, 0, _] then [] # at most none: inner never runs
117
+ in [:rep, _, _, inner] then calls(inner)
118
+ in [:not, inner] then calls(inner)
119
+ in [:and, inner] then calls(inner)
120
+ in [:action, inner] then calls(inner)
121
+ in [:quiet, inner] then calls(inner)
122
+ in [:label, _, inner] then calls(inner)
123
+ in [:check, _, inner] then calls(inner)
124
+ in [:lexeme, inner] then calls(inner) + (@ignore && nullable_node?(inner) ? [@ignore] : [])
125
+ else []
126
+ end
127
+ end
128
+
129
+ # A cycle grows from a first match that didn't go round it (its
130
+ # seed), so one of its rules needs an alternative that doesn't start
131
+ # with any of them: without one it never matches. And in each of its
132
+ # rules, an alternative like that has to come after the ones that do
133
+ # start with one: tried first, it wins every round, so they never
134
+ # grow. Only each rule's own choice is checked, through the wrappers
135
+ # around it, not choices nested inside its parts.
136
+ def check_growth!(cycle) # rubocop:disable Metrics
137
+ raise ArgumentError, no_base_case(cycle) unless cycle.any? { seedable?(@rules[_1], cycle) }
138
+
139
+ cycle.each do |name|
140
+ alts = alternatives(@rules[name])
141
+ base = alts.index { !enters?(_1, cycle) } or next
142
+ grows = alts.drop(base).find { enters?(_1, cycle) } or next
143
+ first, later = [alts[base], grows].map { EBNF.expr(_1, EBNF::CHOICE) }
144
+ raise ArgumentError, "in left-recursive rule :#{name}, the alternative `#{first}` comes before `#{later}`, " \
145
+ "so the rule can never grow past it; " \
146
+ "put the alternatives that start with #{names(cycle, "or")} first"
147
+ end
148
+ end
149
+
150
+ def no_base_case(cycle)
151
+ if cycle.one?
152
+ name = cycle.first
153
+ "left-recursive rule :#{name} has no alternative that doesn't start with :#{name}, " \
154
+ "so it can never match; add one that doesn't, as a base case"
155
+ else
156
+ "left-recursive rules #{names(cycle, "and")}, which can each reach the others without consuming " \
157
+ "input, have no alternative that doesn't start with one of them, so they can never match; " \
158
+ "add one that doesn't, as a base case"
159
+ end
160
+ end
161
+
162
+ def enters?(node, cycle) = calls(node).intersect?(cycle)
163
+
164
+ # ":a, :b and :c"
165
+ def names(rules, conjunction)
166
+ [rules[0...-1].map(&:inspect).join(", "), rules.last.inspect].reject(&:empty?).join(" #{conjunction} ")
167
+ end
168
+
169
+ # Can node match while its calls to the cycle's rules at its start
170
+ # fail?
171
+ def seedable?(node, cycle) # rubocop:disable Metrics
172
+ case node
173
+ in [:ref, name] then !cycle.include?(name)
174
+ in [:choice, alts] then alts.any? { seedable?(_1, cycle) }
175
+ in [:seq, _, parts] then leading(parts).all? { seedable?(_1, cycle) }
176
+ in [:rep, 0, _, _] | [:rep, _, 0, _] | [:not, _] then true
177
+ in [:rep | :label | :check, *] | [:and | :action | :quiet | :lexeme, _]
178
+ seedable?(node.last, cycle) # a wrapper: its inner node is its last element
179
+ in [:both, left, right] then seedable?(left, cycle) && seedable?(right, cycle)
180
+ else true
181
+ end
182
+ end
183
+
184
+ # The alternatives of node's choice, looking through wrappers; node
185
+ # itself if it isn't one.
186
+ def alternatives(node)
187
+ case node
188
+ in [:choice, alts] then alts
189
+ in [:action | :quiet, _] | [:label | :check, _, _] then alternatives(node.last)
190
+ else [node]
191
+ end
192
+ end
193
+
194
+ # The parts up to and including the first that consumes input.
195
+ def leading(parts) = parts.take((parts.index { !nullable_node?(_1) } || parts.size) + 1)
196
+
197
+ # The regexp is rebuilt from its inspect form and tried on "" with
198
+ # every assertion (see Assertions) taken to hold, since each holds
199
+ # somewhere: /(?=a)/ matches empty before an "a", though not in "".
200
+ # Assertions that contradict each other side by side are caught;
201
+ # other combinations that can't hold, like /(?=a)(?=b)/, still count
202
+ # as nullable. A pattern that can't be rebuilt is assumed to consume.
203
+ def nullable_pattern?(source)
204
+ @patterns.fetch(source) do
205
+ body, flags = %r{\A/(.*)/([a-z]*)\z}m.match(source)&.captures
206
+ @patterns[source] = body ? empty_match?(body, flags.chars.sum { FLAGS.fetch(_1, 0) }) : false
207
+ end
208
+ end
209
+
210
+ # Falls back to the regexp as written when taking out its assertions
211
+ # breaks it, or could make a backreference name another group: taking
212
+ # out (?=(a)) renumbers the groups after it.
213
+ def empty_match?(body, options)
214
+ stripped = Assertions.strip(body, options.anybits?(Regexp::EXTENDED))
215
+ stripped = body if stripped != body && body.match?(/\\(?:[1-9]|k[<'])/)
216
+ [stripped, body].each do |source|
217
+ return Regexp.new(source, options).match?("")
218
+ rescue RegexpError
219
+ next
220
+ end
221
+ false
222
+ end
223
+
224
+ def cyclic?(rules) = rules.size > 1 || left_calls(rules.first).include?(rules.first)
225
+
226
+ # Tarjan's algorithm over the left-call graph. Each component's rules,
227
+ # and the components, come back in rule order.
228
+ def strongly_connected
229
+ @index = {}
230
+ @low = {}
231
+ @stack = []
232
+ @components = []
233
+ @rules.each_key { visit(_1) unless @index.key?(_1) }
234
+ order = @rules.keys.each_with_index.to_h
235
+ @components.map { |c| c.sort_by { order[_1] } }.sort_by { order[_1.first] }
236
+ end
237
+
238
+ def visit(name)
239
+ @index[name] = @low[name] = @index.size
240
+ @stack.push(name)
241
+ left_calls(name).each do |callee|
242
+ next unless @rules.key?(callee)
243
+
244
+ visit(callee) unless @index.key?(callee)
245
+ @low[name] = [@low[name], @low[callee]].min if @stack.include?(callee)
246
+ end
247
+ @components << pop_component(name) if @low[name] == @index[name]
248
+ end
249
+
250
+ def pop_component(name)
251
+ component = []
252
+ component << @stack.pop until component.last == name
253
+ component
254
+ end
255
+
256
+ # Can none of names reach itself through the others' left calls?
257
+ def acyclic?(names)
258
+ done = {}
259
+ names.none? { looping?(_1, names, done, {}) }
260
+ end
261
+
262
+ def looping?(name, names, done, on_path)
263
+ return true if on_path[name]
264
+ return false if done[name]
265
+
266
+ on_path[name] = done[name] = true
267
+ left_calls(name).any? { names.include?(_1) && looping?(_1, names, done, on_path) }
268
+ ensure
269
+ on_path.delete(name)
270
+ end
271
+
272
+ # For a component with no leader, cycles no one rule is in all of
273
+ # (though two of them may share one): for each rule, the first cycle
274
+ # through the others that leaves it out.
275
+ def cycles_with_no_rule_in_common(component)
276
+ order = @rules.keys.each_with_index.to_h
277
+ cycles = component.filter_map do |left_out|
278
+ rest = component - [left_out]
279
+ rest.lazy.filter_map { path(_1, rest) }.first
280
+ end
281
+ cycles.uniq(&:to_set).each_with_index.sort_by { |cycle, i| [order[cycle.first], i] }.map(&:first)
282
+ end
283
+
284
+ # The shortest way round from start back to it through names, through
285
+ # another rule if there is one (a leader may also call itself); nil
286
+ # if there's none.
287
+ def path(start, names) # rubocop:disable Metrics/MethodLength
288
+ from = {}
289
+ queue = [start]
290
+ while (name = queue.shift)
291
+ left_calls(name).each do |callee|
292
+ if callee == start
293
+ next if name == start
294
+
295
+ return [*trail(from, name), start]
296
+ end
297
+ next if !names.include?(callee) || from.key?(callee)
298
+
299
+ from[callee] = name
300
+ queue << callee
301
+ end
302
+ end
303
+ [start, start] if left_calls(start).include?(start)
304
+ end
305
+
306
+ def trail(from, name)
307
+ names = [name]
308
+ names.unshift(from[names.first]) while from.key?(names.first)
309
+ names
310
+ end
311
+
312
+ # A regexp's source with each assertion, which matches only where the
313
+ # text around it allows, replaced by (?:), which matches anywhere:
314
+ # lookarounds, anchors (\A, ^, $, \z, \Z, \G) and \b, \B. Assertions
315
+ # side by side hold at one position, so a pair there that can't both
316
+ # hold, (?=X) with (?!X), (?<=X) with (?<!X), or \b with \B, becomes
317
+ # (?!), which matches nowhere. Only that shape: (?=a)(?=b) can't hold
318
+ # either, but stays (?:)(?:). So does a negative lookaround of what
319
+ # always matches empty, as never's (?!) or (?!a*).
320
+ # Escapes, character classes and comments are copied as they are.
321
+ # Only /x makes # start a comment; an inline (?x) doesn't, so a "[" or
322
+ # "(" in such a comment can hide an assertion after it (it's then
323
+ # taken as written, as before this rewrite).
324
+ module Assertions
325
+ module_function
326
+
327
+ # kind: "=", "!", "<=" or "<!" for a lookaround (body: its source),
328
+ # or the anchor itself ("^", "\\b"...).
329
+ Assertion = Data.define(:kind, :body)
330
+ OPPOSITE = { "=" => "!", "<=" => "<!", "\\b" => "\\B" }.freeze
331
+
332
+ def strip(source, extended)
333
+ scanner = StringScanner.new(source)
334
+ out = +""
335
+ run = []
336
+ until scanner.eos?
337
+ item = piece(scanner, extended)
338
+ next run << item if item.is_a?(Assertion)
339
+
340
+ flush(run, out, extended, quantified: item.match?(/\A[?*+{]/))
341
+ out << item
342
+ end
343
+ flush(run, out, extended, quantified: false)
344
+ out
345
+ end
346
+
347
+ # Writes out a run of assertions side by side. A quantifier after the
348
+ # last one may skip it, so then it can't fail the rest.
349
+ def flush(run, out, extended, quantified:)
350
+ held = quantified ? run[0...-1] : run
351
+ never = held.any? { |a| held.include?(Assertion.new(OPPOSITE[a.kind], a.body)) || never?(a, extended) }
352
+ out << "(?!)" if never
353
+ out << ("(?:)" * run.size)
354
+ run.clear
355
+ end
356
+
357
+ # A negative lookaround of something with no assertions of its own
358
+ # that matches "" never holds: that matches empty everywhere.
359
+ def never?(assertion, extended)
360
+ return false unless %w[! <!].include?(assertion.kind) && strip(assertion.body, extended) == assertion.body
361
+
362
+ Regexp.new(assertion.body, extended ? Regexp::EXTENDED : 0).match?("")
363
+ rescue RegexpError
364
+ false
365
+ end
366
+
367
+ # The next piece of the source: an Assertion, or text to copy.
368
+ def piece(s, extended) # rubocop:disable Metrics
369
+ if s.scan(/\\[bBAzZG]|[\^$]/) then Assertion.new(s.matched, nil)
370
+ elsif s.scan(/\(\?(<?[=!])/) then lookaround(s, s[1], extended)
371
+ elsif s.scan(/\\./m) || s.scan(/\(\?#[^)]*\)/) then s.matched
372
+ elsif s.skip(/\[/) then character_class(s)
373
+ elsif extended && s.scan(/#[^\n]*/) then s.matched
374
+ else s.getch
375
+ end
376
+ end
377
+
378
+ # The rest of a lookaround, after its "(?=" (or "(?!", "(?<=", "(?<!").
379
+ def lookaround(s, kind, extended)
380
+ start = finish = s.pos
381
+ depth = 1
382
+ until depth.zero? || s.eos?
383
+ finish = s.pos
384
+ case piece(s, extended)
385
+ when "(" then depth += 1
386
+ when ")" then depth -= 1
387
+ end
388
+ end
389
+ Assertion.new(kind, s.string.byteslice(start, finish - start))
390
+ end
391
+
392
+ # A character class (nested ones included), after its "[". A "]"
393
+ # right after "[" or "[^" is a character, not the end.
394
+ def character_class(s)
395
+ start = s.pos - 1
396
+ s.skip(/\^?\]?/)
397
+ depth = 1
398
+ while depth.positive? && !s.eos?
399
+ if s.skip(/\\./m) then next
400
+ elsif s.skip(/\[/) then depth += 1
401
+ elsif s.skip(/\]/) then depth -= 1
402
+ else s.getch
403
+ end
404
+ end
405
+ s.string.byteslice(start, s.pos - start)
406
+ end
407
+ end
408
+ end
409
+ end