zrip 0.1.2 → 0.2.0

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.
@@ -0,0 +1,615 @@
1
+ use std::ffi::{c_char, c_long, c_void, CStr, CString};
2
+ #[cfg(ruby_engine = "mri")]
3
+ use std::mem::MaybeUninit;
4
+ use std::panic::{catch_unwind, AssertUnwindSafe};
5
+ use std::ptr;
6
+
7
+ use rb_sys::{rb_data_type_t, VALUE};
8
+
9
+ pub type RbResult<T = VALUE> = Result<T, RubyErr>;
10
+
11
+ #[cfg(ruby_engine = "mri")]
12
+ unsafe extern "C" {
13
+ fn rb_obj_frozen_p(obj: VALUE) -> VALUE;
14
+ fn rb_str_locktmp(str: VALUE) -> VALUE;
15
+ fn rb_str_unlocktmp(str: VALUE) -> VALUE;
16
+ }
17
+
18
+ #[cfg(ruby_engine = "mri")]
19
+ type RbWithoutGvlFunc = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
20
+ #[cfg(ruby_engine = "mri")]
21
+ type RbUnblockFunc = unsafe extern "C" fn(*mut c_void);
22
+
23
+ #[cfg(ruby_engine = "mri")]
24
+ unsafe extern "C" {
25
+ fn rb_thread_call_without_gvl(
26
+ func: Option<RbWithoutGvlFunc>,
27
+ data1: *mut c_void,
28
+ ubf: Option<RbUnblockFunc>,
29
+ data2: *mut c_void,
30
+ ) -> *mut c_void;
31
+ }
32
+
33
+ #[derive(Debug)]
34
+ pub enum RubyErr {
35
+ Exception(VALUE),
36
+ Error { class: VALUE, message: String },
37
+ }
38
+
39
+ impl RubyErr {
40
+ pub fn new(class: VALUE, message: impl Into<String>) -> Self {
41
+ Self::Error {
42
+ class,
43
+ message: message.into(),
44
+ }
45
+ }
46
+
47
+ pub fn arg(message: impl Into<String>) -> Self {
48
+ Self::new(unsafe { rb_sys::rb_eArgError }, message)
49
+ }
50
+
51
+ pub fn runtime(message: impl Into<String>) -> Self {
52
+ Self::new(unsafe { rb_sys::rb_eRuntimeError }, message)
53
+ }
54
+
55
+ pub fn type_error(message: impl Into<String>) -> Self {
56
+ Self::new(unsafe { rb_sys::rb_eTypeError }, message)
57
+ }
58
+
59
+ fn current_exception() -> Self {
60
+ let err = unsafe { rb_sys::rb_errinfo() };
61
+ if err == qnil() {
62
+ Self::runtime("Ruby exception")
63
+ } else {
64
+ Self::Exception(err)
65
+ }
66
+ }
67
+ }
68
+
69
+ pub fn wrap<F>(f: F) -> VALUE
70
+ where
71
+ F: FnOnce() -> RbResult<VALUE>,
72
+ {
73
+ match catch_unwind(AssertUnwindSafe(f)) {
74
+ Ok(Ok(value)) => value,
75
+ Ok(Err(err)) => raise(err),
76
+ Err(_) => raise(RubyErr::runtime("native Rust panic")),
77
+ }
78
+ }
79
+
80
+ pub fn wrap_init<F>(f: F)
81
+ where
82
+ F: FnOnce() -> RbResult<()>,
83
+ {
84
+ match catch_unwind(AssertUnwindSafe(f)) {
85
+ Ok(Ok(())) => {}
86
+ Ok(Err(err)) => raise(err),
87
+ Err(_) => raise(RubyErr::runtime("native Rust panic")),
88
+ }
89
+ }
90
+
91
+ pub fn raise(err: RubyErr) -> ! {
92
+ let exc = match err {
93
+ RubyErr::Exception(exc) => exc,
94
+ RubyErr::Error { class, message } => error_exception(class, message),
95
+ };
96
+
97
+ unsafe { rb_sys::rb_exc_raise(exc) }
98
+ }
99
+
100
+ fn error_exception(class: VALUE, message: String) -> VALUE {
101
+ let message = message.replace('\0', "\\0");
102
+ let c_message = CString::new(message).unwrap_or_else(|_| CString::new("Ruby error").unwrap());
103
+ unsafe { rb_sys::rb_exc_new_cstr(class, c_message.as_ptr()) }
104
+ }
105
+
106
+ struct ProtectData<F> {
107
+ func: Option<F>,
108
+ panicked: bool,
109
+ }
110
+
111
+ pub fn protect_value<F>(func: F) -> RbResult<VALUE>
112
+ where
113
+ F: FnOnce() -> VALUE,
114
+ {
115
+ unsafe extern "C" fn call<F>(arg: VALUE) -> VALUE
116
+ where
117
+ F: FnOnce() -> VALUE,
118
+ {
119
+ let data = unsafe { &mut *(arg as *mut ProtectData<F>) };
120
+ let Some(func) = data.func.take() else {
121
+ data.panicked = true;
122
+ return qnil();
123
+ };
124
+
125
+ match catch_unwind(AssertUnwindSafe(func)) {
126
+ Ok(value) => value,
127
+ Err(_) => {
128
+ data.panicked = true;
129
+ qnil()
130
+ }
131
+ }
132
+ }
133
+
134
+ let mut data = ProtectData {
135
+ func: Some(func),
136
+ panicked: false,
137
+ };
138
+ let mut state = 0;
139
+ let value = unsafe {
140
+ rb_sys::rb_protect(
141
+ Some(call::<F>),
142
+ &mut data as *mut ProtectData<F> as VALUE,
143
+ &mut state,
144
+ )
145
+ };
146
+
147
+ if state != 0 {
148
+ Err(RubyErr::current_exception())
149
+ } else if data.panicked {
150
+ Err(RubyErr::runtime("native Rust panic"))
151
+ } else {
152
+ Ok(value)
153
+ }
154
+ }
155
+
156
+ pub fn protect_unit<F>(func: F) -> RbResult<()>
157
+ where
158
+ F: FnOnce(),
159
+ {
160
+ protect_value(|| {
161
+ func();
162
+ qnil()
163
+ })?;
164
+ Ok(())
165
+ }
166
+
167
+ #[cfg(ruby_engine = "mri")]
168
+ struct WithoutGvlData<F, R> {
169
+ func: Option<F>,
170
+ output: MaybeUninit<R>,
171
+ }
172
+
173
+ #[cfg(ruby_engine = "mri")]
174
+ unsafe extern "C" fn without_gvl_trampoline<F, R>(data: *mut c_void) -> *mut c_void
175
+ where
176
+ F: FnOnce() -> R,
177
+ {
178
+ let data = unsafe { &mut *(data.cast::<WithoutGvlData<F, R>>()) };
179
+ let func = data.func.take().expect("missing without-GVL function");
180
+ data.output.write(func());
181
+ ptr::null_mut()
182
+ }
183
+
184
+ #[cfg(ruby_engine = "mri")]
185
+ fn without_gvl<F, R>(func: F) -> R
186
+ where
187
+ F: FnOnce() -> R,
188
+ {
189
+ let mut data = WithoutGvlData {
190
+ func: Some(func),
191
+ output: MaybeUninit::uninit(),
192
+ };
193
+ unsafe {
194
+ rb_thread_call_without_gvl(
195
+ Some(without_gvl_trampoline::<F, R>),
196
+ (&mut data as *mut WithoutGvlData<F, R>).cast::<c_void>(),
197
+ None,
198
+ ptr::null_mut(),
199
+ );
200
+ data.output.assume_init()
201
+ }
202
+ }
203
+
204
+ #[cfg(ruby_engine = "mri")]
205
+ pub fn maybe_without_gvl<F, R>(release_gvl: bool, func: F) -> R
206
+ where
207
+ F: FnOnce() -> R,
208
+ {
209
+ if release_gvl {
210
+ without_gvl(func)
211
+ } else {
212
+ func()
213
+ }
214
+ }
215
+
216
+ #[cfg(not(ruby_engine = "mri"))]
217
+ pub fn maybe_without_gvl<F, R>(_release_gvl: bool, func: F) -> R
218
+ where
219
+ F: FnOnce() -> R,
220
+ {
221
+ func()
222
+ }
223
+
224
+ pub const fn qnil() -> VALUE {
225
+ rb_sys::ruby_special_consts::RUBY_Qnil as VALUE
226
+ }
227
+
228
+ pub const fn qtrue() -> VALUE {
229
+ rb_sys::ruby_special_consts::RUBY_Qtrue as VALUE
230
+ }
231
+
232
+ pub const fn qfalse() -> VALUE {
233
+ rb_sys::ruby_special_consts::RUBY_Qfalse as VALUE
234
+ }
235
+
236
+ pub fn bool_value(value: bool) -> VALUE {
237
+ if value {
238
+ qtrue()
239
+ } else {
240
+ qfalse()
241
+ }
242
+ }
243
+
244
+ pub fn new_binary_string(bytes: &[u8]) -> RbResult<VALUE> {
245
+ let len = c_long_len(bytes.len())?;
246
+ let ptr = if bytes.is_empty() {
247
+ ptr::null()
248
+ } else {
249
+ bytes.as_ptr() as *const c_char
250
+ };
251
+ protect_value(|| unsafe { rb_sys::rb_str_new(ptr, len) })
252
+ }
253
+
254
+ pub fn string_value(value: VALUE) -> RbResult<VALUE> {
255
+ protect_value(|| unsafe { rb_sys::rb_str_to_str(value) })
256
+ }
257
+
258
+ pub fn freeze_value(value: VALUE) -> RbResult<()> {
259
+ protect_unit(|| unsafe { rb_sys::RB_OBJ_FREEZE(value) })
260
+ }
261
+
262
+ pub fn value_to_bytes(value: VALUE) -> RbResult<Vec<u8>> {
263
+ let string = string_value(value)?;
264
+ bytes_from_string_value(string).map(|bytes| bytes.to_vec())
265
+ }
266
+
267
+ pub fn value_to_option_bytes(value: VALUE) -> RbResult<Option<Vec<u8>>> {
268
+ if value == qnil() {
269
+ Ok(None)
270
+ } else {
271
+ value_to_bytes(value).map(Some)
272
+ }
273
+ }
274
+
275
+ pub enum InputBytes {
276
+ #[cfg(ruby_engine = "mri")]
277
+ Borrowed(BorrowedStringBytes),
278
+ #[cfg(not(ruby_engine = "mri"))]
279
+ Owned(Vec<u8>),
280
+ }
281
+
282
+ impl InputBytes {
283
+ pub fn as_slice(&self) -> &[u8] {
284
+ match self {
285
+ #[cfg(ruby_engine = "mri")]
286
+ Self::Borrowed(bytes) => bytes.as_slice(),
287
+ #[cfg(not(ruby_engine = "mri"))]
288
+ Self::Owned(bytes) => bytes.as_slice(),
289
+ }
290
+ }
291
+
292
+ pub fn len(&self) -> usize {
293
+ self.as_slice().len()
294
+ }
295
+
296
+ pub fn lock_for_without_gvl(&mut self, release_gvl: bool) -> RbResult<()> {
297
+ if !release_gvl {
298
+ return Ok(());
299
+ }
300
+
301
+ match self {
302
+ #[cfg(ruby_engine = "mri")]
303
+ Self::Borrowed(bytes) => bytes.lock_tmp(),
304
+ #[cfg(not(ruby_engine = "mri"))]
305
+ Self::Owned(_) => Ok(()),
306
+ }
307
+ }
308
+ }
309
+
310
+ #[cfg(ruby_engine = "mri")]
311
+ pub struct BorrowedStringBytes {
312
+ value: VALUE,
313
+ ptr: *const u8,
314
+ len: usize,
315
+ locked: bool,
316
+ }
317
+
318
+ #[cfg(ruby_engine = "mri")]
319
+ impl BorrowedStringBytes {
320
+ fn new(value: VALUE) -> RbResult<Self> {
321
+ let string = string_value(value)?;
322
+ let bytes = bytes_from_string_value(string)?;
323
+ Ok(Self {
324
+ value: string,
325
+ ptr: bytes.as_ptr(),
326
+ len: bytes.len(),
327
+ locked: false,
328
+ })
329
+ }
330
+
331
+ fn as_slice(&self) -> &[u8] {
332
+ if self.len == 0 {
333
+ &[]
334
+ } else {
335
+ unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
336
+ }
337
+ }
338
+
339
+ fn lock_tmp(&mut self) -> RbResult<()> {
340
+ if self.locked || unsafe { rb_obj_frozen_p(self.value) } == qtrue() {
341
+ return Ok(());
342
+ }
343
+
344
+ protect_value(|| unsafe { rb_str_locktmp(self.value) })?;
345
+ self.locked = true;
346
+ Ok(())
347
+ }
348
+ }
349
+
350
+ #[cfg(ruby_engine = "mri")]
351
+ impl Drop for BorrowedStringBytes {
352
+ fn drop(&mut self) {
353
+ if self.locked {
354
+ unsafe {
355
+ rb_str_unlocktmp(self.value);
356
+ }
357
+ }
358
+ let _ = rb_sys::rb_gc_guard!(self.value);
359
+ }
360
+ }
361
+
362
+ pub fn input_bytes(value: VALUE) -> RbResult<InputBytes> {
363
+ #[cfg(ruby_engine = "mri")]
364
+ {
365
+ BorrowedStringBytes::new(value).map(InputBytes::Borrowed)
366
+ }
367
+
368
+ #[cfg(not(ruby_engine = "mri"))]
369
+ {
370
+ value_to_bytes(value).map(InputBytes::Owned)
371
+ }
372
+ }
373
+
374
+ fn bytes_from_string_value(string: VALUE) -> RbResult<&'static [u8]> {
375
+ let len = unsafe { rb_sys::RSTRING_LEN(string) };
376
+ if len < 0 {
377
+ return Err(RubyErr::runtime("negative String length"));
378
+ }
379
+ if len == 0 {
380
+ return Ok(&[]);
381
+ }
382
+
383
+ let ptr = unsafe { rb_sys::RSTRING_PTR(string) };
384
+ if ptr.is_null() {
385
+ return Err(RubyErr::runtime("null String pointer"));
386
+ }
387
+
388
+ Ok(unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) })
389
+ }
390
+
391
+ pub fn value_to_usize(value: VALUE) -> RbResult<usize> {
392
+ let mut out = 0i64;
393
+ protect_value(|| {
394
+ out = unsafe { rb_sys::rb_num2long(value) as i64 };
395
+ qnil()
396
+ })?;
397
+ usize::try_from(out).map_err(|_| RubyErr::arg("integer must be non-negative"))
398
+ }
399
+
400
+ pub fn value_to_u32(value: VALUE) -> RbResult<u32> {
401
+ u32::try_from(value_to_usize(value)?).map_err(|_| RubyErr::arg("integer too large"))
402
+ }
403
+
404
+ pub fn value_to_i32(value: VALUE) -> RbResult<i32> {
405
+ let mut out = 0i64;
406
+ protect_value(|| {
407
+ out = unsafe { rb_sys::rb_num2long(value) as i64 };
408
+ qnil()
409
+ })?;
410
+ i32::try_from(out).map_err(|_| RubyErr::arg("integer too large"))
411
+ }
412
+
413
+ pub fn usize_value(value: usize) -> RbResult<VALUE> {
414
+ protect_value(|| unsafe { rb_sys::rb_ull2inum(value as u64) })
415
+ }
416
+
417
+ pub fn i32_value(value: i32) -> VALUE {
418
+ unsafe { rb_sys::rb_int2inum(value as isize) }
419
+ }
420
+
421
+ pub fn u64_option_value(value: Option<u64>) -> RbResult<VALUE> {
422
+ match value {
423
+ Some(value) => protect_value(|| unsafe { rb_sys::rb_ull2inum(value) }),
424
+ None => Ok(qnil()),
425
+ }
426
+ }
427
+
428
+ pub fn u32_option_value(value: Option<u32>) -> RbResult<VALUE> {
429
+ match value {
430
+ Some(value) => usize_value(value as usize),
431
+ None => Ok(qnil()),
432
+ }
433
+ }
434
+
435
+ pub unsafe fn wrap_typed_data<T>(
436
+ class: VALUE,
437
+ value: Box<T>,
438
+ data_type: *const rb_data_type_t,
439
+ ) -> RbResult<VALUE> {
440
+ let raw = Box::into_raw(value);
441
+ match protect_value(|| unsafe {
442
+ rb_sys::rb_data_typed_object_wrap(class, raw as *mut c_void, data_type)
443
+ }) {
444
+ Ok(value) => Ok(value),
445
+ Err(err) => {
446
+ unsafe { drop(Box::from_raw(raw)) };
447
+ Err(err)
448
+ }
449
+ }
450
+ }
451
+
452
+ pub unsafe fn typed_data_ref<T>(
453
+ value: VALUE,
454
+ data_type: *const rb_data_type_t,
455
+ type_name: &str,
456
+ ) -> RbResult<&'static T> {
457
+ let mut ptr = std::ptr::null_mut();
458
+ protect_unit(|| unsafe {
459
+ ptr = rb_sys::rb_check_typeddata(value, data_type);
460
+ })
461
+ .map_err(|_| RubyErr::type_error(format!("expected {type_name}")))?;
462
+ if ptr.is_null() {
463
+ return Err(RubyErr::runtime(format!(
464
+ "{type_name} data pointer is null"
465
+ )));
466
+ }
467
+
468
+ Ok(unsafe { &*(ptr as *const T) })
469
+ }
470
+
471
+ pub unsafe fn define_module(name: &CStr) -> RbResult<VALUE> {
472
+ protect_value(|| unsafe { rb_sys::rb_define_module(name.as_ptr()) })
473
+ }
474
+
475
+ pub unsafe fn define_class_under(outer: VALUE, name: &CStr, superclass: VALUE) -> RbResult<VALUE> {
476
+ protect_value(|| unsafe { rb_sys::rb_define_class_under(outer, name.as_ptr(), superclass) })
477
+ }
478
+
479
+ pub unsafe fn define_error_under(outer: VALUE, name: &CStr, superclass: VALUE) -> RbResult<VALUE> {
480
+ unsafe { define_class_under(outer, name, superclass) }
481
+ }
482
+
483
+ pub unsafe fn undef_alloc_func(class: VALUE) -> RbResult<()> {
484
+ protect_unit(|| unsafe { rb_sys::rb_undef_alloc_func(class) })
485
+ }
486
+
487
+ #[allow(dead_code)]
488
+ pub unsafe fn define_module_function_0(
489
+ module: VALUE,
490
+ name: &CStr,
491
+ func: unsafe extern "C" fn(VALUE) -> VALUE,
492
+ ) -> RbResult<()> {
493
+ protect_unit(|| unsafe {
494
+ rb_sys::rb_define_module_function(module, name.as_ptr(), Some(transmute_0(func)), 0)
495
+ })
496
+ }
497
+
498
+ #[allow(dead_code)]
499
+ pub unsafe fn define_module_function_1(
500
+ module: VALUE,
501
+ name: &CStr,
502
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
503
+ ) -> RbResult<()> {
504
+ protect_unit(|| unsafe {
505
+ rb_sys::rb_define_module_function(module, name.as_ptr(), Some(transmute_1(func)), 1)
506
+ })
507
+ }
508
+
509
+ pub unsafe fn define_singleton_method_1(
510
+ object: VALUE,
511
+ name: &CStr,
512
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
513
+ ) -> RbResult<()> {
514
+ protect_unit(|| unsafe {
515
+ rb_sys::rb_define_singleton_method(object, name.as_ptr(), Some(transmute_1(func)), 1)
516
+ })
517
+ }
518
+
519
+ #[allow(dead_code)]
520
+ pub unsafe fn define_singleton_method_2(
521
+ object: VALUE,
522
+ name: &CStr,
523
+ func: unsafe extern "C" fn(VALUE, VALUE, VALUE) -> VALUE,
524
+ ) -> RbResult<()> {
525
+ protect_unit(|| unsafe {
526
+ rb_sys::rb_define_singleton_method(object, name.as_ptr(), Some(transmute_2(func)), 2)
527
+ })
528
+ }
529
+
530
+ pub unsafe fn define_singleton_method_3(
531
+ object: VALUE,
532
+ name: &CStr,
533
+ func: unsafe extern "C" fn(VALUE, VALUE, VALUE, VALUE) -> VALUE,
534
+ ) -> RbResult<()> {
535
+ protect_unit(|| unsafe {
536
+ rb_sys::rb_define_singleton_method(object, name.as_ptr(), Some(transmute_3(func)), 3)
537
+ })
538
+ }
539
+
540
+ pub unsafe fn define_method_0(
541
+ class: VALUE,
542
+ name: &CStr,
543
+ func: unsafe extern "C" fn(VALUE) -> VALUE,
544
+ ) -> RbResult<()> {
545
+ protect_unit(|| unsafe {
546
+ rb_sys::rb_define_method(class, name.as_ptr(), Some(transmute_0(func)), 0)
547
+ })
548
+ }
549
+
550
+ pub unsafe fn define_method_1(
551
+ class: VALUE,
552
+ name: &CStr,
553
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
554
+ ) -> RbResult<()> {
555
+ protect_unit(|| unsafe {
556
+ rb_sys::rb_define_method(class, name.as_ptr(), Some(transmute_1(func)), 1)
557
+ })
558
+ }
559
+
560
+ pub unsafe fn define_method_2(
561
+ class: VALUE,
562
+ name: &CStr,
563
+ func: unsafe extern "C" fn(VALUE, VALUE, VALUE) -> VALUE,
564
+ ) -> RbResult<()> {
565
+ protect_unit(|| unsafe {
566
+ rb_sys::rb_define_method(class, name.as_ptr(), Some(transmute_2(func)), 2)
567
+ })
568
+ }
569
+
570
+ unsafe fn transmute_0(
571
+ func: unsafe extern "C" fn(VALUE) -> VALUE,
572
+ ) -> unsafe extern "C" fn() -> VALUE {
573
+ unsafe {
574
+ std::mem::transmute::<unsafe extern "C" fn(VALUE) -> VALUE, unsafe extern "C" fn() -> VALUE>(
575
+ func,
576
+ )
577
+ }
578
+ }
579
+
580
+ unsafe fn transmute_1(
581
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
582
+ ) -> unsafe extern "C" fn() -> VALUE {
583
+ unsafe {
584
+ std::mem::transmute::<
585
+ unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
586
+ unsafe extern "C" fn() -> VALUE,
587
+ >(func)
588
+ }
589
+ }
590
+
591
+ unsafe fn transmute_2(
592
+ func: unsafe extern "C" fn(VALUE, VALUE, VALUE) -> VALUE,
593
+ ) -> unsafe extern "C" fn() -> VALUE {
594
+ unsafe {
595
+ std::mem::transmute::<
596
+ unsafe extern "C" fn(VALUE, VALUE, VALUE) -> VALUE,
597
+ unsafe extern "C" fn() -> VALUE,
598
+ >(func)
599
+ }
600
+ }
601
+
602
+ unsafe fn transmute_3(
603
+ func: unsafe extern "C" fn(VALUE, VALUE, VALUE, VALUE) -> VALUE,
604
+ ) -> unsafe extern "C" fn() -> VALUE {
605
+ unsafe {
606
+ std::mem::transmute::<
607
+ unsafe extern "C" fn(VALUE, VALUE, VALUE, VALUE) -> VALUE,
608
+ unsafe extern "C" fn() -> VALUE,
609
+ >(func)
610
+ }
611
+ }
612
+
613
+ fn c_long_len(len: usize) -> RbResult<c_long> {
614
+ c_long::try_from(len).map_err(|_| RubyErr::arg("length too large"))
615
+ }
@@ -3,6 +3,55 @@
3
3
  require_relative "dictionary"
