• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vigna / dsi-bitstream-rs / 29740807880

20 Jul 2026 11:52AM UTC coverage: 64.573% (+6.3%) from 58.294%
29740807880

push

github

zommiommy
fix(reader): align the peek_bits contract and assertions with PEEK_BITS

The BitRead::peek_bits doc said n may be as large as PeekWord::BITS, and
both BufBitReader implementations debug_assert!-ed that same loose bound,
but PEEK_BITS is one word and peek_bits performs at most one refill: for a
u32-word reader with an empty buffer, a documented peek_bits(64) loads only
32 bits (failing the availability assertion in debug builds and returning
an invalid partial value in release builds). The guarantee is PEEK_BITS,
so the trait doc, the stale BufBitReader doc paragraph (which still
advertised word size plus one), and the entry assertions now all say
PEEK_BITS.

Tests cover both sides of the boundary: peek_bits(PEEK_BITS) from a
completely empty buffer must be served by a single refill for u16/u32/u64
words and both endiannesses, and peek_bits(PEEK_BITS + 1) panics in debug
builds.

2 of 2 new or added lines in 1 file covered. (100.0%)

300 existing lines in 9 files now uncovered.

2570 of 3980 relevant lines covered (64.57%)

2512407.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

43.24
/src/dispatch/codes.rs
1
/*
2
 * SPDX-FileCopyrightText: 2025 Tommaso Fontana
3
 * SPDX-FileCopyrightText: 2025 Inria
4
 * SPDX-FileCopyrightText: 2025 Sebastiano Vigna
5
 *
6
 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
7
 */
8

9
//! Enumeration of all available codes, with associated read and write methods.
10
//!
11
//! This is the slower and more generic form of dispatching, mostly used for
12
//! testing and writing examples. For faster dispatching, consider using
13
//! [dynamic] or [static] dispatch.
14

15
use super::*;
16
#[cfg(feature = "serde")]
17
use alloc::string::{String, ToString};
18
#[cfg(feature = "mem_dbg")]
19
use mem_dbg::{MemDbg, MemSize};
20

21
/// An enum whose variants represent all the available codes.
22
///
23
/// This enum is kept in sync with implementations in the
24
/// [`codes`](crate::codes) module.
25
///
26
/// Both [`Display`](core::fmt::Display) and [`FromStr`](core::str::FromStr) are
27
/// implemented for this enum in a compatible way, making it possible to store a
28
/// code as a string in a configuration file and then parse it back.
29
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30
#[cfg_attr(feature = "mem_dbg", derive(MemDbg, MemSize))]
31
#[cfg_attr(feature = "mem_dbg", mem_size(flat))]
32
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
33
#[non_exhaustive]
34
pub enum Codes {
35
    Unary,
36
    Gamma,
37
    Delta,
38
    Omega,
39
    VByteLe,
40
    VByteBe,
41
    Zeta(usize),
42
    Pi(usize),
43
    Golomb(u64),
44
    ExpGolomb(usize),
45
    Rice(usize),
46
}
47

48
impl Codes {
49
    /// Returns the canonical form of this code.
50
    ///
51
    /// Some codes are equivalent, in the sense that they are defined
52
    /// differently, but they give rise to the same codewords. Among equivalent
53
    /// codes, there is usually one that is faster to encode and decode, which
54
    /// we call the _canonical representative_ of the equivalence class.
55
    ///
56
    /// The mapping is:
57
    ///
58
    /// - [`Rice(0)`](Codes::Rice),
59
    ///   [`Golomb(1)`](Codes::Golomb) →
60
    ///   [`Unary`](Codes::Unary)
61
    ///
62
    /// - [`Zeta(1)`](Codes::Zeta),
63
    ///   [`ExpGolomb(0)`](Codes::ExpGolomb),
64
    ///   [`Pi(0)`](Codes::Pi) →
65
    ///   [`Gamma`](Codes::Gamma)
66
    ///
67
    /// - [`Golomb(2ⁿ)`](Codes::Golomb) → [`Rice(n)`](Codes::Rice)
68
    #[must_use]
69
    pub const fn canonicalize(self) -> Self {
691,248✔
70
        match self {
98,436✔
71
            Self::Zeta(1) | Self::ExpGolomb(0) | Self::Pi(0) => Self::Gamma,
39,348✔
72
            Self::Rice(0) | Self::Golomb(1) => Self::Unary,
24,672✔
73
            Self::Golomb(b) if b.is_power_of_two() => Self::Rice(b.trailing_zeros() as usize),
307,464✔
74
            other => other,
1,180,728✔
75
        }
76
    }
77

78
    /// Delegates to the [`DynamicCodeRead`] implementation.
79
    ///
80
    /// This inherent method is provided to reduce ambiguity in method
81
    /// resolution.
82
    #[inline(always)]
83
    pub fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
×
84
        &self,
85
        reader: &mut CR,
86
    ) -> Result<u64, CR::Error> {
87
        DynamicCodeRead::read(self, reader)
×
88
    }
