parsanol 1.3.16 → 1.3.18

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: 82fd7d0cba94acf7ca4985c1616d8d7dbc17636f41af33fc2cad5c5cc36cfac2
4
- data.tar.gz: 1a29b8de6b0e9b34ea68643958c775d460fec803425c002e848bc8fc620cc907
3
+ metadata.gz: 8c155ba9401ec5127188455f4411e52798b18b4647a7e0032aac8704f6390a4e
4
+ data.tar.gz: 6944ad7cafb74447a8822b88d6bbc6c2da5bce111da7dcbd3e75bb3facf09b14
5
5
  SHA512:
6
- metadata.gz: 2e55df900cc9f2fc24e4f8c5ab0c2b08d56e4232e23178a2f77c1b64a5efca17c85c32bfc986c1a75e36252d1f7942d02b26499abc4d763cb3aaffcd01ed10e7
7
- data.tar.gz: b81c35f7c1dfce8d4747e690e23329a98a91564430a95c43eb32fce4199b25082f9fd1617f4a8f25f998f1f594c6ab3c337298a561898e6b9e2505c6bd6c89e9
6
+ metadata.gz: 6b53a9d7cad648052f790dbd3577968e085652ae91dda58f095175e815c349182d9387fd897f141d2d41225226ad417594e6bdd18d0c352b574ca008759bf73a
7
+ data.tar.gz: 857eb89870d64a905b540237d694f0f991eb2e7665489a8bfc5f5225444a95fddb309b9749829869293f863a479c3dcf698a904596ecbda716b50d251c85bd54
data/Cargo.lock CHANGED
@@ -253,7 +253,7 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
253
253
  [[package]]
254
254
  name = "parsanol"
255
255
  version = "0.5.1"