4
4
 
5
5
  module Zrip
6
+ # Zstandard block-format codec.
7
+ #
8
+ # `BlockCodec` keeps mutable native context state and is intended to be used
9
+ # per Ractor.
10
+ #
11
+ # @!method self.new(dict: nil, level: DEFAULT_LEVEL)
12
+ # Create a block codec.
13
+ # @param dict [Dictionary, String, nil] optional Zstandard dictionary
14
+ # @param level [Integer] compression level
15
+ # @return [BlockCodec]
16
+ #
17
+ # @!method self._native_new(dict, id, level)
18
+ # Native constructor used by `.new`.
19
+ # @param dict [String, nil] dictionary bytes
20
+ # @param id [Integer] dictionary ID, or `0` without a dictionary
21
+ # @param level [Integer] compression level
22
+ # @return [BlockCodec]
23
+ # @raise [CompressError]
24
+ #
25
+ # @!method compress(bytes)
26
+ # Compress bytes to a Zstandard block.
27
+ # @param bytes [String] uncompressed bytes
28
+ # @return [String]
29
+ # @raise [CompressError]
30
+ #
31
+ # @!method decompress(bytes, max_output_size: nil)
32
+ # Decompress a Zstandard block.
33
+ # @param bytes [String] compressed Zstandard block
34
+ # @param max_output_size [Integer, nil] optional output byte limit
35
+ # @return [String]
36
+ # @raise [DecompressError]
37
+ # @raise [OutputSizeLimitError]
38
+ #
39
+ # @!method _native_decompress(bytes, max_output_size)
40
+ # Native decompression entry used by #decompress.
41
+ # @param bytes [String] compressed Zstandard block
42
+ # @param max_output_size [Integer] output byte limit, or `0` for unbounded
43
+ # @return [String]
44
+ # @raise [DecompressError]
45
+ # @raise [OutputSizeLimitError]
46
+ #
47
+ # @!method has_dict?
48
+ # @return [Boolean]
49
+ #
50
+ # @!method size
51
+ # @return [Integer] dictionary size in bytes
52
+ #
53
+ # @!method level
54
+ # @return [Integer] compression level
6
55
  class BlockCodec