89

90
    /// Delegates to the [`DynamicCodeWrite`] implementation.
91
    ///
92
    /// This inherent method is provided to reduce ambiguity in method
93
    /// resolution.
94
    #[inline(always)]
95
    pub fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
×
96
        &self,
97
        writer: &mut CW,
98
        n: u64,
99
    ) -> Result<usize, CW::Error> {
100
        DynamicCodeWrite::write(self, writer, n)
×
101
    }
102

103
    /// Converts a code to the constants in the [`code_consts`] module used for
104
    /// [`ConstCode`]. This is mostly used to verify that the code is supported
105
    /// by [`ConstCode`].
106
    ///
107
    /// The code is [canonicalized](Codes::canonicalize) before
108
    /// the conversion, so equivalent codes map to the same
109
    /// constant.
110
    ///
111
    /// # Errors
112
    ///
113
    /// Returns [`DispatchError::UnsupportedCode`] if the (canonicalized)
114
    /// code has no corresponding constant in [`code_consts`].
115
    pub const fn to_code_const(&self) -> Result<usize, DispatchError> {
×
116
        Ok(match self.canonicalize() {
×
117
            Self::Unary => code_consts::UNARY,
×
118
            Self::Gamma => code_consts::GAMMA,
×
119
            Self::Delta => code_consts::DELTA,
×
120
            Self::Omega => code_consts::OMEGA,
×
121
            Self::VByteLe => code_consts::VBYTE_LE,
×
122
            Self::VByteBe => code_consts::VBYTE_BE,
×
123
            Self::Zeta(2) => code_consts::ZETA2,
×
124
            Self::Zeta(3) => code_consts::ZETA3,
×
125
            Self::Zeta(4) => code_consts::ZETA4,
×
126
            Self::Zeta(5) => code_consts::ZETA5,
×
127
            Self::Zeta(6) => code_consts::ZETA6,
×
128
            Self::Zeta(7) => code_consts::ZETA7,
×
129
            Self::Zeta(8) => code_consts::ZETA8,
×
130
            Self::Zeta(9) => code_consts::ZETA9,
×
131
            Self::Zeta(10) => code_consts::ZETA10,
×
132
            Self::Rice(1) => code_consts::RICE1,
×
133
            Self::Rice(2) => code_consts::RICE2,
×
134
            Self::Rice(3) => code_consts::RICE3,
×
135
            Self::Rice(4) => code_consts::RICE4,
×
136
            Self::Rice(5) => code_consts::RICE5,
×
137
            Self::Rice(6) => code_consts::RICE6,
×
138
            Self::Rice(7) => code_consts::RICE7,
×
139
            Self::Rice(8) => code_consts::RICE8,
×
140
            Self::Rice(9) => code_consts::RICE9,
×
141
            Self::Rice(10) => code_consts::RICE10,
×
142
            Self::Pi(1) => code_consts::PI1,
×
143
            Self::Pi(2) => code_consts::PI2,
×
144
            Self::Pi(3) => code_consts::PI3,
×
145
            Self::Pi(4) => code_consts::PI4,
×
146
            Self::Pi(5) => code_consts::PI5,
×
147
            Self::Pi(6) => code_consts::PI6,
×
148
            Self::Pi(7) => code_consts::PI7,
×
149
            Self::Pi(8) => code_consts::PI8,
×
150
            Self::Pi(9) => code_consts::PI9,
×
151
            Self::Pi(10) => code_consts::PI10,
×
152
            Self::Golomb(3) => code_consts::GOLOMB3,
×
153
            Self::Golomb(5) => code_consts::GOLOMB5,
×
154
            Self::Golomb(6) => code_consts::GOLOMB6,
×
155
            Self::Golomb(7) => code_consts::GOLOMB7,
×
156
            Self::Golomb(9) => code_consts::GOLOMB9,
×
157
            Self::Golomb(10) => code_consts::GOLOMB10,
×
158
            Self::ExpGolomb(1) => code_consts::EXP_GOLOMB1,
×
159
            Self::ExpGolomb(2) => code_consts::EXP_GOLOMB2,
×
160
            Self::ExpGolomb(3) => code_consts::EXP_GOLOMB3,
×
161
            Self::ExpGolomb(4) => code_consts::EXP_GOLOMB4,
×
162
            Self::ExpGolomb(5) => code_consts::EXP_GOLOMB5,
×
163
            Self::ExpGolomb(6) => code_consts::EXP_GOLOMB6,
×
164
            Self::ExpGolomb(7) => code_consts::EXP_GOLOMB7,
×
165
            Self::ExpGolomb(8) => code_consts::EXP_GOLOMB8,
×
166
            Self::ExpGolomb(9) => code_consts::EXP_GOLOMB9,
×
167
            Self::ExpGolomb(10) => code_consts::EXP_GOLOMB10,
×
168
            _ => {
169
                return Err(DispatchError::UnsupportedCode(*self));
×
170
            }
171
        })
172
    }
