parsanol 1.3.5-aarch64-linux → 1.3.7-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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +48 -48
  3. data/lib/parsanol/ast_visitor.rb +1 -1
  4. data/lib/parsanol/atoms/alternative.rb +3 -2
  5. data/lib/parsanol/atoms/base.rb +12 -6
  6. data/lib/parsanol/atoms/can_flatten.rb +8 -8
  7. data/lib/parsanol/atoms/context.rb +23 -16
  8. data/lib/parsanol/atoms/custom.rb +2 -2
  9. data/lib/parsanol/atoms/dynamic.rb +1 -1
  10. data/lib/parsanol/atoms/infix.rb +10 -5
  11. data/lib/parsanol/atoms/lookahead.rb +7 -4
  12. data/lib/parsanol/atoms/re.rb +1 -1
  13. data/lib/parsanol/atoms/repetition.rb +29 -11
  14. data/lib/parsanol/atoms/sequence.rb +3 -2
  15. data/lib/parsanol/atoms/str.rb +9 -3
  16. data/lib/parsanol/atoms.rb +20 -20
  17. data/lib/parsanol/builder_callbacks.rb +2 -2
  18. data/lib/parsanol/cause.rb +2 -2
  19. data/lib/parsanol/context.rb +2 -2
  20. data/lib/parsanol/error_reporter.rb +5 -5
  21. data/lib/parsanol/expression/treetop.rb +17 -17
  22. data/lib/parsanol/expression.rb +1 -1
  23. data/lib/parsanol/fast_mode.rb +50 -12
  24. data/lib/parsanol/first_set.rb +1 -1
  25. data/lib/parsanol/grammar_builder.rb +10 -8
  26. data/lib/parsanol/incremental_parser.rb +13 -8
  27. data/lib/parsanol/interval_tree.rb +12 -3
  28. data/lib/parsanol/lazy_result.rb +2 -2
  29. data/lib/parsanol/mermaid.rb +12 -9
  30. data/lib/parsanol/native/batch_decoder.rb +13 -9
  31. data/lib/parsanol/native/dynamic.rb +7 -6
  32. data/lib/parsanol/native/parser.rb +7 -5
  33. data/lib/parsanol/native/serializer.rb +42 -42
  34. data/lib/parsanol/native/transformer.rb +55 -28
  35. data/lib/parsanol/native/types.rb +3 -3
  36. data/lib/parsanol/native.rb +26 -20
  37. data/lib/parsanol/optimizer.rb +6 -6
  38. data/lib/parsanol/optimizers/choice_optimizer.rb +1 -1
  39. data/lib/parsanol/optimizers/cut_inserter.rb +5 -2
  40. data/lib/parsanol/optimizers/lookahead_optimizer.rb +9 -3
  41. data/lib/parsanol/optimizers/quantifier_optimizer.rb +5 -5
  42. data/lib/parsanol/optimizers/sequence_optimizer.rb +1 -1
  43. data/lib/parsanol/options/zero_copy.rb +1 -1
  44. data/lib/parsanol/options.rb +1 -1
  45. data/lib/parsanol/parallel.rb +4 -3
  46. data/lib/parsanol/parser.rb +18 -16
  47. data/lib/parsanol/parslet.rb +7 -7
  48. data/lib/parsanol/pattern/binding.rb +1 -1
  49. data/lib/parsanol/pattern.rb +4 -1
  50. data/lib/parsanol/pool.rb +3 -3
  51. data/lib/parsanol/pools/buffer_pool.rb +2 -2
  52. data/lib/parsanol/pools/position_pool.rb +2 -2
  53. data/lib/parsanol/position.rb +1 -1
  54. data/lib/parsanol/result_builder.rb +4 -4
  55. data/lib/parsanol/result_stream.rb +10 -5
  56. data/lib/parsanol/slice.rb +11 -8
  57. data/lib/parsanol/source.rb +14 -9
  58. data/lib/parsanol/source_location.rb +1 -1
  59. data/lib/parsanol/streaming_parser.rb +3 -3
  60. data/lib/parsanol/string_view.rb +4 -1
  61. data/lib/parsanol/transform.rb +2 -2
  62. data/lib/parsanol/version.rb +1 -1
  63. data/lib/parsanol/wasm_parser.rb +1 -1
  64. data/lib/parsanol.rb +37 -39
  65. data/parsanol.gemspec +30 -30
  66. metadata +1 -1
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'parsanol/native/transformer'
3
+ require "parsanol/native/transformer"
4
4
 
