parsanol 1.3.81-aarch64-linux → 1.3.83-aarch64-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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0c85bb6ed37d4b272d3c2232453260631a9340aba985adaf43ff8d6be9f8b3a
4
- data.tar.gz: 591430101290d9e3cfcba3b3f725263864f2daa5760c1d083276d8c64fc34dce
3
+ metadata.gz: 9d77e5143a8bb36ec74223556d54b300f26b80adc0c28c1c192712b69374ca17
4
+ data.tar.gz: 49a1036eb86a943aebc9ec08e37c0f5fbf6b302480f928e36d91b502d327f03f
5
5
  SHA512:
6
- metadata.gz: 02cccef1f811a8d99a9da8dc3f19d0c2fa3d5211faa25d779d2f71b26b9fb51135649708171f2fbf9398ffb815d657b2bd2b130039f97b6a155412c059698c76
7
- data.tar.gz: 643d76aabbeab125985c8841ec79eb5c9481f68b11a9e27e6922b7781238f9eb0ff42a7b4f55d6bb216bc8b9766167db0ce4a63b03ddce64f06bfc5706261d38
6
+ metadata.gz: 718f9b08d6cba2a180e07f7e59783fae4c322dc27c991be78a3a814d7bfce669620dc3d459eb8d6399428ee60f543f33dba9eeda4412306b04466cdc0d02aae3
7
+ data.tar.gz: 6683bd143dc36f15c0c964d1b3b95a0d0bce3801fa1535f7a3672560899466bd4ed2deaecd9f0f1da7bee71eeab4afc8e493b1fe3628cd9e7f3c8cc74d362f3b
@@ -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: 40098992 bytes
4
- sha256: 7a192ca31b988dcb7ff09aae8c7f198366ec949ba6d643c6345f7697b733f4f2
3
+ size: 40172512 bytes
4
+ sha256: 359ae26a1f3e2f13c0aba4eceb7f03653e1189a5cc9b8173215ce9861a0005f1
@@ -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: 40334088 bytes
4
- sha256: a59a1e8331a9da417e8dbaefdefa59632c43094cf0c34c63a71fec3cf4bad616
3
+ size: 40407592 bytes
4
+ sha256: e3c5a2bade542954a2a4ca002ac51324b73d70bb35af0083c10708c878616411
@@ -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: 40329752 bytes
4
- sha256: bb046626dcb81c3ee91852c1bc42c8aa98f931abf6594e9d023331515d300562
3
+ size: 40407448 bytes
4
+ sha256: 0036df3e435906993e26973bd69a3330171b69ef8e9e001ed29760bb54ffffee
@@ -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: 40339776 bytes
4
- sha256: 43352d26128f698a3e233303d8bb8844784c12a369f9ca577a476818a1c8b5cd
3
+ size: 40417504 bytes
4
+ sha256: 6bcf4690e25658d3050046c39e9d496bd45e0d92ac66b9acb7eabb077fad2c5d
@@ -18,14 +18,25 @@ module Parsanol
18
18
  def initialize(*components)
19
19
  super()
20
20
  @parslets = components
21
-
22
- # Pre-built error message
23
- @fail_msg = "Failed to match sequence (#{inspect})"
21
+ # The failure message renders LAZILY (error_msgs): constructing
22
+ # sequences is hot-path work for the builder/injector, and
23
+ # stringifying the whole subtree eagerly is exponential on
24
+ # shared cross-referencing grammars (parsanol-ruby#180 side
25
+ # observation: a 25-rule Fibonacci-shaped grammar took 6s).
26
+ @fail_msg = nil
24
27
  end
25
28
 
26
29
  # Error messages hash (for compatibility)
27
30
  def error_msgs
