parsanol 1.3.44-x86_64-linux → 1.3.45-x86_64-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: 3462396a466396e4051285e9dfffad10efc49b495c3730018e360dc53765ce1e
4
- data.tar.gz: f3a4f6478ef3efacfd17b3532c7b3b044f18154f94743525a916ee248294a254
3
+ metadata.gz: 6ed8f95c36bf9e3dd85b870a405ccbcec380716c96289d93cd86b7c37dee85a7
4
+ data.tar.gz: 862a88b04453dff2b2d592cd0738f7a7f7eae8d33ebf8f12840d3866284a0b95
5
5
  SHA512:
6
- metadata.gz: e2fce0073aa484546216662e8cfa5752a94df0f38ccf2941851d4201634f936b352b6807bc5d6dcf3037caef723bfb1fb1c9e28262a87f1a750a1bf803129311
7
- data.tar.gz: eea570245005ac91445440385e7cde74b56aabd5e5ec39934500220a90494a11bb588853849c4d4cbe4791eadf36b45352967ba5b9df9341929180dec8c10471
6
+ metadata.gz: b44f418c7e8ad987128ee60d3b75fba74cd20fa424690966343c761908a7054882f76a84b756303794d01c2a2a1082586b8f0894c4964681fceb8b6b5d78971f
7
+ data.tar.gz: 20d795c4f67853769740817322ade2714d3978cbf6ffa81bb2cb7ace226d682b3165daf3d7888e99c0778df370da79d3a3888b80793fa9f5849528b9549f8615
@@ -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: 33786784 bytes
4
- sha256: cb9572b970bf3ccdd14ea21deb7301327842e78e6510a0a34527fa805adad109
3
+ size: 34568360 bytes
4
+ sha256: c2f5a5efc114a59720cc992e300aeede1934333de9066e5ad5e502c7e795b78d
@@ -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: 34016368 bytes
4
- sha256: 363319e524c95c613ae3d5b7fcf44f297ebdd99176acb7a4d5663aca10f64ffa
3
+ size: 34797976 bytes
4
+ sha256: 4d0b945dc2434399b9bb4aebc0a25d39cfef140091eab772320afe027b519a2d
@@ -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: 34015400 bytes
4
- sha256: 8145581cae5120e09405fd705e2da0f4da292bd5e4e9d4dd50fc81bb8d4a8ba7
3
+ size: 34788784 bytes
4
+ sha256: 9392d2b6618f0a15b0701863b9f5c316eb55274cf67ef5975bfd467719ff90af
@@ -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: 34025312 bytes
4
- sha256: 10cdb11297339a9df70139ae3f24bc3c3308d07156fe7a008aeccf1070868185
3
+ size: 34798712 bytes
4
+ sha256: 2b0a6f998b61524c94579f09d5aa7dafa1d5961f156110699ff9352bf97f845d
@@ -169,6 +169,13 @@ module Parsanol
169
169
  @pos += 1
170
170
  str = decode_inline_string_bytes(len)
171
171
  str.to_sym
172
+ when TAG_INLINE_STRING
173
+ # Inline string: len, then u64 chunks (same wire form as
174
+ # TAG_SYMBOL without the to_sym). Emitted by the rs 0.8.x
175
+ # engine; previously unhandled here (parsanol-ruby#83).
176
+ len = @data[@pos]
177
+ @pos += 1
178
+ decode_inline_string_bytes(len)
172
179
  when TAG_REPETITION
173
180
  inner = decode_value
174
181
  [:repetition, inner].compact
@@ -83,7 +83,8 @@ module Parsanol
83
83
  elsif [REPETITION_SYM, REPETITION_TAG].include?(first)
84
84
  # Optimized: transform items starting from index 1
85
85
  len = arr.length
86
- return EMPTY_ARRAY if len == 1
86
+ # Empty repetition: named [], unnamed → "" (CanFlatten#flatten_repetition)
87
+ return (named ? EMPTY_ARRAY : EMPTY_STRING) if len == 1
87
88
 
88
89
  items = Array.new(len - 1)
89
90
  i = 0
@@ -91,7 +92,7 @@ module Parsanol
91
92
  items[i] = transform(arr[i + 1])
92
93
  i += 1
93
94
  end
94
- flatten_repetition(items)
95
+ flatten_repetition(items, named: named)
95
96
  elsif [MAYBE_SYM, MAYBE_TAG].include?(first)
96
97
  # Maybe flattens to nil-or-value (named) or ""-or-value (unnamed),
97
98
  # never to an array