5
5
  module Parsanol
6
6
  module Native
@@ -59,7 +59,7 @@ module Parsanol
59
59
  # @param slice_class [Class] The Slice class to use
60
60
  # @param grammar_atom [Parsanol::Atoms::Base] The grammar atom (unused, kept for API compat)
61
61
  # @return [Object] Transformed Ruby AST
62
- def decode_and_flatten(data, input, slice_class, grammar_atom)
62
+ def decode_and_flatten(data, input, slice_class, _grammar_atom)
63
63
  # Check if data is batch data (flat u64 array) or already a Ruby value
64
64
  if data.is_a?(Integer) || (data.is_a?(Array) && data.first.is_a?(Integer))
65
65
  # Batch data (flat u64 array) - decode first, then transform
@@ -84,11 +84,13 @@ module Parsanol
84
84
  case value
85
85
  when Array
86
86
  # Recursively process array elements
87
- processed = value.map { |v| join_consecutive_slices(v, slice_class, input) }
87
+ processed = value.map do |v|
88
+ join_consecutive_slices(v, slice_class, input)
89
+ end
88
90
 
89
91
  # Check if all non-nil elements are Slices
90
92
  non_nil = processed.compact
91
- if non_nil.all? { |v| v.is_a?(slice_class) }
93
+ if non_nil.all?(slice_class)
92
94
  # Check if slices are consecutive
93
95
  if slices_consecutive?(non_nil)
94
96
  # Join into single slice
@@ -129,7 +131,7 @@ module Parsanol
129
131
  last = slices.last
130
132
  total_length = last.offset + last.content.bytesize - first.offset
131
133
  content = input_bytes[first.offset, total_length]
132
- content = content.force_encoding('UTF-8') if content
134
+ content = content.force_encoding("UTF-8") if content
133
135
  slice_class.new(first.offset, content, input)
134
136
  end
135
137
 
@@ -156,7 +158,7 @@ module Parsanol
156
158
  bits = @data[@pos]
157
159
  @pos += 1
158
160
  # Convert IEEE 754 bits to float
159
- [bits].pack('Q').unpack1('D')
161
+ [bits].pack("Q").unpack1("D")
160
162
  when TAG_STRING
161
163
  offset = @data[@pos]
162
164
  length = @data[@pos + 1]
@@ -203,6 +205,7 @@ module Parsanol
203
205
 
204
206
  # Read key
205
207
  raise "Expected TAG_HASH_KEY, got #{tag}" unless tag == TAG_HASH_KEY
208
+
206
209
  @pos += 1
207
210
  key = decode_inline_string
208
211
 
@@ -228,22 +231,23 @@ module Parsanol
228
231
  def decode_inline_string_bytes(len)
229
232
  # Read u64 chunks
230
233
  chunks = (len + 7) / 8
231
- bytes = String.new(encoding: 'ASCII-8BIT', capacity: len)
234
+ bytes = String.new(encoding: "ASCII-8BIT", capacity: len)
232
235
  chunks.times do
233
236
  chunk = @data[@pos]
234
237
  @pos += 1
235
238
  8.times do |byte_idx|
236
239
  break if bytes.bytesize >= len
240
+
237
241
  bytes << ((chunk >> (byte_idx * 8)) & 0xFF)
238
242
  end
239
243
  end
240
244
 
241
- bytes.force_encoding('UTF-8')
245
+ bytes.force_encoding("UTF-8")
242
246
  end
243
247
 
244
248
  def create_slice(offset, length)
245
249
  content = @input_bytes[offset, length]
246
- content = content.force_encoding('UTF-8') if content
250
+ content = content.force_encoding("UTF-8") if content
247
251
  @slice_class.new(offset, content, @input)
248
252
  end
249
253
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require "json"
4
4
 
5
5
  module Parsanol
6
6
  module Native
@@ -49,13 +49,14 @@ module Parsanol
49
49
  #
50
50
  def register(block, description: nil)
51
51
  # Register with Rust FFI
52
- ffi_id = Native.register_callback(@next_id, description || "Ruby callback ##{@next_id}")
52
+ ffi_id = Native.register_callback(@next_id,
53
+ description || "Ruby callback ##{@next_id}")
53
54
 
54
55
  # Also keep a Ruby-side reference for GC safety
55
56
  @mutex.synchronize do