173

174
    /// Converts a value from [`code_consts`] to a code.
175
    ///
176
    /// # Errors
177
    ///
178
    /// Returns [`DispatchError::UnsupportedCodeConst`] if the value
179
    /// does not correspond to any known code constant.
180
    pub const fn from_code_const(const_code: usize) -> Result<Self, DispatchError> {
×
181
        Ok(match const_code {
×
182
            code_consts::UNARY => Self::Unary,
×
183
            code_consts::GAMMA => Self::Gamma,
×
184
            code_consts::DELTA => Self::Delta,
×
185
            code_consts::OMEGA => Self::Omega,
×
186
            code_consts::VBYTE_LE => Self::VByteLe,
×
187
            code_consts::VBYTE_BE => Self::VByteBe,
×
188
            code_consts::ZETA2 => Self::Zeta(2),
×
189
            code_consts::ZETA3 => Self::Zeta(3),
×
190
            code_consts::ZETA4 => Self::Zeta(4),
×
191
            code_consts::ZETA5 => Self::Zeta(5),
×
192
            code_consts::ZETA6 => Self::Zeta(6),
×
193
            code_consts::ZETA7 => Self::Zeta(7),
×
194
            code_consts::ZETA8 => Self::Zeta(8),
×
195
            code_consts::ZETA9 => Self::Zeta(9),
×
196
            code_consts::ZETA10 => Self::Zeta(10),
×
197
            code_consts::RICE1 => Self::Rice(1),
×
198
            code_consts::RICE2 => Self::Rice(2),
×
199
            code_consts::RICE3 => Self::Rice(3),
×
200
            code_consts::RICE4 => Self::Rice(4),
×
201
            code_consts::RICE5 => Self::Rice(5),
×
202
            code_consts::RICE6 => Self::Rice(6),
×
203
            code_consts::RICE7 => Self::Rice(7),
×
204
            code_consts::RICE8 => Self::Rice(8),
×
205
            code_consts::RICE9 => Self::Rice(9),
×
206
            code_consts::RICE10 => Self::Rice(10),
×
207
            code_consts::PI1 => Self::Pi(1),
×
208
            code_consts::PI2 => Self::Pi(2),
×
209
            code_consts::PI3 => Self::Pi(3),
×
210
            code_consts::PI4 => Self::Pi(4),
×
211
            code_consts::PI5 => Self::Pi(5),
×
212
            code_consts::PI6 => Self::Pi(6),
×
213
            code_consts::PI7 => Self::Pi(7),
×
214
            code_consts::PI8 => Self::Pi(8),
×
215
            code_consts::PI9 => Self::Pi(9),
×
216
            code_consts::PI10 => Self::Pi(10),
×
217
            code_consts::GOLOMB3 => Self::Golomb(3),
×
218
            code_consts::GOLOMB5 => Self::Golomb(5),
×
219
            code_consts::GOLOMB6 => Self::Golomb(6),
×
220
            code_consts::GOLOMB7 => Self::Golomb(7),
×
221
            code_consts::GOLOMB9 => Self::Golomb(9),
×
222
            code_consts::GOLOMB10 => Self::Golomb(10),
×
223
            code_consts::EXP_GOLOMB1 => Self::ExpGolomb(1),
×
224
            code_consts::EXP_GOLOMB2 => Self::ExpGolomb(2),
×
225
            code_consts::EXP_GOLOMB3 => Self::ExpGolomb(3),
×
226
            code_consts::EXP_GOLOMB4 => Self::ExpGolomb(4),
×
227
            code_consts::EXP_GOLOMB5 => Self::ExpGolomb(5),
×
228
            code_consts::EXP_GOLOMB6 => Self::ExpGolomb(6),
×
229
            code_consts::EXP_GOLOMB7 => Self::ExpGolomb(7),
×
230
            code_consts::EXP_GOLOMB8 => Self::ExpGolomb(8),
×
231
            code_consts::EXP_GOLOMB9 => Self::ExpGolomb(9),
×
232
            code_consts::EXP_GOLOMB10 => Self::ExpGolomb(10),
×
233
            _ => return Err(DispatchError::UnsupportedCodeConst(const_code)),
×
234
        })
235
    }