@@ -290,309 +291,98 @@ module Parsanol
290
291
  result
291
292
  end
292
293
 
293
- # Flatten sequence items according to Parslet semantics:
294
- # 1. If ALL items are hashes, return as array (this is a repetition result)
295
- # 2. If there are named captures (hashes) among Slices/strings, return ONLY the merged hash (discard Slices/strings)
296
- # 3. If only Slices/strings, join them preserving position from first Slice
297
- # 4. Return single value if only one item
298
- #
299
- # This matches Parslet's behavior where:
300
- # str('SCHEMA') >> str(' ') >> match('[a-z]').repeat(1).as(:name) >> str(';')
301
- # returns: {:name => "test"} (not ["SCHEMA ", {:name=>"test"}, ";"])
302
- #
303
- # But for repetitions with named captures:
304
- # match('[a-z]').as(:x).repeat(2)
305
- # returns: [{:x => "a"}, {:x => "b"}] (array of hashes, NOT merged!)
306
- #
307
- # Optimized: Single-pass with direct result building
294
+ # Exact port of Parslet::Atoms::CanFlatten#flatten_sequence /
295
+ # #merge_fold / #flatten_repetition / #foldl. Heuristic single-pass
296
+ # rewrites diverged from this (parsanol-ruby#83); stay byte-for-byte
297
+ # with parslet's fold. Hot shapes (all-Hash, all-stringlike)
298
+ # short-circuit before the fold to avoid per-step allocations.
299
+ def self.foldl(list, &)
300
+ return EMPTY_STRING if list.empty?
301
+
302
+ list.drop(1).inject(list.first, &)
303
+ end
304
+
308
305
  def self.flatten_sequence(items)
309
- return EMPTY_ARRAY if items.empty? # Match Parsanol Ruby mode
310
- return items.first if items.length == 1
311
-
312
- # Single pass: categorize items
313
- merged_hash = {}
314
- slice_or_string_parts = []
315
- hash_count = 0
316
- total_items = 0
317
- has_non_empty_array = false
318
-
319
- items.each do |item|
320
- case item
321
- when Hash
322
- merged_hash.merge!(item)
323
- hash_count += 1
324
- total_items += 1
325
- when ::Parsanol::Slice, String
326
- slice_or_string_parts << item
327
- total_items += 1
328
- when Array
329
- # Check if this is a non-empty array (repetition result with content)
330
- # Parslet behavior: when a sequence contains a non-empty repetition,
331
- # the WHOLE sequence should be kept as array, not merged.
332
- if item.empty?
333
- # Empty repetition - skip (sequence semantics: merge rest)
334
- # Don't increment total_items for empty arrays
335
- else
336
- # Check if array contains only hashes (repetition wrapper pattern)
337
- # In this case, merge the inner hashes into merged_hash
338
- non_hash_items = item.grep_v(Hash)
339
- all_items_are_hashes = non_hash_items.empty?
340
-
341
- if all_items_are_hashes
342
- # Check if merging would overwrite existing keys in merged_hash.
343
- # If so, this is a repetition pattern (item >> (sep >> item).repeat)
344
- # and should be kept as array, not merged.
345
- # Example: merged_hash={namedTypeOrRename: A}, array=[{namedTypeOrRename: B}]
346
- # → should produce [{namedTypeOrRename: A}, {namedTypeOrRename: B}]
347
- existing_keys = merged_hash.keys
348
- repeated_keys = item.flat_map(&:keys).tally.any? { |_key, count| count > 1 }
349
- shares_keys = repeated_keys || item.any? do |sub_item|
350
- sub_item.is_a?(Hash) && sub_item.keys.intersect?(existing_keys)
351
- end
352
-
353
- if shares_keys
354
- has_non_empty_array = true
355
- item.each do |sub_item|
356
- hash_count += 1 if sub_item.is_a?(Hash)
357
- end
358
- total_items += 1
359
- else
360
- item.each do |sub_item|
361
- merged_hash.merge!(sub_item) if sub_item.is_a?(Hash)
362
- end
363
- end
364
- else
365
- # Non-empty repetition with non-hash items - mark that we should keep as array
366
- has_non_empty_array = true
367
- # Still collect items for potential array result
368
- item.each do |sub_item|
369
- case sub_item
370
- when Hash
371
- hash_count += 1
372
- when ::Parsanol::Slice, String
373
- slice_or_string_parts << sub_item
374
- end
375
- end
376
- total_items += 1
377
- end
378
- end
379
- when nil
380
- # Skip nil values (from lookahead or optional that didn't match)
381
- else
382
- total_items += 1
383
- end
306
+ list = items.compact
307
+ return EMPTY_STRING if list.empty?
308
+ return list.first if list.length == 1
309
+
310
+ # Hot path: all-hash sequence. Single-pass merge preserves
311
+ # parslet's merge_fold(Hash, Hash) last-wins semantics.
312
+ if list.all?(Hash)
313
+ return list.reduce { |acc, hash| acc.merge(hash) }
384
314
  end