56
57
  @callbacks[ffi_id] = {
57
58
  block: block,
58
- description: description || "Ruby callback ##{ffi_id}"
59
+ description: description || "Ruby callback ##{ffi_id}",
59
60
  }
60
61
  end
61
62
 
@@ -136,7 +137,7 @@ module Parsanol
136
137
  ctx = DynamicContext.new(
137
138
  context[:input],
138
139
  context[:pos],
139
- context[:captures].transform_keys(&:to_sym)
140
+ context[:captures].transform_keys(&:to_sym),
140
141
  )
141
142
 
142
143
  # Call the block
@@ -207,7 +208,7 @@ module Parsanol
207
208
  # @return [String] The remaining input
208
209
  #
209
210
  def remaining
210
- @input[@pos..] || ''
211
+ @input[@pos..] || ""
211
212
  end
212
213
 
213
214
  # Check if at end of input
@@ -229,7 +230,7 @@ module Parsanol
229
230
  if length
230
231
  @input[@pos + start, length]
231
232
  else
232
- @input[@pos + start..]
233
+ @input[(@pos + start)..]
233
234
  end
234
235
  end
235
236
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'digest'
3
+ require "digest"
4
4
 
5
5
  module Parsanol
6
6
  module Native
@@ -17,11 +17,11 @@ module Parsanol
17
17
 
18
18
  @cached_available = begin
19
19
  # Try versioned path first (released gem), then non-versioned (local dev)
20
- ruby_version = RUBY_VERSION.split('.').take(2).join('.')
20
+ ruby_version = RUBY_VERSION.split(".").take(2).join(".")
21
21
  begin
22
22
  require "parsanol/#{ruby_version}/parsanol_native"
23
23
  rescue LoadError
24
- require 'parsanol/parsanol_native'
24
+ require "parsanol/parsanol_native"
25
25
  end
26
26
  Parsanol::Native.is_available
27
27
  rescue LoadError
@@ -54,7 +54,7 @@ module Parsanol
54
54
  def cache_stats
55
55
  {
56
56
  hash_cache_size: GRAMMAR_HASH_CACHE.size,
57
- grammar_cache_size: GRAMMAR_CACHE.size
57
+ grammar_cache_size: GRAMMAR_CACHE.size,
58
58
  }
59
59
  end
60
60
 
@@ -70,6 +70,7 @@ module Parsanol
70
70
  if visited[obj_id]
71
71
  return [:cycle, atom.class.name]
72
72
  end
73
+
73
74
  visited[obj_id] = true
74
75
 
75
76
  case atom
@@ -89,7 +90,8 @@ module Parsanol
89
90
  when ::Parsanol::Atoms::Named
90
91
  [:named, atom.name.to_s, atom_structure(atom.parslet, visited)]
91
92
  when ::Parsanol::Atoms::Lookahead
92
- [:lookahead, atom.positive, atom_structure(atom.bound_parslet, visited)]
93
+ [:lookahead, atom.positive,
94
+ atom_structure(atom.bound_parslet, visited)]
93
95
  else
94
96
  [:unknown, atom.class.name]
95
97
  end
@@ -78,9 +78,9 @@ module Parsanol
78
78
 
79
79
  def serialize_str(atom)
80
80
  {
81
- 'Str' => {
82
- 'pattern' => atom.str
83
- }
81
+ "Str" => {
82
+ "pattern" => atom.str,
83
+ },
84
84
  }
85
85
  end
86
86
 
@@ -90,46 +90,46 @@ module Parsanol
90
90
  pattern = atom.match
91
91
  pattern = ::Regexp.last_match(1) if pattern =~ /^\(\?[-mix]*:(.+)\)$/
92
92
  {
93
- 'Re' => {
94
- 'pattern' => pattern
95
- }
93
+ "Re" => {
94
+ "pattern" => pattern,
95
+ },
96
96
  }
97
97
  end
98
98
 
99
99
  def serialize_sequence(atom)
100
100
  atom_ids = atom.parslets.map { |p| serialize_atom(p) }
101
101
  {
102
- 'Sequence' => {
103
- 'atoms' => atom_ids
104
- }
102
+ "Sequence" => {
103
+ "atoms" => atom_ids,
104
+ },
105
105
  }
106
106
  end
107
107
 
108
108
  def serialize_alternative(atom)
109
109
  atom_ids = atom.alternatives.map { |p| serialize_atom(p) }