236
}
237

238
impl DynamicCodeRead for Codes {
239
    #[inline]
240
    fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
230,372✔
241
        &self,
242
        reader: &mut CR,
243
    ) -> Result<u64, CR::Error> {
244
        Ok(match self.canonicalize() {
230,372✔
245
            Codes::Unary => reader.read_unary()?,
24,660✔
246
            Codes::Gamma => reader.read_gamma()?,
34,960✔
247
            Codes::Delta => reader.read_delta()?,
8,740✔
248
            Codes::Omega => reader.read_omega()?,
8,740✔
249
            Codes::VByteBe => reader.read_vbyte_be()?,
8,748✔
250
            Codes::VByteLe => reader.read_vbyte_le()?,
8,748✔
251
            Codes::Zeta(3) => reader.read_zeta3()?,
8,740✔
252
            Codes::Zeta(k) => reader.read_zeta(k)?,
122,080✔
253
            Codes::Pi(2) => reader.read_pi2()?,
8,712✔
254
            Codes::Pi(k) => reader.read_pi(k)?,
139,504✔
255
            Codes::Golomb(b) => reader.read_golomb(b)?,
82,080✔
256
            Codes::ExpGolomb(k) => reader.read_exp_golomb(k)?,
156,960✔
257
            Codes::Rice(log2_b) => reader.read_rice(log2_b)?,
196,768✔
258
        })
259
    }
260
}
261

262
impl DynamicCodeWrite for Codes {
263
    #[inline]
264
    fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
230,372✔
265
        &self,
266
        writer: &mut CW,
267
        n: u64,
268
    ) -> Result<usize, CW::Error> {
269
        Ok(match self.canonicalize() {
230,372✔
270
            Codes::Unary => writer.write_unary(n)?,
36,990✔
271
            Codes::Gamma => writer.write_gamma(n)?,
52,440✔
272
            Codes::Delta => writer.write_delta(n)?,
13,110✔
273
            Codes::Omega => writer.write_omega(n)?,
13,110✔
274
            Codes::VByteBe => writer.write_vbyte_be(n)?,
13,122✔
275
            Codes::VByteLe => writer.write_vbyte_le(n)?,
13,122✔
276
            Codes::Zeta(3) => writer.write_zeta3(n)?,
13,110✔
277
            Codes::Zeta(k) => writer.write_zeta(n, k)?,
152,600✔
278
            Codes::Pi(2) => writer.write_pi2(n)?,
13,068✔
279
            Codes::Pi(k) => writer.write_pi(n, k)?,
174,380✔
280
            Codes::Golomb(b) => writer.write_golomb(n, b)?,
102,600✔
281
            Codes::ExpGolomb(k) => writer.write_exp_golomb(n, k)?,
196,200✔
282
            Codes::Rice(log2_b) => writer.write_rice(n, log2_b)?,
245,960✔
283
        })
284
    }
285
}
286

287
impl<E: Endianness, CR: CodesRead<E> + ?Sized> StaticCodeRead<E, CR> for Codes {
288
    #[inline(always)]
289
    fn read(&self, reader: &mut CR) -> Result<u64, CR::Error> {
115,032✔
290
        <Self as DynamicCodeRead>::read(self, reader)
345,096✔
291
    }
292
}
293