256
- source = "git+https://github.com/parsanol/parsanol-rs?branch=main#f6782c030b42aaa722357cd107fd56ed9853c4ea"
256
+ source = "git+https://github.com/parsanol/parsanol-rs?branch=main#7357b1a1885355a7116cd9c4c58a2c9d2d559bee"
257
257
  dependencies = [
258
258
  "ahash",
259
259
  "getrandom 0.3.4",
@@ -270,7 +270,7 @@ dependencies = [
270
270
  [[package]]
271
271
  name = "parsanol-derive"
272
272
  version = "0.5.1"
273
- source = "git+https://github.com/parsanol/parsanol-rs?branch=main#f6782c030b42aaa722357cd107fd56ed9853c4ea"
273
+ source = "git+https://github.com/parsanol/parsanol-rs?branch=main#7357b1a1885355a7116cd9c4c58a2c9d2d559bee"
274
274
  dependencies = [
275
275
  "proc-macro2",
276
276
  "quote",
@@ -148,6 +148,13 @@ module Parsanol
148
148
  to_s(TOP)
149
149
  end
150
150
 
151
+ # Finalizes result by flattening. Public: the native error
152
+ # fallback (both extension and ffi tiers) calls it cross-object to
153
+ # finalize a recovered tree.
154
+ def finalize_result(value)
155
+ flatten(value)
156
+ end
157
+
151
158
  protected
152
159
 
153
160
  # Pre-allocated constant result tuples
@@ -180,11 +187,6 @@ module Parsanol
180
187
  # Alias for ok (legacy compatibility)
181
188
  alias succ ok
182
189
 
183
- # Finalizes result by flattening.
184
- def finalize_result(value)
185
- flatten(value)
186
- end
187
-
188
190
  private
189
191
 
190
192
  # Converts raw input to Source if needed.
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parsanol
4
+ module Native
5
+ # Rust engine for runtimes that cannot load C-API extensions
6
+ # (JRuby, TruffleRuby, or an MRI whose platform gem is absent and
7
+ # cannot build one). Binds the parsanol cdylib's C ABI through the
8
+ # `ffi` gem: grammars register once into Rust-side handles keyed by
9
+ # serialized structure; results cross the boundary as flat-u64 batch
10
+ # arrays decoded by the same BatchDecoder used elsewhere.
11
+ module Ffi
12
+ class Error < StandardError
13
+ end
14
+
15
+ class << self
16
+ @available = nil
17
+
18
+ def available?
19
+ return @available unless @available.nil?
20
+
21
+ @available = begin
22
+ require "ffi"
23
+ lib = locate_library
24
+ lib && bind_library(lib)
25
+ rescue LoadError, StandardError
26
+ false
27
+ end
28
+ end
29
+
30
+ # Registers (once, cached) and parses. +grammar+ is a grammar
31
+ # atom or a pre-serialized JSON string. Mirrors Native.parse's
32
+ # contract, including the single reporter-pass fallback to the
33
+ # pure-Ruby engine for parslet-compatible failure errors.
34
+ def parse(grammar, input)
35
+ unless available? && @binding
36
+ raise Error, "parsanol FFI library not available"
37
+ end
38
+
39
+ if grammar.is_a?(String)
40
+ json = grammar
41
+ atom = nil
42
+ else
43
+ json = ::Parsanol::Native::Parser.serialize_grammar(grammar)
44
+ atom = grammar
45
+ end
46
+ handle = ((@handles ||= {})[json] ||= @binding.c_register(json))
47
+ raise Error, "grammar registration failed" if handle.zero?
48
+
49
+ written = parse_into(handle, input)
50
+ if written.positive?
51
+ return ::Parsanol::Native::BatchDecoder.decode_and_flatten(
52
+ @buffer.read_array_of_uint64(written), input, ::Parsanol::Slice
53
+ )
54
+ end
55
+
56
+ message = @binding.c_last_error.to_s
57
+ if atom
58
+ return ::Parsanol::Native.raise_native_parse_error(
59
+ RuntimeError.new(message), atom, input
60
+ )
61
+ end
62
+
63
+ raise Error, "parse failed: #{message}"
64
+ end
65
+
66
+ private
67
+
68
+ # Search order: explicit override, gem-vendored cdylib next to
69
+ # this file, then the system linker path.
70
+ def locate_library
71
+ candidates = []
72
+ env = ENV.fetch("PARSANOL_FFI_LIB", nil)
73
+ candidates << env if env && !env.empty?
74
+ here = File.dirname(__FILE__)
75
+ %w[libparsanol.dylib libparsanol.so parsanol.dll].each do |name|
76
+ path = File.expand_path(name, here)
77
+ candidates << path if File.file?(path)
78
+ end
79
+ candidates << "parsanol"
80
+ candidates.each do |cand|
81
+ return cand if File.file?(cand)
82
+
83
+ begin
84
+ FFI::DynamicLibrary.open(cand, FFI::DynamicLibrary::RTLD_NOW)
85
+ return cand
86
+ rescue StandardError
87
+ next
88
+ end
89
+ end
90
+ nil
91
+ end
92
+
93
+ def bind_library(path) # rubocop:disable Naming/PredicateMethod -- binds and reports success
94
+ binding_mod = Module.new do
95
+ extend FFI::Library
96
+
97
+ ffi_lib path
98
+ attach_function :c_register, :parsanol_c_register,
99
+ %i[string], :uint64
100
+ attach_function :c_parse, :parsanol_c_parse,
101
+ %i[uint64 string pointer size_t], :long_long
102
+ attach_function :c_last_error, :parsanol_c_last_error,
103
+ [], :string
104
+ attach_function :c_release, :parsanol_c_release,
105
+ %i[uint64], :void
106
+ end
107
+ # Probe the symbols now so a mismatched library fails loudly
108
+ # at availability time, not at first parse.
109
+ probe = binding_mod.c_last_error
110
+ @binding = binding_mod
111
+ @buffer = nil
112
+ !probe.nil? || true
113
+ end
114
+
115
+ # Two-call buffer protocol: cap=0 asks for the needed size, then
116
+ # one allocation carries the whole batch.
117
+ def parse_into(handle, input)
118
+ needed = @binding.c_parse(handle, input, nil, 0)
119
+ return needed unless needed.negative?
120
+
121
+ @buffer = FFI::MemoryPointer.new(:uint64, -needed)
122
+ @binding.c_parse(handle, input, @buffer, -needed)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -18,7 +18,7 @@ module Parsanol
18
18
  def available?
19
19
  return @cached_available unless @cached_available.nil?
20
20
 
21
- @cached_available = begin
21
+ @ext_loaded = begin
22
22
  # Try versioned path first (released gem), then non-versioned (local dev)
23
23
  ruby_version = RUBY_VERSION.split(".").take(2).join(".")
24
24
  begin
@@ -30,6 +30,13 @@ module Parsanol
30
30
  rescue LoadError
31
31
  false
32
32
  end
33
+ @cached_available = @ext_loaded || Ffi.available?
34
+ end
35
+
36
+ # True when the MRI C-API extension is loaded; false when the
37
+ # engine runs through the ffi-gem cdylib tier instead.
38
+ def extension_loaded?
39
+ @ext_loaded ? true : false
33
40
  end
34
41
 
35
42
  # Parse input with a Ruby grammar, returning clean AST.
@@ -10,6 +10,8 @@ require "parsanol/native/batch_decoder"
10
10
 
11
11
  module Parsanol
12
12
  module Native
13
+ # ffi-gem cdylib tier: lazy — only loaded when the MRI extension is absent.
14
+ autoload :Ffi, "parsanol/native/ffi"
13
15
  class << self
14
16
  # Check if native extension is available
15
17
  def available?
@@ -38,6 +40,10 @@ module Parsanol
38
40
  def parse(grammar, input)
39
41
  raise LoadError, "Native parser not available" unless available?
40
42
 
43
+ # ffi-gem tier (JRuby/TruffleRuby/no-binary MRI): same contract,
44
+ # batch-decoded results, single reporter-pass error fallback.
45
+ return Ffi.parse(grammar, input) unless Parser.extension_loaded?
46
+
41
47
  # Both sub-methods return the final decoded tree; on native failure
42
48
  # they fall back to the pure-Ruby parser, whose result is already
43
49
  # final and must not be transformed again.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.16"
4
+ VERSION = "1.3.18"
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.16
4
+ version: 1.3.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -215,6 +215,7 @@ files:
215
215
  - lib/parsanol/native.rb
216
216
  - lib/parsanol/native/batch_decoder.rb
217
217
  - lib/parsanol/native/dynamic.rb
218
+ - lib/parsanol/native/ffi.rb
218
219
  - lib/parsanol/native/parser.rb
219
220
  - lib/parsanol/native/serializer.rb
220
221
  - lib/parsanol/native/transformer.rb