110
110
  {
111
- 'Alternative' => {
112
- 'atoms' => atom_ids
113
- }
111
+ "Alternative" => {
112
+ "atoms" => atom_ids,
113
+ },
114
114
  }
115
115
  end
116
116
 
117
117
  def serialize_repetition(atom)
118
118
  {
119
- 'Repetition' => {
120
- 'atom' => serialize_atom(atom.parslet),
121
- 'min' => atom.min,
122
- 'max' => atom.max
123
- }
119
+ "Repetition" => {
120
+ "atom" => serialize_atom(atom.parslet),
121
+ "min" => atom.min,
122
+ "max" => atom.max,
123
+ },
124
124
  }
125
125
  end
126
126
 
127
127
  def serialize_named(atom)
128
128
  {
129
- 'Named' => {
130
- 'name' => atom.name.to_s,
131
- 'atom' => serialize_atom(atom.parslet)
132
- }
129
+ "Named" => {
130
+ "name" => atom.name.to_s,
131
+ "atom" => serialize_atom(atom.parslet),
132
+ },
133
133
  }
134
134
  end
135
135
 
@@ -169,7 +169,7 @@ module Parsanol
169
169
  serialize_named(parslet)
170
170
  when Parsanol::Atoms::Entity
171
171
  # Nested entity - just reference it via serialize_atom
172
- { 'Entity' => { 'atom' => serialize_atom(parslet) } }
172
+ { "Entity" => { "atom" => serialize_atom(parslet) } }
173
173
  when Parsanol::Atoms::Lookahead
174
174
  serialize_lookahead(parslet)
175
175
  else
@@ -181,9 +181,9 @@ module Parsanol
181
181
  else
182
182
  # If the entity's block returns nil, create a placeholder that will fail
183
183
  @atoms[atom_id] = {
184
- 'Str' => {
185
- 'pattern' => "\x00__UNIMPLEMENTED_ENTITY_#{atom.name}__"
186
- }
184
+ "Str" => {
185
+ "pattern" => "\x00__UNIMPLEMENTED_ENTITY_#{atom.name}__",
186
+ },
187
187
  }
188
188
  end
189
189
  atom_id
@@ -191,10 +191,10 @@ module Parsanol
191
191
 
192
192
  def serialize_lookahead(atom)
193
193
  {
194
- 'Lookahead' => {
195
- 'atom' => serialize_atom(atom.bound_parslet),
196
- 'positive' => atom.positive
197
- }
194
+ "Lookahead" => {
195
+ "atom" => serialize_atom(atom.bound_parslet),
196
+ "positive" => atom.positive,
197
+ },
198
198
  }
199
199
  end
200
200
 
@@ -202,10 +202,10 @@ module Parsanol
202
202
  # Capture stores matched text for later reference by Dynamic atoms.
203
203
  # Now properly serialized for native parser support (parsanol-rs 0.3.0+).
204
204
  {
205
- 'Capture' => {
206
- 'name' => atom.capture_key.to_s,
207
- 'atom' => serialize_atom(atom.inner_atom)
208
- }
205
+ "Capture" => {
206
+ "name" => atom.capture_key.to_s,
207
+ "atom" => serialize_atom(atom.inner_atom),
208
+ },
209
209
  }
210
210
  end
211
211
 
@@ -220,9 +220,9 @@ module Parsanol
220
220
  return serialize_unknown(atom) unless inner
221
221
 
222
222
  {
223
- 'Scope' => {
224
- 'atom' => serialize_atom(inner)
225
- }
223
+ "Scope" => {
224
+ "atom" => serialize_atom(inner),
225
+ },
226
226
  }
227
227
  end
228
228
 
@@ -232,9 +232,9 @@ module Parsanol
232
232
  callback_id = Parsanol::Native::Dynamic.register(atom.block)
233
233
 
234
234
  {
235
- 'Dynamic' => {
236
- 'callback_id' => callback_id
237
- }
235
+ "Dynamic" => {
236
+ "callback_id" => callback_id,
237
+ },
238
238
  }
239
239
  end
240
240
 
@@ -242,9 +242,9 @@ module Parsanol
242
242
  # For unsupported atom types, create a placeholder
243
243
  # This will cause a parse error at runtime
244
244
  {
245
- 'Str' => {
246
- 'pattern' => '' # Empty pattern that will never match
247
- }
245
+ "Str" => {
246
+ "pattern" => "", # Empty pattern that will never match
247
+ },
248
248
  }