294
impl<E: Endianness, CW: CodesWrite<E> + ?Sized> StaticCodeWrite<E, CW> for Codes {
295
    #[inline(always)]
296
    fn write(&self, writer: &mut CW, n: u64) -> Result<usize, CW::Error> {
115,032✔
297
        <Self as DynamicCodeWrite>::write(self, writer, n)
460,128✔
298
    }
299
}
300

301
impl CodeLen for Codes {
302
    #[inline]
303
    fn len(&self, n: u64) -> usize {
230,372✔
304
        match self.canonicalize() {
230,372✔
305
            // The checked conversion avoids truncation on 32-bit platforms
306
            Codes::Unary => n
12,330✔
307
                .checked_add(1)
308
                .and_then(|len| usize::try_from(len).ok())
49,320✔
309
                .expect("unary code length does not fit in a usize"),
310
            Codes::Gamma => len_gamma(n),
34,960✔
311
            Codes::Delta => len_delta(n),
8,740✔
312
            Codes::Omega => len_omega(n),
8,740✔
313
            Codes::VByteLe | Codes::VByteBe => bit_len_vbyte(n),
17,496✔
314
            Codes::Zeta(k) => len_zeta(n, k),
139,560✔
315
            Codes::Pi(k) => len_pi(n, k),
156,928✔
316
            Codes::Golomb(b) => len_golomb(n, b),
82,080✔
317
            Codes::ExpGolomb(k) => len_exp_golomb(n, k),
156,960✔
318
            Codes::Rice(log2_b) => len_rice(n, log2_b),
196,768✔
319
        }
320
    }
321
}
322

323
/// Error type for parsing a code from a string.
324
#[derive(Debug, Clone)]
325
pub enum CodeError {
326
    /// Error parsing an integer parameter.
327
    ParseError(core::num::ParseIntError),
328
    /// Unknown code name. Uses a fixed-size array instead of `String` for `no_std` compatibility.
329
    UnknownCode([u8; 32]),
330
    /// A parameter is outside the valid range for its code (for example
331
    /// `Golomb(0)`, `Zeta(0)`, or a shift parameter `>= 64`).
332
    InvalidParameter([u8; 32]),
333
}
334
impl core::error::Error for CodeError {}
335
impl core::fmt::Display for CodeError {
336
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
×
337
        match self {
×
338
            CodeError::ParseError(e) => write!(f, "parse error: {}", e),
×
339
            CodeError::UnknownCode(s) => {
×
340
                write!(f, "unknown code: ")?;
×
UNCOV
341
                for c in s {
×
342
                    if *c == 0 {
×
UNCOV
343
                        break;
×
344
                    }
UNCOV
345
                    write!(f, "{}", *c as char)?;
×
346
                }
UNCOV
347
                Ok(())
×
348
            }
UNCOV
349
            CodeError::InvalidParameter(s) => {
×
UNCOV
350
                write!(f, "invalid parameter for code: ")?;
×
351
                for c in s {
×
352
                    if *c == 0 {
×
UNCOV
353
                        break;
×
354
                    }
UNCOV
355
                    write!(f, "{}", *c as char)?;
×
356
                }
357
                Ok(())
×
358
            }
359
        }
360
    }
361
}
362

363
impl From<core::num::ParseIntError> for CodeError {
364
    fn from(e: core::num::ParseIntError) -> Self {
1✔
365
        CodeError::ParseError(e)
1✔
366
    }
367
}
368

369
impl core::fmt::Display for Codes {
UNCOV
370
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
×
UNCOV
371
        match self {
×
UNCOV
372
            Codes::Unary => write!(f, "Unary"),
×
UNCOV
373
            Codes::Gamma => write!(f, "Gamma"),
×
374
            Codes::Delta => write!(f, "Delta"),
×
375
            Codes::Omega => write!(f, "Omega"),
×
376
            Codes::VByteBe => write!(f, "VByteBe"),
×
377
            Codes::VByteLe => write!(f, "VByteLe"),
×
378
            Codes::Zeta(k) => write!(f, "Zeta({})", k),
×
UNCOV
379
            Codes::Pi(k) => write!(f, "Pi({})", k),
×
UNCOV
380
            Codes::Golomb(b) => write!(f, "Golomb({})", b),
×
UNCOV
381
            Codes::ExpGolomb(k) => write!(f, "ExpGolomb({})", k),
×
UNCOV
382
            Codes::Rice(log2_b) => write!(f, "Rice({})", log2_b),
×
383
        }
384
    }