28
- { failed: @fail_msg }
31
+ { failed: fail_msg }
32
+ end
33
+
34
+ # Rendered lazily (see initialize); construction is hot-path
35
+ # builder/injector work and stringifying the whole subtree
36
+ # eagerly is exponential on shared cross-referencing grammars
37
+ # (parsanol-ruby#180 side observation).
38
+ def fail_msg
39
+ @fail_msg ||= "Failed to match sequence (#{inspect})"
29
40
  end
30
41
 
31
42
  # Appends a parser to this sequence with flattening.
@@ -104,7 +115,7 @@ module Parsanol
104
115
  # Single element sequence
105
116
  def match_single(parser, source, context, consume_all)
106
117
  success, value = parser.apply(source, context, consume_all)
107
- return context.err(self, source, @fail_msg, [value]) unless success
118
+ return context.err(self, source, fail_msg, [value]) unless success
108
119
 
109
120
  ok([:sequence, value])
110
121
  end
@@ -112,10 +123,10 @@ module Parsanol
112
123
  # Two-element sequence
113
124
  def match_pair(p1, p2, source, context, consume_all)
114
125
  success, v1 = p1.apply(source, context, false)
115
- return context.err(self, source, @fail_msg, [v1]) unless success
126
+ return context.err(self, source, fail_msg, [v1]) unless success
116
127
 
117
128
  success, v2 = p2.apply(source, context, consume_all)
118
- return context.err(self, source, @fail_msg, [v2]) unless success
129
+ return context.err(self, source, fail_msg, [v2]) unless success
119
130
 
120
131
  ok([:sequence, v1, v2])
121
132
  end
@@ -123,13 +134,13 @@ module Parsanol
123
134
  # Three-element sequence
124
135
  def match_triple(p1, p2, p3, source, context, consume_all)
125
136
  success, v1 = p1.apply(source, context, false)
126
- return context.err(self, source, @fail_msg, [v1]) unless success
137
+ return context.err(self, source, fail_msg, [v1]) unless success
127
138
 
128
139
  success, v2 = p2.apply(source, context, false)
129
- return context.err(self, source, @fail_msg, [v2]) unless success
140
+ return context.err(self, source, fail_msg, [v2]) unless success
130
141
 
131
142
  success, v3 = p3.apply(source, context, consume_all)
132
- return context.err(self, source, @fail_msg, [v3]) unless success
143
+ return context.err(self, source, fail_msg, [v3]) unless success
133
144
 
134
145
  ok([:sequence, v1, v2, v3])
135
146
  end
@@ -148,7 +159,7 @@ module Parsanol
148
159
  success, value = components[idx].apply(source, context, must_consume)
149
160
 
150
161
  unless success
151
- return context.err(self, source, @fail_msg, [value])
162
+ return context.err(self, source, fail_msg, [value])
152
163
  end
153
164
 
154
165
  result[idx + 1] = value
@@ -28,12 +28,16 @@ module Parsanol
28
28
  # +capturers+ maps leading-literal marker -> kind label (the
29
29
  # compiler derives markers from each capturer rule's first Str
30
30
  # atom, e.g. "//" => :line_comment); units not matching any
31
- # marker are whitespace-shaped and stay unrecorded.
32
- attr_reader :capturers
31
+ # marker are whitespace-shaped and stay unrecorded — unless
32
+ # +whitespace_kind+ is declared (parsanol-ruby#180): every
33
+ # unit then records under that label, giving source-preserving
34
+ # grammars the full ordered trivia stream.
35
+ attr_reader :capturers, :whitespace_kind
33
36
 
34
- def initialize(atom, capturers)
37
+ def initialize(atom, capturers, whitespace_kind: nil)
35
38
  super(atom)
36
39
  @capturers = capturers
40
+ @whitespace_kind = whitespace_kind
37
41
  end
38
42
 
39
43
  def apply(source, context, consume_all)
@@ -55,16 +59,16 @@ module Parsanol
55
59
  private
56
60
 
57
61
  def record(source, context, start_pos, end_pos)