249
249
  end
250
250
  end
@@ -18,9 +18,9 @@ module Parsanol
18
18
  #
19
19
  class AstTransformer
20
20
  # Frozen string constants for tag comparisons (avoid allocations)
21
- SEQUENCE_TAG = ':sequence'
22
- REPETITION_TAG = ':repetition'
23
- EMPTY_STRING = ''
21
+ SEQUENCE_TAG = ":sequence"
22
+ REPETITION_TAG = ":repetition"
23
+ EMPTY_STRING = ""
24
24
  EMPTY_ARRAY = [].freeze
25
25
  EMPTY_HASH = {}.freeze
26
26
 
@@ -62,7 +62,7 @@ module Parsanol
62
62
  # Check if this is a tagged array from native parser
63
63
  # Native parser produces Symbol tags: [:sequence, item1, item2, ...]
64
64
  first = arr.first
65
- if first == SEQUENCE_SYM || first == SEQUENCE_TAG
65
+ if [SEQUENCE_SYM, SEQUENCE_TAG].include?(first)
66
66
  # Optimized: transform items starting from index 1
67
67
  # Avoid creating arr[1..] slice
68
68
  len = arr.length
@@ -75,7 +75,7 @@ module Parsanol
75
75
  i += 1
76
76
  end
77
77
  flatten_sequence(items)
78
- elsif first == REPETITION_SYM || first == REPETITION_TAG
78
+ elsif [REPETITION_SYM, REPETITION_TAG].include?(first)
79
79
  # Optimized: transform items starting from index 1
80
80
  len = arr.length
81
81
  return EMPTY_ARRAY if len == 1
@@ -87,7 +87,7 @@ module Parsanol
87
87
  i += 1
88
88
  end
89
89
  flatten_repetition(items)
90
- elsif first.is_a?(Symbol) || (first.is_a?(String) && first.start_with?(':'))
90
+ elsif first.is_a?(Symbol) || (first.is_a?(String) && first.start_with?(":"))
91
91
  # Other tagged arrays - pass through
92
92
  arr.map { |item| transform(item) }
93
93
  else
@@ -119,13 +119,15 @@ module Parsanol
119
119
 
120
120
  # Check if value is a tagged repetition from native parser
121
121
  is_tagged_repetition = value.is_a?(Array) && !value.empty? &&
122
- value.first.is_a?(String) && value.first == REPETITION_TAG
122
+ value.first.is_a?(String) && value.first == REPETITION_TAG
123
123
 
124
124
  # Check RAW value for repetition pattern BEFORE transformation
125
125
  # Array with items that all have the parent key
126
126
  # e.g., [{x: 1}, {x: 2}] where parent key is :x
127
127
  is_raw_array_repetition = value.is_a?(Array) && !value.empty? &&
128
- value.all? { |item| item.is_a?(Hash) && item.keys.length == 1 && item.key?(key) }
128
+ value.all? do |item|
129
+ item.is_a?(Hash) && item.keys.length == 1 && item.key?(key)
130
+ end
129
131
 
130
132
  # Empty array from native parser is a repetition result (not a sequence)
131
133
  # Sequences produce arrays of arrays like [[], []], not empty arrays
@@ -134,12 +136,15 @@ module Parsanol
134
136
  # Special handling for arrays that look like character repetitions
135
137
  # (arrays of single-character Slices/strings should be joined)
136
138
  if transformed.is_a?(Array) && !transformed.empty? &&
137
- transformed.all? { |item| slice_or_string?(item) && item_length(item) == 1 }
139
+ transformed.all? do |item|
140
+ slice_or_string?(item) && item_length(item) == 1
141
+ end
138
142
  # Join preserving position from first Slice
139
143
  first_slice = transformed.find { |i| i.is_a?(::Parsanol::Slice) }
140
144
  content = transformed.map { |i| slice_content(i) }.join
141
145
  transformed = if first_slice
142
- ::Parsanol::Slice.new(first_slice.offset, content, first_slice.input)
146
+ ::Parsanol::Slice.new(first_slice.offset, content,
147
+ first_slice.input)
143
148
  else
144
149
  content
145
150
  end
@@ -148,9 +153,9 @@ module Parsanol
148
153
  # Check for UNTAGGED repetition pattern (native output):
149
154
  # If array items all have the same key as parent, it's a repetition