385
}
386

387
fn array_format_error(s: &str) -> [u8; 32] {
10✔
388
    let mut error_buffer = [0u8; 32];
20✔
389
    let len = s.len().min(32);
40✔
390
    error_buffer[..len].copy_from_slice(&s.as_bytes()[..len]);
30✔
391
    error_buffer
10✔
392
}
393

394
impl core::str::FromStr for Codes {
395
    type Err = CodeError;
396

397
    fn from_str(s: &str) -> Result<Self, Self::Err> {
18✔
398
        match s {
18✔
399
            "Unary" => Ok(Codes::Unary),
19✔
400
            "Gamma" => Ok(Codes::Gamma),
17✔
401
            "Delta" => Ok(Codes::Delta),
17✔
402
            "Omega" => Ok(Codes::Omega),
17✔
403
            "VByteBe" => Ok(Codes::VByteBe),
17✔
404
            "VByteLe" => Ok(Codes::VByteLe),
17✔
405

406
            _ => {
407
                // Strict grammar: exactly `Name(param)` with `param` a single
408
                // integer and nothing after the closing parenthesis. The former
409
                // `split` accepted trailing/incomplete text (e.g. `Zeta(3)x`).
410
                let inner = s
32✔
411
                    .strip_suffix(')')
412
                    .ok_or_else(|| CodeError::UnknownCode(array_format_error(s)))?;
23✔
413
                let (name, param) = inner
45✔
414
                    .split_once('(')
415
                    .ok_or_else(|| CodeError::UnknownCode(array_format_error(s)))?;
15✔
416
                if param.contains('(') || param.contains(')') {
58✔
417
                    return Err(CodeError::UnknownCode(array_format_error(s)));
1✔
418
                }
419
                let invalid = || CodeError::InvalidParameter(array_format_error(s));
26✔
420
                match name {
14✔
421
                    // zeta is defined for k in [1, 64).
422
                    "Zeta" => {
14✔
423
                        let k: usize = param.parse()?;
15✔
424
                        if k == 0 || k >= 64 {
5✔
425
                            return Err(invalid());
2✔
426
                        }
427
                        Ok(Codes::Zeta(k))
1✔
428
                    }
429
                    // pi, exponential Golomb and Rice shift by their parameter,
430
                    // so it must stay below 64.
431
                    "Pi" => {
10✔
432
                        let k: usize = param.parse()?;
8✔
433
                        if k >= 64 {
2✔
434
                            return Err(invalid());
1✔
435
                        }
436
                        Ok(Codes::Pi(k))
1✔
437
                    }
438
                    "ExpGolomb" => {
8✔
439
                        let k: usize = param.parse()?;
8✔
440
                        if k >= 64 {
2✔
441
                            return Err(invalid());
1✔
442
                        }
443
                        Ok(Codes::ExpGolomb(k))
1✔
444
                    }
445
                    "Rice" => {
6✔
446
                        let k: usize = param.parse()?;
12✔
447
                        if k >= 64 {
3✔
448
                            return Err(invalid());
1✔
449
                        }
450
                        Ok(Codes::Rice(k))
2✔
451
                    }
452
                    // Golomb divides by its modulus, which must be positive.
453
                    "Golomb" => {
3✔
454
                        let b: u64 = param.parse()?;
8✔
455
                        if b == 0 {
2✔
456
                            return Err(invalid());
1✔
457
                        }
458
                        Ok(Codes::Golomb(b))
1✔
459
                    }
460
                    _ => Err(CodeError::UnknownCode(array_format_error(name))),
1✔
461
                }
462
            }
463
        }
464
    }
465
}
466

467
#[cfg(feature = "serde")]
468
impl serde::Serialize for Codes {
UNCOV
469
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
×
470
    where
471
        S: serde::Serializer,
472
    {
UNCOV
473
        serializer.serialize_str(&self.to_string())
×
474
    }
475
}
476

477
#[cfg(feature = "serde")]
478
impl<'de> serde::Deserialize<'de> for Codes {
UNCOV
479
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
×
480
    where
481
        D: serde::Deserializer<'de>,
482
    {
UNCOV
483
        let s = String::deserialize(deserializer)?;
×
UNCOV
484
        s.parse().map_err(serde::de::Error::custom)
×
485
    }
486
}
487