58
- return unless @capturers
59
-
60
62
  text = source.input[start_pos...end_pos]
61
63
  # A trivia unit may lead with whitespace before the comment
62
64
  # shape; markers test the comment head.
63
65
  stripped = text.lstrip
64
- marker, label = @capturers.find { |m, _label| stripped.start_with?(m) }
65
- return unless marker
66
-
67
- context.push_captured_trivia(label, stripped.strip)
66
+ marker, label = @capturers&.find { |m, _label| stripped.start_with?(m) }
67
+ if marker
68
+ context.push_captured_trivia(label, stripped.strip)
69
+ elsif @whitespace_kind
70
+ context.push_captured_trivia(@whitespace_kind, text)
71
+ end
68
72
  end
69
73
  end
70
74
  end
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/lib/parsanol/native/libparsanol.so
3
- size: 35898616 bytes
4
- sha256: f604a8ab076f360350c20ba56652a6033209d14f91b7810142c3cbcd04bb7021
3
+ size: 35976496 bytes
4
+ sha256: eae3f380df547353fdda1c2d515d9dd59355b36250c9af6152459d18df0d0d63
@@ -332,12 +332,16 @@ module Parsanol
332
332
 
333
333
  def serialize_trivia_capture(atom)
334
334
  inner_id = serialize_atom(atom.wrapped_atom)
335
- {
336
- "TriviaCapture" => {
337
- "atom" => inner_id,
338
- "rules" => atom.capturers.map { |marker, label| [marker, label.to_s] },
339
- },
335
+ payload = {
336
+ "atom" => inner_id,
337
+ "rules" => atom.capturers.map { |marker, label| [marker, label.to_s] },
340
338
  }
339
+ # parsanol-ruby#180: the optional whitespace kind — units that
340
+ # match no marker record under it (source-preserving grammars).
341
+ # Optional on the wire: engines predating it deserialize and
342
+ # simply don't record whitespace units (documented caveat).
343
+ payload["whitespace"] = atom.whitespace_kind.to_s if atom.whitespace_kind
344
+ { "TriviaCapture" => payload }
341
345
  end
342
346
 
343
347
  def serialize_trivia(atom)
@@ -54,7 +54,8 @@ module Parsanol
54
54
  skip_name = self.class.skip_rule_name
55
55
  if skip_name
56
56
  Parsanol::Skip.inject(self, atom, skip_name,
57
- captures: self.class.skip_captures)[0]
57
+ captures: self.class.skip_captures,
58
+ whitespace: self.class.skip_whitespace)[0]
58
59
  else
59
60
  atom
60
61
  end
@@ -89,9 +90,10 @@ module Parsanol
89
90
  # rule(:line_comment) { str("//") >> match(/[^\n]/).repeat }
90
91
  # skip :trivia, capture: { line_comment: :line_comment }
91
92
  #
92
- def skip(rule_name, capture: nil)
93
+ def skip(rule_name, capture: nil, whitespace: nil)
93
94
  @skip_rule_name = rule_name
94
95
  @skip_captures = capture
96
+ @skip_whitespace = whitespace
95
97
  nil
96
98
  end
97
99
 
@@ -106,6 +108,12 @@ module Parsanol
106
108
  def skip_captures
107
109
  @skip_captures
108
110
  end
111
+
112
+ # The declared whitespace kind (units matching no capturer
113
+ # record under it), or nil — see {.skip}.
114
+ def skip_whitespace
115
+ @skip_whitespace
116
+ end
109
117
  end
110
118
 
111
119
  # Delegates matching to the root rule.
data/lib/parsanol/skip.rb CHANGED
@@ -20,7 +20,8 @@ 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
- def self.inject(parser_instance, root_atom, skip_rule_name, captures: nil)
23
+ def self.inject(parser_instance, root_atom, skip_rule_name, captures: nil,
24
+ whitespace: nil)
24
25
  skip_atom = parser_instance.__send__(skip_rule_name)