150
155
  is_transformed_repetition = transformed.is_a?(Array) && !transformed.empty? &&
151
- transformed.all? do |item|
152
- item.is_a?(Hash) && item.keys.length == 1 && item.key?(sym_key)
153
- end
156
+ transformed.all? do |item|
157
+ item.is_a?(Hash) && item.keys.length == 1 && item.key?(sym_key)
158
+ end
154
159
 
155
160
  is_repetition = is_tagged_repetition || is_raw_array_repetition || is_transformed_repetition || is_empty_repetition
156
161
 
@@ -184,7 +189,9 @@ module Parsanol
184
189
  if transformed.empty?
185
190
  { sym_key => EMPTY_ARRAY }
186
191
  # Check if items already have the same key (avoid double-wrapping)
187
- elsif transformed.all? { |item| item.is_a?(Hash) && item.key?(sym_key) }
192
+ elsif transformed.all? do |item|
193
+ item.is_a?(Hash) && item.key?(sym_key)
194
+ end
188
195
  { sym_key => transformed }
189
196
  else
190
197
  # Wrap each item with the name
@@ -207,7 +214,9 @@ module Parsanol
207
214
  # We can't tell from the value alone, so we return empty Slice (sequence semantics)
208
215
  # The repetition detection in transform_single_key_hash will handle the other case
209
216
  { sym_key => ::Parsanol::Slice.new(0, EMPTY_STRING, nil) }
210
- elsif transformed.all? { |v| v.is_a?(Hash) && v.keys.length == 1 && v.key?(sym_key) }
217
+ elsif transformed.all? do |v|
218
+ v.is_a?(Hash) && v.keys.length == 1 && v.key?(sym_key)
219
+ end
211
220
  # Items already have the parent key (repetition pattern) - keep as-is
212
221
  { sym_key => transformed }
213
222
  elsif transformed.all?(Hash)
@@ -229,13 +238,15 @@ module Parsanol
229
238
  sym_key = cached_symbol(key)
230
239
 
231
240
  is_repetition = value.is_a?(Array) && !value.empty? &&
232
- value.first.is_a?(String) && value.first == REPETITION_TAG
241
+ value.first.is_a?(String) && value.first == REPETITION_TAG
233
242
 
234
243
  transformed = transform(value)
235
244
 
236
245
  result[sym_key] = if is_repetition
237
246
  if transformed.is_a?(Array)
238
- if transformed.all? { |item| item.is_a?(Hash) && item.key?(sym_key) }
247
+ if transformed.all? do |item|
248
+ item.is_a?(Hash) && item.key?(sym_key)
249
+ end
239
250
  transformed
240
251
  else
241
252
  transformed.map { |item| { sym_key => item } }
@@ -308,7 +319,7 @@ module Parsanol
308
319
  else
309
320
  # Check if array contains only hashes (repetition wrapper pattern)
310
321
  # In this case, merge the inner hashes into merged_hash
311
- non_hash_items = item.reject { |sub| sub.is_a?(Hash) }
322
+ non_hash_items = item.grep_v(Hash)
312
323
  all_items_are_hashes = non_hash_items.empty?
313
324
 
314
325
  if all_items_are_hashes
@@ -399,12 +410,16 @@ module Parsanol
399
410
  # REPETITION pattern (same keys like entity_decl): keep as array
400
411
  # WRAPPER pattern (different keys like spaces vs schemaDecl): merge
401
412
  first_inner_keys = items.first[wrapper_key].keys.to_set
402
- all_same_keys = items.all? { |item| item[wrapper_key].keys.to_set == first_inner_keys }
413
+ items.all? do |item|
414
+ item[wrapper_key].keys.to_set == first_inner_keys
415
+ end
403
416
 
404
417
  # Check if items have single keys or multiple keys
405
418
  # - Single key items with repeated outer key = true repetition (keep array)
406
419
  # - Multiple key items with repeated outer key = duplicate labels in sequence (merge)
407
- max_keys_per_item = items.map { |item| item.is_a?(Hash) ? item.keys.length : 0 }.max || 0
420
+ max_keys_per_item = items.map do |item|
421
+ item.is_a?(Hash) ? item.keys.length : 0
422
+ end.max || 0
408
423
 
409
424
  # Check if inner values are hashes with different keys
410
425
  # This distinguishes:
@@ -412,9 +427,13 @@ module Parsanol
412
427
  # - Duplicate labels: [{group: {char: 'a'}}, {group: {digit: '5'}}] - inner is hash with different keys