488
/// Structure representing minimal binary coding with a fixed upper bound.
489
///
490
/// [Minimal binary coding](crate::codes::minimal_binary) does not
491
/// fit the [`Codes`] enum because it is not defined for all integers.
492
///
493
/// Instances of this structure can be used in contexts in which a
494
/// [`DynamicCodeRead`], [`DynamicCodeWrite`], [`StaticCodeRead`],
495
/// [`StaticCodeWrite`] or [`CodeLen`] implementing minimal binary coding
496
/// is necessary.
497
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
498
pub struct MinimalBinary(
499
    /// The upper bound of the minimal binary code.
500
    pub u64,
501
);
502

503
impl DynamicCodeRead for MinimalBinary {
504
    fn read<E: Endianness, R: CodesRead<E> + ?Sized>(
48✔
505
        &self,
506
        reader: &mut R,
507
    ) -> Result<u64, R::Error> {
508
        reader.read_minimal_binary(self.0)
144✔
509
    }
510
}
511

512
impl DynamicCodeWrite for MinimalBinary {
513
    fn write<E: Endianness, W: CodesWrite<E> + ?Sized>(
48✔
514
        &self,
515
        writer: &mut W,
516
        n: u64,
517
    ) -> Result<usize, W::Error> {
518
        writer.write_minimal_binary(n, self.0)
192✔
519
    }
520
}
521

522
impl<E: Endianness, CR: CodesRead<E> + ?Sized> StaticCodeRead<E, CR> for MinimalBinary {
523
    fn read(&self, reader: &mut CR) -> Result<u64, CR::Error> {
24✔
524
        <Self as DynamicCodeRead>::read(self, reader)
72✔
525
    }
526
}
527

528
impl<E: Endianness, CW: CodesWrite<E> + ?Sized> StaticCodeWrite<E, CW> for MinimalBinary {
529
    fn write(&self, writer: &mut CW, n: u64) -> Result<usize, CW::Error> {
24✔
530
        <Self as DynamicCodeWrite>::write(self, writer, n)
96✔
531
    }
532
}
533

534
impl CodeLen for MinimalBinary {
535
    fn len(&self, n: u64) -> usize {
48✔
536
        len_minimal_binary(n, self.0)
144✔
537
    }
538
}
539

540
#[cfg(test)]
541
mod parse_tests {
542
    use super::Codes;
543
    use core::str::FromStr;
544

545
    #[test]
546
    fn parses_valid_parameterized_codes() {
547
        assert_eq!(Codes::from_str("Unary").unwrap(), Codes::Unary);
548
        assert_eq!(Codes::from_str("Zeta(3)").unwrap(), Codes::Zeta(3));
549
        assert_eq!(Codes::from_str("Pi(2)").unwrap(), Codes::Pi(2));
550
        assert_eq!(Codes::from_str("Golomb(7)").unwrap(), Codes::Golomb(7));
551
        assert_eq!(
552
            Codes::from_str("ExpGolomb(0)").unwrap(),
553
            Codes::ExpGolomb(0)
554
        );
555
        assert_eq!(Codes::from_str("Rice(0)").unwrap(), Codes::Rice(0));
556
        assert_eq!(Codes::from_str("Rice(63)").unwrap(), Codes::Rice(63));
557
    }
558

559
    #[test]
560
    fn rejects_out_of_range_parameters() {
561
        // These previously deserialized into values that panic on first use
562
        // (divide-by-zero) or shift by >= 64.
563
        assert!(Codes::from_str("Golomb(0)").is_err());
564
        assert!(Codes::from_str("Zeta(0)").is_err());
565
        assert!(Codes::from_str("Zeta(64)").is_err());
566
        assert!(Codes::from_str("Rice(64)").is_err());
567
        assert!(Codes::from_str("Pi(64)").is_err());
568
        assert!(Codes::from_str("ExpGolomb(64)").is_err());
569
    }
570

571
    #[test]
572
    fn rejects_malformed_grammar() {
573
        assert!(Codes::from_str("Zeta(3").is_err());
574
        assert!(Codes::from_str("Zeta(3)x").is_err());
575
        assert!(Codes::from_str("Zeta(3)(4)").is_err());
576
        assert!(Codes::from_str("Zeta()").is_err());
577
        assert!(Codes::from_str("Nope(1)").is_err());
578
    }
579
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc