parsanol 1.3.87-arm-linux → 1.3.89-arm-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/parsanol/3.2/parsanol_native.so +2 -2
- data/lib/parsanol/3.3/parsanol_native.so +2 -2
- data/lib/parsanol/3.4/parsanol_native.so +2 -2
- data/lib/parsanol/4.0/parsanol_native.so +2 -2
- data/lib/parsanol/atoms/context.rb +4 -4
- data/lib/parsanol/atoms/lookbehind.rb +5 -2
- data/lib/parsanol/atoms/named.rb +8 -4
- data/lib/parsanol/atoms/trivia.rb +7 -3
- data/lib/parsanol/native/libparsanol.so +0 -0
- data/lib/parsanol/native/transformer.rb +10 -3
- data/lib/parsanol/parg/compiler.rb +48 -2
- data/lib/parsanol/parg/imports.rb +2 -1
- data/lib/parsanol/parg/lexer.rb +1 -1
- data/lib/parsanol/parg/parser.rb +44 -10
- data/lib/parsanol/parg/visitor.rb +3 -1
- data/lib/parsanol/parser.rb +10 -2
- data/lib/parsanol/skip.rb +46 -26
- data/lib/parsanol/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4179b41593acae7cf74ca3167806f07fbdafb4cc54ee4d6b672141d74551ac49
|
|
4
|
+
data.tar.gz: ef1ba1ba6ba7f6a5f09b2dc25f704944252e41a07298c96417f716bff4e55a70
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 975a9f43d4856e2998af49a01b3428e0d767db399f07df9a6507b954e4e7b7aa7b9f976716722b266c8fd33dec48a07b4e6be9d86c479c2720687978192dd8af
|
|
7
|
+
data.tar.gz: 570681fc86af2ab6f25b0c6bad910138cd9b25445b420655b183d7d1d4dca39a7be718cfb38b739515d985f6c6a840da1ef129a937ece067f4615eff415b887a
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/parsanol/3.2/parsanol_native.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 34005012 bytes
|
|
4
|
+
sha256: a8d9bf7504fcf0ac72af4bac22ef380f91ffb941c38d27badd31a38aae2cdd0c
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/parsanol/3.3/parsanol_native.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 34190272 bytes
|
|
4
|
+
sha256: 8ae6bfd6ecf22108657162245248e494a911493c8ab4ce1feccf520fc68254e0
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/parsanol/3.4/parsanol_native.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 34184432 bytes
|
|
4
|
+
sha256: 2403ab2731c77acff92e87d5bfd55be82eecbeeb91349f0aef2a159508f53bf7
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/parsanol/4.0/parsanol_native.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 34194168 bytes
|
|
4
|
+
sha256: 897db94d6190f5fde15254a05fabe8dde3043951497c0b166ba168a7ca4a24ad
|
|
@@ -263,10 +263,10 @@ module Parsanol
|
|
|
263
263
|
[false, @reporter.err(*)]
|
|
264
264
|
end
|
|
265
265
|
|
|
266
|
-
# Records one captured trivia unit (kind, text
|
|
267
|
-
# the next successful Named capture.
|
|
268
|
-
def push_captured_trivia(kind, text)
|
|
269
|
-
@pending_trivia << { kind: kind, text: text }
|
|
266
|
+
# Records one captured trivia unit (kind, text, input offset)
|
|
267
|
+
# for attachment to the next successful Named capture.
|
|
268
|
+
def push_captured_trivia(kind, text, pos)
|
|
269
|
+
@pending_trivia << { kind: kind, text: text, pos: pos }
|
|
270
270
|
nil
|
|
271
271
|
end
|
|
272
272
|
|
|
@@ -35,7 +35,7 @@ module Parsanol
|
|
|
35
35
|
!@regex_source.nil?
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
def try(source,
|
|
38
|
+
def try(source, context, _consume_all)
|
|
39
39
|
start = source.bytepos
|
|
40
40
|
if regex?
|
|
41
41
|
@compiled ||= Regexp.new("(?:#{@regex_source})\\z")
|
|
@@ -47,7 +47,10 @@ module Parsanol
|
|
|
47
47
|
end
|
|
48
48
|
matched = !behind.nil? && behind == @pattern.b
|
|
49
49
|
end
|
|
50
|
-
|
|
50
|
+
return ok(nil) if matched == @positive
|
|
51
|
+
|
|
52
|
+
message = regex? ? "text behind does not match /#{@regex_source}/" : "text behind is not #{@pattern.inspect}"
|
|
53
|
+
context.err_at(self, source, message, start)
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
def to_s_inner(_prec)
|
data/lib/parsanol/atoms/named.rb
CHANGED
|
@@ -41,7 +41,7 @@ module Parsanol
|
|
|
41
41
|
|
|
42
42
|
comments = context.take_pending_trivia
|
|
43
43
|
result = wrap_result(value)
|
|
44
|
-
result = attach_comments(result, comments) unless comments.empty?
|
|
44
|
+
result = attach_comments(result, comments, source) unless comments.empty?
|
|
45
45
|
ok(result)
|
|
46
46
|
end
|
|
47
47
|
|
|
@@ -50,9 +50,13 @@ module Parsanol
|
|
|
50
50
|
# parsanol-ruby#152: trivia captured ahead of this capture rides
|
|
51
51
|
# with it under `comments:`. The attachment only fires when the
|
|
52
52
|
# grammar declared capture AND trivia actually preceded — key
|
|
53
|
-
# absence everywhere else keeps v1 trees byte-identical.
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
# absence everywhere else keeps v1 trees byte-identical. Units
|
|
54
|
+
# carry their input offset so the value is a positioned Slice,
|
|
55
|
+
# like every other capture value (native parity, ruby#180).
|
|
56
|
+
def attach_comments(result, comments, source)
|
|
57
|
+
list = comments.map do |unit|
|
|
58
|
+
{ unit[:kind] => Slice.new(unit[:pos], unit[:text], source.input) }
|
|
59
|
+
end
|
|
56
60
|
if result.is_a?(Hash)
|
|
57
61
|
result.merge(comments: list)
|
|
58
62
|
else
|
|
@@ -61,13 +61,17 @@ module Parsanol
|
|
|
61
61
|
def record(source, context, start_pos, end_pos)
|
|
62
62
|
text = source.input[start_pos...end_pos]
|
|
63
63
|
# A trivia unit may lead with whitespace before the comment
|
|
64
|
-
# shape; markers test the comment head.
|
|
64
|
+
# shape; markers test the comment head. The recorded offset
|
|
65
|
+
# must agree with the recorded content as a slice of the
|
|
66
|
+
# input: trimmed marker text starts after the unit's leading
|
|
67
|
+
# whitespace, not at the wrapper (native parity, ruby#180).
|
|
65
68
|
stripped = text.lstrip
|
|
66
69
|
marker, label = @capturers&.find { |m, _label| stripped.start_with?(m) }
|
|
67
70
|
if marker
|
|
68
|
-
context.push_captured_trivia(label, stripped.strip
|
|
71
|
+
context.push_captured_trivia(label, stripped.strip,
|
|
72
|
+
start_pos + (text.length - stripped.length))
|
|
69
73
|
elsif @whitespace_kind
|
|
70
|
-
context.push_captured_trivia(@whitespace_kind, text)
|
|
74
|
+
context.push_captured_trivia(@whitespace_kind, text, start_pos)
|
|
71
75
|
end
|
|
72
76
|
end
|
|
73
77
|
end
|
|
Binary file
|
|
@@ -310,9 +310,16 @@ module Parsanol
|
|
|
310
310
|
transformed
|
|
311
311
|
elsif is_repetition
|
|
312
312
|
if transformed.is_a?(Array)
|
|
313
|
-
if transformed.all?
|
|
314
|
-
|
|
315
|
-
|
|
313
|
+
if transformed.all?(Hash)
|
|
314
|
+
# Hash items carry their own capture
|
|
315
|
+
# names (parslet semantics) — the
|
|
316
|
+
# single-key path
|
|
317
|
+
# (transform_repetition_value) already
|
|
318
|
+
# keeps them; this multi-key path (a
|
|
319
|
+
# Named with a trivia attachment, or
|
|
320
|
+
# merged siblings) must agree — only
|
|
321
|
+
# unnamed items take the parent name
|
|
322
|
+
# per item.
|
|
316
323
|
transformed
|
|
317
324
|
else
|
|
318
325
|
transformed.map { |item| { sym_key => item } }
|
|
@@ -422,7 +422,12 @@ module Parsanol
|
|
|
422
422
|
when :rep
|
|
423
423
|
Atoms::Repetition.new(build_atom(node.a), node.b, node.c)
|
|
424
424
|
when :opt then Atoms::Repetition.new(build_atom(node.a), 0, 1, :maybe)
|
|
425
|
-
when :pred
|
|
425
|
+
when :pred
|
|
426
|
+
if node.c == :behind
|
|
427
|
+
build_lookbehind(node)
|
|
428
|
+
else
|
|
429
|
+
Atoms::Lookahead.new(build_atom(node.b), node.a)
|
|
430
|
+
end
|
|
426
431
|
when :cap
|
|
427
432
|
# "" as name is the PARG spelling of Ruby parslet's
|
|
428
433
|
# str("").as(:name): an always-succeeding zero-width marker
|
|
@@ -470,6 +475,41 @@ module Parsanol
|
|
|
470
475
|
end
|
|
471
476
|
end
|
|
472
477
|
|
|
478
|
+
# `!<X` / `&<X` (parsanol-ruby#197, coradoc#281): the behind
|
|
479
|
+
# guard consumes nothing and constrains only the text behind the
|
|
480
|
+
# position. A case-sensitive literal is the fixed byte window
|
|
481
|
+
# (Literal form); a character class — or builtin class reference,
|
|
482
|
+
# or case-insensitive literal — is the end-anchored flanking
|
|
483
|
+
# regex (Regex form). Anything else cannot be expressed behind.
|
|
484
|
+
def build_lookbehind(node)
|
|
485
|
+
positive = node.a
|
|
486
|
+
body = node.b
|
|
487
|
+
case body.kind
|
|
488
|
+
when :lit
|
|
489
|
+
if body.b
|
|
490
|
+
Atoms::Lookbehind.regex("(?i:#{Regexp.escape(body.a)})",
|
|
491
|
+
positive: positive)
|
|
492
|
+
else
|
|
493
|
+
Atoms::Lookbehind.new(body.a.bytesize, body.a, positive: positive)
|
|
494
|
+
end
|
|
495
|
+
when :class
|
|
496
|
+
Atoms::Lookbehind.regex(class_pattern(body.a), positive: positive)
|
|
497
|
+
when :ref
|
|
498
|
+
if BUILTIN_CLASSES.key?(body.a)
|
|
499
|
+
Atoms::Lookbehind.regex(class_pattern(BUILTIN_CLASSES[body.a]),
|
|
500
|
+
positive: positive)
|
|
501
|
+
else
|
|
502
|
+
raise CompileError,
|
|
503
|
+
"lookbehind body must be a literal or character class " \
|
|
504
|
+
"(got rule #{body.a.inspect})"
|
|
505
|
+
end
|
|
506
|
+
else
|
|
507
|
+
raise CompileError,
|
|
508
|
+
"lookbehind body must be a literal or character class " \
|
|
509
|
+
"(got #{body.kind.inspect})"
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
|
|
473
513
|
def class_pattern(ranges)
|
|
474
514
|
body = ranges.map do |lo, hi|
|
|
475
515
|
if lo == hi
|
|
@@ -635,7 +675,13 @@ module Parsanol
|
|
|
635
675
|
set
|
|
636
676
|
when :opt then first_set(node.a) + [EPS]
|
|
637
677
|
when :pred
|
|
638
|
-
|
|
678
|
+
# A behind-guard constrains nothing ahead: its first set is
|
|
679
|
+
# ANY for both signs.
|
|
680
|
+
if node.c == :behind
|
|
681
|
+
Set.new([ANY])
|
|
682
|
+
else
|
|
683
|
+
node.a ? first_set(node.b) : Set.new([ANY])
|
|
684
|
+
end
|
|
639
685
|
when :cap then first_set(node.b)
|
|
640
686
|
when :ref
|
|
641
687
|
if @ref_stack.include?(node.a)
|
|
@@ -76,7 +76,8 @@ module Parsanol
|
|
|
76
76
|
when :alt then Node.new(:alt, node.a.map { |child| copy(child, prefix) })
|
|
77
77
|
when :rep then Node.new(:rep, copy(node.a, prefix), node.b, node.c)
|
|
78
78
|
when :opt then Node.new(:opt, copy(node.a, prefix))
|
|
79
|
-
when :pred
|
|
79
|
+
when :pred
|
|
80
|
+
Node.new(:pred, node.a, copy(node.b, prefix), node.c)
|
|
80
81
|
when :cap then Node.new(:cap, node.a, copy(node.b, prefix))
|
|
81
82
|
when :ref then Node.new(:ref, "#{prefix}#{node.a}")
|
|
82
83
|
else node
|
data/lib/parsanol/parg/lexer.rb
CHANGED
data/lib/parsanol/parg/parser.rb
CHANGED
|
@@ -296,30 +296,64 @@ module Parsanol
|
|
|
296
296
|
if peek&.type == :punct && peek.value == "*"
|
|
297
297
|
advance
|
|
298
298
|
max = peek&.type == :num ? advance.value.to_i : nil
|
|
299
|
-
node = Node.new(:rep,
|
|
299
|
+
node = Node.new(:rep, parse_rep_body, min, max)
|
|
300
300
|
else
|
|
301
|
-
node = Node.new(:rep,
|
|
301
|
+
node = Node.new(:rep, parse_rep_body, min, min)
|
|
302
302
|
end
|
|
303
|
-
return node
|
|
303
|
+
return capture_postfix(node)
|
|
304
304
|
elsif token.type == :punct && token.value == "*"
|
|
305
305
|
advance
|
|
306
306
|
max = peek&.type == :num ? advance.value.to_i : nil
|
|
307
|
-
return Node.new(:rep,
|
|
307
|
+
return capture_postfix(Node.new(:rep, parse_rep_body, 0, max))
|
|
308
308
|
end
|
|
309
309
|
parse_postfixed
|
|
310
310
|
end
|
|
311
311
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
312
|
+
# A repetition's body must not claim the `as` that follows the
|
|
313
|
+
# repetition's own closing paren (parsanol-ruby#197): the body
|
|
314
|
+
# parser is postfix-blind, and the postfix wraps the REPETITION —
|
|
315
|
+
# `*( X ) as name` captures the whole repeated run, matching the
|
|
316
|
+
# Ruby DSL's `.repeat.as(:name)`. An `as` inside the body's own
|
|
317
|
+
# parens still binds the body — write `*( ( X ) as name )` for
|
|
318
|
+
# that (naming each iteration's X).
|
|
319
|
+
def parse_rep_body
|
|
320
|
+
sign, behind = parse_guard_sign
|
|
317
321
|
node = parse_primary
|
|
322
|
+
if sign.nil?
|
|
323
|
+
node
|
|
324
|
+
else
|
|
325
|
+
Node.new(:pred, sign, node, behind ? :behind : nil)
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# `!X` / `&X` are lookaheads; `!<X` / `&<X` (coradoc#281,
|
|
330
|
+
# parsanol-ruby#197) inspect the text BEHIND the position and
|
|
331
|
+
# consume nothing.
|
|
332
|
+
def parse_guard_sign
|
|
333
|
+
return [nil, false] unless peek&.type == :punct && %w[! &].include?(peek.value)
|
|
334
|
+
|
|
335
|
+
positive = advance.value == "&"
|
|
336
|
+
behind = peek&.type == :punct && peek.value == "<"
|
|
337
|
+
advance if behind
|
|
338
|
+
[positive, behind]
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def capture_postfix(node)
|
|
318
342
|
if peek&.type == :ident && peek.value == "as"
|
|
319
343
|
advance
|
|
320
344
|
node = Node.new(:cap, ident.value, node)
|
|
321
345
|
end
|
|
322
|
-
|
|
346
|
+
node
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def parse_postfixed
|
|
350
|
+
sign, behind = parse_guard_sign
|
|
351
|
+
node = capture_postfix(parse_primary)
|
|
352
|
+
if sign.nil?
|
|
353
|
+
node
|
|
354
|
+
else
|
|
355
|
+
Node.new(:pred, sign, node, behind ? :behind : nil)
|
|
356
|
+
end
|
|
323
357
|
end
|
|
324
358
|
|
|
325
359
|
def parse_primary
|
|
@@ -53,7 +53,9 @@ module Parsanol
|
|
|
53
53
|
Node.new(node.kind, rewrite(node.a), node.b, node.c)
|
|
54
54
|
when :opt
|
|
55
55
|
Node.new(node.kind, rewrite(node.a))
|
|
56
|
-
when :pred
|
|
56
|
+
when :pred
|
|
57
|
+
Node.new(node.kind, node.a, rewrite(node.b), node.c)
|
|
58
|
+
when :cap
|
|
57
59
|
Node.new(node.kind, node.a, rewrite(node.b))
|
|
58
60
|
else
|
|
59
61
|
yield(node) || node
|
data/lib/parsanol/parser.rb
CHANGED
|
@@ -55,7 +55,8 @@ module Parsanol
|
|
|
55
55
|
if skip_name
|
|
56
56
|
Parsanol::Skip.inject(self, atom, skip_name,
|
|
57
57
|
captures: self.class.skip_captures,
|
|
58
|
-
whitespace: self.class.skip_whitespace
|
|
58
|
+
whitespace: self.class.skip_whitespace,
|
|
59
|
+
exempt: self.class.skip_exempt)[0]
|
|
59
60
|
else
|
|
60
61
|
atom
|
|
61
62
|
end
|
|
@@ -90,10 +91,11 @@ module Parsanol
|
|
|
90
91
|
# rule(:line_comment) { str("//") >> match(/[^\n]/).repeat }
|
|
91
92
|
# skip :trivia, capture: { line_comment: :line_comment }
|
|
92
93
|
#
|
|
93
|
-
def skip(rule_name, capture: nil, whitespace: nil)
|
|
94
|
+
def skip(rule_name, capture: nil, whitespace: nil, exempt: nil)
|
|
94
95
|
@skip_rule_name = rule_name
|
|
95
96
|
@skip_captures = capture
|
|
96
97
|
@skip_whitespace = whitespace
|
|
98
|
+
@skip_exempt = exempt
|
|
97
99
|
nil
|
|
98
100
|
end
|
|
99
101
|
|
|
@@ -114,6 +116,12 @@ module Parsanol
|
|
|
114
116
|
def skip_whitespace
|
|
115
117
|
@skip_whitespace
|
|
116
118
|
end
|
|
119
|
+
|
|
120
|
+
# The declared exemption list (rule names whose bodies build
|
|
121
|
+
# without injection), or nil — see {.skip}.
|
|
122
|
+
def skip_exempt
|
|
123
|
+
@skip_exempt
|
|
124
|
+
end
|
|
117
125
|
end
|
|
118
126
|
|
|
119
127
|
# Delegates matching to the root rule.
|
data/lib/parsanol/skip.rb
CHANGED
|
@@ -20,8 +20,16 @@ module Parsanol
|
|
|
20
20
|
# capturer's leading literal attach to the next Named capture
|
|
21
21
|
# under `comments:` — the PARG `skip = trivia capture: comments`
|
|
22
22
|
# shape. Whitespace-shaped units never record.
|
|
23
|
+
#
|
|
24
|
+
# +exempt+ (parsanol-ruby#195) lists rule names whose bodies build
|
|
25
|
+
# WITHOUT injection — the DSL mirror of the PARG compiler's
|
|
26
|
+
# skip_exempt_names closure. Grammars that manage part of their
|
|
27
|
+
# trivia explicitly (expressir's remark rules) must not have
|
|
28
|
+
# wrappers inside those bodies: the wrapper would consume text the
|
|
29
|
+
# rule itself captures (a remark's own spaces) and record it as
|
|
30
|
+
# pending trivia, corrupting the value and double-representing it.
|
|
23
31
|
def self.inject(parser_instance, root_atom, skip_rule_name, captures: nil,
|
|
24
|
-
whitespace: nil)
|
|
32
|
+
whitespace: nil, exempt: nil)
|
|
25
33
|
skip_atom = parser_instance.__send__(skip_rule_name)
|
|
26
34
|
skip_atom = skip_atom.parslet while skip_atom.is_a?(Atoms::Entity)
|
|
27
35
|
|
|
@@ -33,7 +41,8 @@ module Parsanol
|
|
|
33
41
|
|
|
34
42
|
markers = capturer_markers(parser_instance, captures)
|
|
35
43
|
whitespace_kind = whitespace&.to_sym
|
|
36
|
-
|
|
44
|
+
exempt_names = exempt.nil? ? nil : Set.new(exempt.map(&:to_s))
|
|
45
|
+
injector = Injector.new(skip_atom, markers, whitespace_kind, exempt_names)
|
|
37
46
|
[injector.inject(root_atom, entry: true), skip_atom]
|
|
38
47
|
end
|
|
39
48
|
|
|
@@ -102,10 +111,11 @@ module Parsanol
|
|
|
102
111
|
# side observation). In-progress atoms mark a cycle re-entry and
|
|
103
112
|
# return raw, exactly as the path-scoped set did.
|
|
104
113
|
class Injector
|
|
105
|
-
def initialize(skip_atom, markers = nil, whitespace_kind = nil)
|
|
114
|
+
def initialize(skip_atom, markers = nil, whitespace_kind = nil, exempt_names = nil)
|
|
106
115
|
@skip_atom = skip_atom
|
|
107
116
|
@markers = markers
|
|
108
117
|
@whitespace_kind = whitespace_kind
|
|
118
|
+
@exempt_names = exempt_names
|
|
109
119
|
@wrapper_ids = {}.compare_by_identity
|
|
110
120
|
# Results keyed per injection context: the same shared atom
|
|
111
121
|
# can be reached both inside and outside repetition interiors,
|
|
@@ -199,30 +209,14 @@ module Parsanol
|
|
|
199
209
|
when Atoms::Ignored
|
|
200
210
|
Atoms::Ignored.new(walk(atom.wrapped_atom, mode))
|
|
201
211
|
when Atoms::Entity
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
|
|
207
|
-
# reference itself stays an Entity pointing at the injected
|
|
208
|
-
# body: inlining the body into every referencing site makes
|
|
209
|
-
# every rendered string (to_s, and failure messages built
|
|
210
|
-
# from inspect) expand the grammar exponentially on
|
|
211
|
-
# cross-referencing grammars — one failing Sequence rendered
|
|
212
|
-
# a multi-gigabyte message (parsanol-ruby#190).
|
|
213
|
-
begin
|
|
214
|
-
body = walk(atom.parslet, mode == :exempt ? :exempt : false)
|
|
215
|
-
if body.is_a?(Atoms::Lookahead)
|
|
216
|
-
# A whole-body lookahead keeps its #192 boundary visible
|
|
217
|
-
# to enclosing sequences (no wrapper before it).
|
|
218
|
-
body
|
|
219
|
-
else
|
|
220
|
-
ref = Atoms::Entity.new(atom.rule_name) { body }
|
|
221
|
-
ref.label = atom.label if atom.label
|
|
222
|
-
ref
|
|
223
|
-
end
|
|
224
|
-
rescue StandardError
|
|
212
|
+
# parsanol-ruby#195: exempt rules build without injection —
|
|
213
|
+
# the reference returns untouched, its body never walked.
|
|
214
|
+
# A wrapper inside a rule that explicitly manages trivia
|
|
215
|
+
# consumes text the rule itself captures.
|
|
216
|
+
if @exempt_names&.include?(atom.rule_name.to_s)
|
|
225
217
|
atom
|
|
218
|
+
else
|
|
219
|
+
inject_entity(atom, mode)
|
|
226
220
|
end
|
|
227
221
|
when Atoms::Str, Atoms::Re
|
|
228
222
|
mode ? atom : Atoms::Sequence.new(maybe, atom)
|
|
@@ -233,6 +227,32 @@ module Parsanol
|
|
|
233
227
|
@memo[mode][atom] = injected
|
|
234
228
|
injected
|
|
235
229
|
end
|
|
230
|
+
|
|
231
|
+
# Rule bodies own their injection when the grammar declares
|
|
232
|
+
# skip on every rule (PARG); in the DSL surface the root tree
|
|
233
|
+
# is walked once, so the resolved body is injected as a fresh
|
|
234
|
+
# injection scope (even inside a repetition interior — the
|
|
235
|
+
# d54a29e exception is for rule-reference children). The
|
|
236
|
+
# reference itself stays an Entity pointing at the injected
|
|
237
|
+
# body: inlining the body into every referencing site makes
|
|
238
|
+
# every rendered string (to_s, and failure messages built
|
|
239
|
+
# from inspect) expand the grammar exponentially on
|
|
240
|
+
# cross-referencing grammars — one failing Sequence rendered
|
|
241
|
+
# a multi-gigabyte message (parsanol-ruby#190).
|
|
242
|
+
def inject_entity(atom, mode)
|
|
243
|
+
body = walk(atom.parslet, mode == :exempt ? :exempt : false)
|
|
244
|
+
if body.is_a?(Atoms::Lookahead)
|
|
245
|
+
# A whole-body lookahead keeps its #192 boundary visible
|
|
246
|
+
# to enclosing sequences (no wrapper before it).
|
|
247
|
+
body
|
|
248
|
+
else
|
|
249
|
+
ref = Atoms::Entity.new(atom.rule_name) { body }
|
|
250
|
+
ref.label = atom.label if atom.label
|
|
251
|
+
ref
|
|
252
|
+
end
|
|
253
|
+
rescue StandardError
|
|
254
|
+
atom
|
|
255
|
+
end
|
|
236
256
|
end
|
|
237
257
|
end
|
|
238
258
|
end
|
data/lib/parsanol/version.rb
CHANGED