parsanol 1.3.45-arm64-darwin → 1.3.46-arm64-darwin

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: 6725276c28acbe061606fca57771e612c09fdfbf1586eefbfaba0b218c5dcd25
4
- data.tar.gz: 831dadd77bda92f1e299931e7eeda78b5854ffd244a57e29e8e7215bc728ec02
3
+ metadata.gz: 4f030ddf86028adfeda97de5c841b53a474ea939f997b3db2df30e089093ea94
4
+ data.tar.gz: 33c565b4b973793102b1a14c7c30963a5f1063639640af60f91836a7e7dbd11f
5
5
  SHA512:
6
- metadata.gz: a1e4e3286ecdacc416df1a7569b874e88c71e5763d020874aa80e653742372b75392a410d4b2567650282ddc194a042e55aa6cf6959452c16436b4f1868ef488
7
- data.tar.gz: d49010e87af19a5c958d77d03c6ad08462db15bbf2e57cf0822dcc34a192aead6d06161985ae9946a523c7abf08664508484f77aef1c296564dde1d17c56d249
6
+ metadata.gz: 3873ee15d9f7cd59123623738548ee8990cbe2fbff3d96cf3f995c3de30c9b45cb6e125531ba1aa40da3c7ee3eda6d316bd43e83b1a2a5f545898a571d883214
7
+ data.tar.gz: 5fe62804addb85b981c7d69cb0679a71c0fbe74e408f4e5cc2c3f0ac46198946c1b90e92af57fb2a4fb0ad964cc71ccad4410a4cc99d9469fec1a0fee5dd1f72
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parsanol
4
+ # Explains the first difference between two parse trees.
5
+ #
6
+ # Built for engine-parity debugging (parsanol-ruby#83): when
7
+ # `tree_ruby != tree_native`, `TreeDiff.why` pinpoints the first
8
+ # divergent node instead of leaving you to eyeball two inspect
9
+ # dumps.
10
+ #
11
+ # @example
12
+ # Parsanol::TreeDiff.why(ruby_tree, native_tree)
13
+ # # => { path: "paragraph.lines",
14
+ # # why: "kind mismatch: Array vs Hash",
15
+ # # ruby: "Array(1)",
16
+ # # native: "Hash{text, line_break}" }
17
+ #
18
+ # Returns nil when the trees are equal.
19
+ module TreeDiff
20
+ module_function
21
+
22
+ def why(left, right, max_depth: 200)
23
+ step(left, right, [], 0, max_depth)
24
+ end
25
+
26
+ def step(left, right, path, depth, max_depth)
27
+ if depth > max_depth
28
+ return { path: format_path(path), why: "depth limit exceeded", ruby: kind(left),
29
+ native: kind(right) }
30
+ end
31
+ containers = (left.is_a?(Hash) && right.is_a?(Hash)) ||
32
+ (left.is_a?(Array) && right.is_a?(Array))
33
+ # Containers always descend: Hash#== would short-circuit equal via
34
+ # Slice's content-only ==, hiding offset drift.
35
+ return nil if !containers && equal_values?(left, right)
36
+
37
+ if left.is_a?(Hash) && right.is_a?(Hash)
38
+ only_left = left.keys - right.keys
39
+ only_right = right.keys - left.keys
40
+ if only_left.any? || only_right.any?
41
+ return { path: format_path(path),
42
+ why: "key mismatch: ruby-only #{only_left.inspect}, " \
43
+ "native-only #{only_right.inspect}",
44
+ ruby: kind(left), native: kind(right) }
45
+ end
46
+ left.each do |key, value|
47
+ if right.key?(key)
48
+ found = step(value, right[key], path + [key], depth + 1, max_depth)
49
+ return found if found
50
+ end
51
+ end
52
+ nil
53
+ elsif left.is_a?(Array) && right.is_a?(Array)
54
+ if left.length != right.length
55
+ return { path: format_path(path),
56
+ why: "size mismatch: ruby #{left.length} vs native #{right.length}",
57
+ ruby: "Array(#{left.length})", native: "Array(#{right.length})" }
58
+ end
59
+ left.each_with_index do |value, index|
60
+ found = step(value, right[index], path + ["[#{index}]"], depth + 1, max_depth)
61
+ return found if found
62
+ end
63
+ nil
64
+ elsif left.is_a?(::Parsanol::Slice) && right.is_a?(::Parsanol::Slice)
65
+ if left.content == right.content
66
+ { path: format_path(path),
67
+ why: "offset drift: #{left.offset} vs #{right.offset}",
68
+ ruby: kind(left), native: kind(right) }
69
+ else
70
+ { path: format_path(path),
71
+ why: "content mismatch: #{left.content.inspect} vs #{right.content.inspect}",
72
+ ruby: kind(left), native: kind(right) }
73
+ end
74
+ else
75
+ { path: format_path(path), why: "kind mismatch: #{kind(left)} vs #{kind(right)}",
76
+ ruby: kind(left), native: kind(right) }
77
+ end
78
+ end
79
+
80
+ # Equality for parity purposes: Slices must match on content AND
81
+ # offset (position drift is a real divergence), and a Slice never
82
+ # equals a plain String here even though Slice#== would allow it —
83
+ # engines that disagree on type disagree on the tree.
84
+ def equal_values?(left, right)
85
+ left_is_slice = left.is_a?(::Parsanol::Slice)
86
+ right_is_slice = right.is_a?(::Parsanol::Slice)
87
+ if left_is_slice || right_is_slice
88
+ left_is_slice && right_is_slice &&
89
+ left.content == right.content && left.offset == right.offset
90
+ else
91
+ left == right
92
+ end
93
+ end
94
+
95
+ def kind(item)
96
+ case item
97
+ when ::Parsanol::Slice then "Slice(#{item.content.inspect}@#{item.offset})"
98
+ when Hash then "Hash{#{item.keys.join(', ')}}"
99
+ when Array then "Array(#{item.length})"
100
+ when String then "String(#{item.inspect})"
101
+ when nil then "nil"
102
+ else item.class.name
103
+ end
104
+ end
105
+
106
+ def format_path(path)
107
+ path.each_with_index.inject(+"") do |acc, (part, i)|
108
+ if i.zero? || part.to_s.start_with?("[")
109
+ acc << part.to_s
110
+ else
111
+ acc << "." << part.to_s
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.45"
4
+ VERSION = "1.3.46"
5
5
  end
data/lib/parsanol.rb CHANGED
@@ -266,6 +266,7 @@ module Parsanol
266
266
 
267
267
  autoload :Expression, "parsanol/expression"
268
268
  autoload :IncrementalSession, "parsanol/incremental"
269
+ autoload :TreeDiff, "parsanol/tree_diff"
269
270
  end
270
271
 
271
272
  require "parsanol/version"
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.45
4
+ version: 1.3.46
5
5
  platform: arm64-darwin
6
6
  authors:
7
7
  - Ribose Inc.
@@ -114,6 +114,7 @@ files:
114
114
  - lib/parsanol/streaming_parser.rb
115
115
  - lib/parsanol/string_view.rb
116
116
  - lib/parsanol/transform.rb
117
+ - lib/parsanol/tree_diff.rb
117
118
  - lib/parsanol/version.rb
118
119
  - lib/parsanol/vm.rb
119
120
  - lib/parsanol/wasm/README.md