385
315
 
386
- # PARSLET SEQUENCE BEHAVIOR WITH REPETITIONS:
387
- # If the sequence contains a non-empty repetition result (array with items),
388
- # return as array instead of merging.
389
- # Example: factor.as(:left) >> (op >> factor).as(:rhs).repeat
390
- # With input "a+b" produces: [{left: {...}}, {rhs: {...}}]
391
- # With input "a" produces: {left: {...}} (empty repetition, merge)
392
- if has_non_empty_array
393
- # Flatten the items: top-level hashes + array items
394
- result = []
395
- items.each do |item|
396
- case item
397
- when Hash
398
- result << item
399
- when Array
400
- result.concat(item)
401
- when ::Parsanol::Slice, String
402
- # Skip unnamed Slices/strings when we have named captures
403
- end
404
- end
405
- return result.length == 1 ? result.first : result
316
+ # Hot path: all stringlike (String/Slice). Build one Slice
317
+ # from the first Slice's offset, joining contents in place.
318
+ if list.all? { |x| x.is_a?(::Parsanol::Slice) || x.is_a?(String) }
319
+ first_slice = list.find { |x| x.is_a?(::Parsanol::Slice) }
320
+ content = list.map { |x| x.is_a?(::Parsanol::Slice) ? x.content : x.to_s }.join
321
+ return first_slice ? ::Parsanol::Slice.new(first_slice.offset, content, first_slice.input) : content
406
322
  end
407
323
 
408
- # KEY INSIGHT: If ALL items are hashes, we need to determine:
409
- # 1. WRAPPER PATTERN: All hashes have the SAME single key, and values are HASHES
410
- # => Merge the inner hashes under that key
411
- # Example: [{:syntax => {:spaces => {...}}},
412
- # {:syntax => {:schemaDecl => [...]}}]
413
- # Result: {:syntax => {:spaces => {...}, :schemaDecl => [...]}}
414
- #
415
- # 2. REPETITION PATTERN: All hashes have the SAME single key, but values are SIMPLE
416
- # => Keep as array (this is a repetition result)
417
- # Example: [{:letter => "a"}, {:letter => "b"}, {:letter => "c"}]
418
- # Result: [{:letter => "a"}, {:letter => "b"}, {:letter => "c"}]
419
- #
420
- # 3. MIXED KEYS: Hashes have DIFFERENT keys
421
- # => Merge into single hash
422
- # Example: [{:explicitAttr => {...}}, {:whereClause => {...}}]
423
- # Result: {:explicitAttr => {...}, :whereClause => {...}}
424
- if hash_count == total_items && hash_count > 1
425
- # Check if all hashes have the same single key
426
- first_item = items.first
427
- if first_item.is_a?(Hash) && first_item.keys.length == 1
428
- wrapper_key = first_item.keys.first
429
-
430
- # Verify all items are hashes with the same single key
431
- all_same_wrapper = items.all? do |item|
432
- item.is_a?(Hash) && item.keys.length == 1 && item.keys.first == wrapper_key
433
- end
324
+ # Cold path: parslet's exact fold (Hash/Slice/String/Array
325
+ # mixtures, including the #83 paragraph cases).
326
+ foldl(list) { |acc, item| merge_fold(acc, item) }
327
+ end
434
328
 