25
26
  skip_atom = skip_atom.parslet while skip_atom.is_a?(Atoms::Entity)
26
27
 
@@ -31,7 +32,8 @@ module Parsanol
31
32
  end
32
33
 
33
34
  markers = capturer_markers(parser_instance, captures)
34
- injector = Injector.new(skip_atom, markers)
35
+ whitespace_kind = whitespace&.to_sym
36
+ injector = Injector.new(skip_atom, markers, whitespace_kind)
35
37
  [injector.inject(root_atom, entry: true), skip_atom]
36
38
  end
37
39
 
@@ -92,15 +94,28 @@ module Parsanol
92
94
  # clean — the wrapper is Trivia: Ignored + diagnostics
93
95
  # transparency), bare terminals gain a leading wrapper, and the
94
96
  # entry root takes leading and trailing wrappers.
97
+ # The walk is a pure function of the atom subtree, so results are
98
+ # memoized: dense cross-referencing grammars (expressir's ~250
99
+ # rules) revisit shared subtrees once per referencing path, which
100
+ # made the path-scoped visited-set exponential — a 25-rule
101
+ # Fibonacci-shaped grammar took 6.2s to build (parsanol-ruby#180
102
+ # side observation). In-progress atoms mark a cycle re-entry and
103
+ # return raw, exactly as the path-scoped set did.
95
104
  class Injector
96
- def initialize(skip_atom, markers = nil)
105
+ def initialize(skip_atom, markers = nil, whitespace_kind = nil)
97
106
  @skip_atom = skip_atom
98
107
  @markers = markers
108
+ @whitespace_kind = whitespace_kind
99
109
  @wrapper_ids = {}.compare_by_identity
110
+ # Results keyed per injection context: the same shared atom
111
+ # can be reached both inside and outside repetition interiors,
112
+ # and the refinement injects differently there.
113
+ @memo = { true => {}.compare_by_identity, false => {}.compare_by_identity }
114
+ @in_progress = { true => {}.compare_by_identity, false => {}.compare_by_identity }
100
115
  end
101
116
 
102
117
  def inject(atom, entry: false)
103
- injected = walk(atom, {})
118
+ injected = walk(atom, false)
104
119
  return injected unless entry
105
120
 
106
121
  Atoms::Sequence.new(maybe, injected, maybe)
@@ -112,8 +127,9 @@ module Parsanol
112
127
  @maybe ||= begin
113
128
  inner = Atoms::Repetition.new(@skip_atom, 0, 1)
114
129
  wrapper =
115
- if @markers
116
- Atoms::TriviaCapture.new(inner, @markers)
130
+ if @markers || @whitespace_kind
131
+ Atoms::TriviaCapture.new(inner, @markers,
132
+ whitespace_kind: @whitespace_kind)
117
133
  else
118
134
  Atoms::Trivia.new(inner)
119
135
  end
@@ -126,50 +142,67 @@ module Parsanol
126
142
  @wrapper_ids.key?(atom)
127
143
  end
128
144
 
129
- def walk(atom, visited)
130
- return atom if visited.key?(atom)
145
+ # +rep_interior+: the d54a29e refinement (PARG's owner-set
146
+ # semantics, now aligned for the DSL): no injection inside
147
+ # repetition interiors except through rule references — their
148
+ # bodies are fresh injection scopes. Wrapping char-level
149
+ # terminals inside a repetition consumes trivia within the
150
+ # ENCLOSING capture's scope and mis-attaches recorded units when
151
+ # the iteration fails.
152
+ def walk(atom, rep_interior)
153
+ cached = @memo[rep_interior][atom]
154
+ return cached if cached
155
+ return atom if @in_progress[rep_interior].key?(atom)
131
156
  return atom if atom.equal?(@skip_atom)
132
157
 