413
428
  inner_keys_all_same = true
414
429
  first_inner_keys = nil
415
- if items.all? { |item| item.is_a?(Hash) && item[wrapper_key].is_a?(Hash) }
430
+ if items.all? do |item|
431
+ item.is_a?(Hash) && item[wrapper_key].is_a?(Hash)
432
+ end
416
433
  first_inner_keys = items.first[wrapper_key].keys.to_set
417
- inner_keys_all_same = items.all? { |item| item[wrapper_key].keys.to_set == first_inner_keys }
434
+ inner_keys_all_same = items.all? do |item|
435
+ item[wrapper_key].keys.to_set == first_inner_keys
436
+ end
418
437
  end
419
438
 
420
439
  # DUPLICATE LABELS IN SEQUENCE: multiple keys per item with repeated outer key
@@ -426,7 +445,9 @@ module Parsanol
426
445
 
427
446
  # Check if inner hashes have the same keys or different keys
428
447
  first_inner_keys ||= items.first[wrapper_key].keys.to_set
429
- all_same_keys = items.all? { |item| item[wrapper_key].keys.to_set == first_inner_keys }
448
+ all_same_keys = items.all? do |item|
449
+ item[wrapper_key].keys.to_set == first_inner_keys
450
+ end
430
451
 
431
452
  if has_duplicate_labels
432
453
  # DUPLICATE LABELS PATTERN: items have multiple keys with repeated outer key
@@ -436,7 +457,7 @@ module Parsanol
436
457
  merged = {}
437
458
  items.each do |item|
438
459
  item.each do |k, v|
439
- merged[k] = v # Last value wins
460
+ merged[k] = v # Last value wins
440
461
  end
441
462
  end
442
463
  # Return only the wrapper key with its last value
@@ -475,11 +496,14 @@ module Parsanol
475
496
  if slice_or_string_parts.any?
476
497
  # Join Slices/strings, preserving position from first Slice
477
498
  first_slice = slice_or_string_parts.find { |i| i.is_a?(::Parsanol::Slice) }
478
- content = slice_or_string_parts.map { |i| i.is_a?(::Parsanol::Slice) ? i.content : i }.join
499
+ content = slice_or_string_parts.map do |i|
500
+ i.is_a?(::Parsanol::Slice) ? i.content : i
501
+ end.join
479
502
 
480
503
  if first_slice
481
504
  # Create new Slice with combined content, preserving position from first
482
- return ::Parsanol::Slice.new(first_slice.offset, content, first_slice.input)
505
+ return ::Parsanol::Slice.new(first_slice.offset, content,
506
+ first_slice.input)
483
507
  else
484
508
  # All plain strings (shouldn't happen with new decode_flat, but handle it)
485
509
  return slice_or_string_parts.length == 1 ? slice_or_string_parts.first : content
@@ -520,11 +544,14 @@ module Parsanol
520
544
  # If all Slices or strings, join them preserving position from first Slice
521
545
  if all_slices_or_strings
522
546
  first_slice = flat_items.find { |i| i.is_a?(::Parsanol::Slice) }
523
- content = flat_items.map { |i| i.is_a?(::Parsanol::Slice) ? i.content : i }.join
547
+ content = flat_items.map do |i|
548
+ i.is_a?(::Parsanol::Slice) ? i.content : i
549
+ end.join
524
550
 
525
551
  if first_slice
526
552
  # Create new Slice with combined content, preserving position from first
527
- ::Parsanol::Slice.new(first_slice.offset, content, first_slice.input)
553
+ ::Parsanol::Slice.new(first_slice.offset, content,
554
+ first_slice.input)
528
555
  else
529
556
  # All plain strings (shouldn't happen with new decode_flat, but handle it)
530
557
  content
@@ -19,9 +19,9 @@ module Parsanol
19
19
  TAG_INLINE_STRING = 0x0A
20
20
 
21
21
  # Frozen string constants for transformer (avoid allocations)
22
- SEQUENCE_TAG = ':sequence'
23
- REPETITION_TAG = ':repetition'
24
- EMPTY_STRING = ''
22
+ SEQUENCE_TAG = ":sequence"
23
+ REPETITION_TAG = ":repetition"
24
+ EMPTY_STRING = ""
25
25
  EMPTY_ARRAY = [].freeze
26
26
  EMPTY_HASH = {}.freeze
27
27
  end