435
- if all_same_wrapper
436
- # Check if values are all hashes (wrapper pattern) or not (repetition pattern)
437
- all_values_are_hashes = items.all? do |item|
438
- item[wrapper_key].is_a?(Hash)
439
- end
440
-
441
- return items unless all_values_are_hashes
442
-
443
- # Check if inner hashes have the same keys or different keys
444
- # REPETITION pattern (same keys like entity_decl): keep as array
445
- # WRAPPER pattern (different keys like spaces vs schemaDecl): merge
446
- first_inner_keys = items.first[wrapper_key].keys.to_set
447
- items.all? do |item|
448
- item[wrapper_key].keys.to_set == first_inner_keys
449
- end
450
-
451
- # Check if items have single keys or multiple keys
452
- # - Single key items with repeated outer key = true repetition (keep array)
453
- # - Multiple key items with repeated outer key = duplicate labels in sequence (merge)
454
- max_keys_per_item = items.map do |item|
455
- item.is_a?(Hash) ? item.keys.length : 0
456
- end.max || 0
457
-
458
- # Check if inner values are hashes with different keys
459
- # This distinguishes:
460
- # - True repetition: [{letter: 'a'}, {letter: 'b'}] - inner is string
461
- # - Duplicate labels: [{group: {char: 'a'}}, {group: {digit: '5'}}] - inner is hash with different keys
462
- inner_keys_all_same = true
463
- first_inner_keys = nil
464
- if items.all? do |item|
465
- item.is_a?(Hash) && item[wrapper_key].is_a?(Hash)
466
- end
467
- first_inner_keys = items.first[wrapper_key].keys.to_set
468
- inner_keys_all_same = items.all? do |item|
469
- item[wrapper_key].keys.to_set == first_inner_keys
470
- end
471
- end
472
-
473
- # DUPLICATE LABELS IN SEQUENCE: multiple keys per item with repeated outer key
474
- # OR inner hashes with different keys
475
- # Example: [{group: {char: 'a'}}, {group: {digit: '5'}}]
476
- # Ruby semantics: merge with last value wins for the outer key
477
- # This is different from true repetition where each item has exactly one key
478
- has_duplicate_labels = max_keys_per_item > 1 || (first_inner_keys && !inner_keys_all_same)
479
-
480
- # Check if inner hashes have the same keys or different keys
481
- first_inner_keys ||= items.first[wrapper_key].keys.to_set
482
- all_same_keys = items.all? do |item|
483
- item[wrapper_key].keys.to_set == first_inner_keys
484
- end
485
-
486
- if has_duplicate_labels
487
- # DUPLICATE LABELS PATTERN: items have multiple keys with repeated outer key
488
- # OR inner hashes have different keys
489
- # This is a SEQUENCE with duplicate .as() labels
490
- # Ruby semantics: merge and keep last value for the outer key
491
- merged = {}
492
- items.each do |item|
493
- item.each do |k, v|
494
- merged[k] = v # Last value wins
495
- end
496
- end
497
- # Return only the wrapper key with its last value
498
- return { wrapper_key => merged[wrapper_key] }
499
- elsif all_same_keys
500
- # TRUE REPETITION: each item has exactly one key
501
- # Keep as array of hashes
502
- # Example: [{letter: 'a'}, {letter: 'b'}] or [{schemaDecl: ...}, {schemaDecl: ...}]
503
- return items
504
- else
505
- # DIFFERENT INNER KEYS with single keys: Same outer key with different inner keys
506
- # This is a WRAPPER pattern - keep all items as array
507
- # Example: [{:syntax => {:entityDecl => ...}}, {:syntax => {:typeDecl => ...}}]
508
- # Should NOT merge or drop items - keep all declarations
509
- return items
510
- end
511
-
512
- return items unless all_values_are_hashes
513
-
514
- # Repetition pattern: keep as array
329
+ # Parslet compares exact classes (`left.class == right.class`) and
330
+ # uses `instance_of?` below not `is_a?` so Slice subclasses and
331
+ # Hash subclasses do not take the wrong branch.
332
+ def self.merge_fold(left, right)
333
+ # rubocop:disable Style/ClassEqualityComparison
334
+ if left.class == right.class
335
+ # rubocop:enable Style/ClassEqualityComparison
336
+ return left.is_a?(Hash) ? left.merge(right) : left + right
337
+ end
515
338
 
516
- end
517
- end
339
+ if left.respond_to?(:to_str) && right.respond_to?(:to_str)
340
+ return right if right.respond_to?(:to_slice)
341
+ return left if left.respond_to?(:to_slice)
518
342
 
519
- # MIXED KEYS: Hashes have different keys
520
- # Parslet sequence semantics: merge into single hash
521
- return merged_hash
343
+ return left.to_str + right.to_str
522
344
  end
523
345
 