133
- visited = visited.merge(atom => true)
134
- case atom
135
- when Atoms::Sequence
136
- out = []
137
- atom.parslets.each do |child|
138
- injected = walk(child, visited)
139
- out << maybe unless wrapper?(injected) || injected.is_a?(Atoms::Trivia)
140
- out << injected
141
- end
142
- out.length == 1 ? out.first : Atoms::Sequence.new(*out)
143
- when Atoms::Alternative
144
- Atoms::Alternative.new(*atom.alternatives.map { |a| walk(a, visited) })
145
- when Atoms::Repetition
146
- if wrapper?(atom)
147
- atom
158
+ @in_progress[rep_interior][atom] = true
159
+ injected =
160
+ case atom
161
+ when Atoms::Sequence
162
+ out = []
163
+ atom.parslets.each do |child|
164
+ walked = walk(child, rep_interior)
165
+ unless rep_interior || wrapper?(walked) || walked.is_a?(Atoms::Trivia)
166
+ out << maybe
167
+ end
168
+ out << walked
169
+ end
170
+ out.length == 1 ? out.first : Atoms::Sequence.new(*out)
171
+ when Atoms::Alternative
172
+ Atoms::Alternative.new(*atom.alternatives.map { |a| walk(a, rep_interior) })
173
+ when Atoms::Repetition
174
+ if wrapper?(atom)
175
+ atom
176
+ else
177
+ Atoms::Repetition.new(walk(atom.parslet, true), atom.min, atom.max,
178
+ atom.result_tag)
179
+ end
180
+ when Atoms::Named
181
+ Atoms::Named.new(walk(atom.parslet, rep_interior), atom.name)
182
+ when Atoms::Lookahead
183
+ Atoms::Lookahead.new(walk(atom.bound_parslet, rep_interior), atom.positive)
184
+ when Atoms::Ignored
185
+ Atoms::Ignored.new(walk(atom.wrapped_atom, rep_interior))
186
+ when Atoms::Entity
187
+ # Rule bodies own their injection when the grammar declares
188
+ # skip on every rule (PARG); in the DSL surface the root tree
189
+ # is walked once, so descend through resolved bodies with
190
+ # the in-progress set guarding recursion. A rule body is a
191
+ # fresh injection scope even inside a repetition interior —
192
+ # the d54a29e exception is for rule-reference children.
193
+ begin
194
+ walk(atom.parslet, false)
195
+ rescue StandardError
196
+ atom
197
+ end
198
+ when Atoms::Str, Atoms::Re
199
+ rep_interior ? atom : Atoms::Sequence.new(maybe, atom)
148
200
  else
149
- Atoms::Repetition.new(walk(atom.parslet, visited), atom.min, atom.max,
150
- atom.result_tag)
151
- end
152
- when Atoms::Named
153
- Atoms::Named.new(walk(atom.parslet, visited), atom.name)
154
- when Atoms::Lookahead
155
- Atoms::Lookahead.new(walk(atom.bound_parslet, visited), atom.positive)
156
- when Atoms::Ignored
157
- Atoms::Ignored.new(walk(atom.wrapped_atom, visited))
158
- when Atoms::Entity
159
- # Rule bodies own their injection when the grammar declares
160
- # skip on every rule (PARG); in the DSL surface the root tree
161
- # is walked once, so descend through resolved bodies with the
162
- # identity visited-set guarding recursion.
163
- begin
164
- walk(atom.parslet, visited)
165
- rescue StandardError
166
201
  atom
167
202
  end
168
- when Atoms::Str, Atoms::Re
169
- Atoms::Sequence.new(maybe, atom)
170
- else
171
- atom
172
- end
203
+ @in_progress[rep_interior].delete(atom)
204
+ @memo[rep_interior][atom] = injected
205
+ injected
173
206
  end
174
207
  end
175
208
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.81"
4
+ VERSION = "1.3.83"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parsanol
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.81
4
+ version: 1.3.83
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Ribose Inc.