7
56
  def self.new(dict: nil, level: DEFAULT_LEVEL)
8
57
  case dict
@@ -1,6 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zrip
4
+ # FastCOVER-based Zstandard dictionary trainer.
5
+ #
6
+ # @!method self.new(max_dict_size)
7
+ # Create a dictionary trainer.
8
+ # @param max_dict_size [Integer] maximum dictionary size in bytes
9
+ # @return [DictTrainer]
10
+ #
11
+ # @!method self._native_new(max_dict_size)
12
+ # Native constructor used by `.new`.
13
+ # @param max_dict_size [Integer] maximum dictionary size in bytes
14
+ # @return [DictTrainer]
15
+ #
16
+ # @!method add_sample(bytes)
17
+ # Add a training sample. Samples shorter than 4 bytes are ignored.
18
+ # @param bytes [String] sample bytes
19
+ # @return [nil]
20
+ #
21
+ # @!method train
22
+ # Train and consume the trainer.
23
+ # @return [String] dictionary bytes
24
+ #
25
+ # @!method sample_count
26
+ # @return [Integer] accepted sample count
27
+ #
28
+ # @!method total_bytes
29
+ # @return [Integer] total bytes from accepted samples
30
+ #
31
+ # @!method trained?
32
+ # @return [Boolean]
33
+ #
34
+ # @!method max_dict_size
35
+ # @return [Integer] configured maximum dictionary size
4
36
  class DictTrainer
5
37
  def self.new(max_dict_size)
6
38
  _native_new(max_dict_size)