524
- # PARSLET SEQUENCE SEMANTICS:
525
- # If there are named captures (hashes) mixed with other things,
526
- # return ONLY the merged hash (discard unnamed Slices/strings)
527
- return merged_hash unless merged_hash.empty?
528
-
529
- # No named captures - handle Slices/strings and other items
530
- if slice_or_string_parts.any?
531
- # Join Slices/strings, preserving position from first Slice
532
- first_slice = slice_or_string_parts.find { |i| i.is_a?(::Parsanol::Slice) }
533
- content = slice_or_string_parts.map do |i|
534
- i.is_a?(::Parsanol::Slice) ? i.content : i
535
- end.join
536
-
537
- if first_slice
538
- # Create new Slice with combined content, preserving position from first
539
- return ::Parsanol::Slice.new(first_slice.offset, content,
540
- first_slice.input)
541
- else
542
- # All plain strings (shouldn't happen with new decode_flat, but handle it)
543
- return slice_or_string_parts.length == 1 ? slice_or_string_parts.first : content
544
- end
545
- end
346
+ return left if right.respond_to?(:to_str)
347
+ return right if left.respond_to?(:to_str)
546
348
 
547
- # Only other items (arrays, etc.)
548
- return EMPTY_ARRAY if total_items.zero?
349
+ return left + [right] if right.is_a?(Hash)
350
+ return [left] + right if left.is_a?(Hash)
549
351
 
550
- items.length == 1 ? items.first : items
352
+ # Fallback: hoist both sides into an array (defensive; parslet
353
+ # raises here, but native trees can carry nested Arrays that
354
+ # parslet would already have folded).
355
+ Array(left) + Array(right)
551
356
  end
552
357
 
553
- # Parslet/Parsanol repetition semantics:
554
- # 1. Return [] for empty repetitions
555
- # 2. If all items are Slices (or strings), join them preserving position
556
- # 3. Otherwise return array
557
- def self.flatten_repetition(items)
558
- return EMPTY_ARRAY if items.empty?
559
-
560
- # Single-pass flatten and check
561
- flat_items = []
562
- all_slices_or_strings = true
563
-
564
- items.each do |item|
565
- if item.is_a?(Array)
566
- item.each do |sub|
567
- flat_items << sub
568
- all_slices_or_strings = false unless slice_or_string?(sub)
569
- end
570
- else
571
- flat_items << item
572
- all_slices_or_strings = false unless slice_or_string?(item)
573
- end
358
+ # Exact port of Parslet::Atoms::CanFlatten#flatten_repetition.
359
+ # `named` is true only when this repetition is the direct child of
360
+ # a Named (.as(...)); it controls empty-list folding ([] vs "").
361
+ # `instance_of?` (not `is_a?`/`any?(Hash)`) matches parslet.
362
+ def self.flatten_repetition(items, named: false)
363
+ # rubocop:disable Style/PredicateWithKind
364
+ if items.any? { |e| e.instance_of?(Hash) }
365
+ return items.select { |e| e.instance_of?(Hash) }
574
366
  end
575
367
 
576
- return EMPTY_ARRAY if flat_items.empty?
368
+ if items.any? { |e| e.instance_of?(Array) }
369
+ return items.select { |e| e.instance_of?(Array) }.flatten(1)
370
+ end
371
+ # rubocop:enable Style/PredicateWithKind
577
372
 
578
- # If all Slices or strings, join them preserving position from first Slice
579
- if all_slices_or_strings
580
- first_slice = flat_items.find { |i| i.is_a?(::Parsanol::Slice) }
581
- content = flat_items.map do |i|
582
- i.is_a?(::Parsanol::Slice) ? i.content : i
583
- end.join
373
+ return EMPTY_ARRAY if named && items.empty?
584
374
 
585
- if first_slice
586
- # Create new Slice with combined content, preserving position from first
587
- ::Parsanol::Slice.new(first_slice.offset, content,
588
- first_slice.input)
589
- else
590
- # All plain strings (shouldn't happen with new decode_flat, but handle it)
591
- content
592
- end
593
- else
594
- flat_items
375
+ # Hot path: all-stringlike repetition → foldl via concat.
376
+ # Cold path: parslet's exact fold.
377
+ if items.all? { |x| x.is_a?(::Parsanol::Slice) || x.is_a?(String) }
378
+ return EMPTY_STRING if items.empty?
379
+
380
+ first_slice = items.find { |x| x.is_a?(::Parsanol::Slice) }
381
+ content = items.map { |x| x.is_a?(::Parsanol::Slice) ? x.content : x.to_s }.join
382
+ return first_slice ? ::Parsanol::Slice.new(first_slice.offset, content, first_slice.input) : content
595
383
  end
384
+
385
+ foldl(items.compact) { |acc, item| acc + item }
596
386
  end
597
387
 
598
388
  # Check if value is a Slice or String
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.44"
4
+ VERSION = "1.3.45"
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.44
4
+ version: 1.3.45
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Ribose Inc.