parsanol 1.3.45 → 1.3.46

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: f398d307a2ba1ef741d5d6b4667d40d3e80b90eba6024610816596c55840d778
4
- data.tar.gz: 2523a3a5f0cdf0b378b879a6e87480635b921e0ba83308ef8a87ba59e38d31bf
3
+ metadata.gz: a69acb95360d14562fcea57597673c9fadb653c1b10688a6cd3b9324bc6b751a
4
+ data.tar.gz: fd3ee0db276a54c3aa13108747f4cb632a45fc8f53c7ffc01731c578d1b18b45
5
5
  SHA512:
6
- metadata.gz: 627ac372929495048c804de63bb2e7975b37338c2c0f310d766fb584e5b44ad9957ebb625e9a936f4a949f6b4b1ff7251e967df570cbe9fcc4a3fd073b0b71da
7
- data.tar.gz: 7f917a9c4a7221c69fba8ea32db5573e4be28e332188281aa5b98c3c7f04a3a17adabe19f11c9a49ae4500cff692b1fabe698d00a023fd0a71ba9b2e5a222736
6
+ metadata.gz: e20527cabb71ea4aecb84aa26b629d81e34f6d0d2dd46a2e33c874abb1c7bdeea2efe06add1bc8ed6c9d648cd837de15f7a6735dcbb7fdb84b3547ac2b573ec2
7
+ data.tar.gz: 2d9aad0fdb64fe5354b0a6fba463754b24b4bc8993f25fdf79b5ada6ffa52f02b1c87066979559f531b90c10acdbbf40e93009abcbbecb5e658cefd08140fd71
data/Cargo.lock CHANGED
@@ -252,9 +252,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
252
252
 
253
253
  [[package]]
254
254
  name = "parsanol"
255
- version = "0.8.3"
255
+ version = "0.8.5"
256
256
  source = "registry+https://github.com/rust-lang/crates.io-index"
257
- checksum = "47ac4243cfccfaaa7d00f06979dea23ed94fdd9a3eb7f14543234797344a4932"
257
+ checksum = "3a57413139c7aacd6343dbcd65985eceaebcf9d61dfa73222e5d0d7b527a581b"
258
258
  dependencies = [
259
259
  "ahash",
260
260
  "getrandom 0.3.4",
@@ -269,9 +269,9 @@ dependencies = [
269
269
 
270
270
  [[package]]
271
271
  name = "parsanol-derive"
272
- version = "0.8.3"
272
+ version = "0.8.5"
273
273
  source = "registry+https://github.com/rust-lang/crates.io-index"
274
- checksum = "7ee574e76f50d3d0e60b77363550ad9b679df6033be57db09b40eae07f8ac7a2"
274
+ checksum = "5e33d7bd1f1644da4ee51892fc3289ec89826a59e692a4f7076a40dd8ba74672"
275
275
  dependencies = [
276
276
  "proc-macro2",
277
277
  "quote",
@@ -28,7 +28,7 @@ rb-sys = { version = "0.9.124", features = ["global-allocator"] }
28
28
  magnus = { version = "0.9" }
29
29
 
30
30
  # parsanol parser library (from git for latest features)
31
- parsanol = { version = "0.8.3", features = ["ruby"] }
31
+ parsanol = { version = "0.8.5", features = ["ruby"] }
32
32
 
33
33
  # Logging
34
34
  log = "0.4"
@@ -11,6 +11,11 @@ use magnus::{Error, Ruby};
11
11
  /// functions from parsanol-rs.
12
12
  #[magnus::init]
13
13
  fn init(ruby: &Ruby) -> Result<(), Error> {
14
+ // The native surface is Ractor-callable (parsanol-ruby#59): parse
15
+ // state lives in per-thread caches and Mutex-guarded plain-data
16
+ // registries, no shared Ruby objects cross the boundary.
17
+ unsafe { rb_sys::rb_ext_ractor_safe(true) };
18
+
14
19
  // Initialize the parsanol-rs ffi::ruby module
15
20
  // This sets up Parsanol::Native with all the functions
16
21
  parsanol::ffi::ruby::init(ruby)
@@ -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: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -129,6 +129,7 @@ files:
129
129
  - lib/parsanol/streaming_parser.rb
130
130
  - lib/parsanol/string_view.rb
131
131
  - lib/parsanol/transform.rb
132
+ - lib/parsanol/tree_diff.rb
132
133
  - lib/parsanol/version.rb
133
134
  - lib/parsanol/vm.rb
134
135
  - lib/parsanol/wasm/README.md