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

vigna / dsi-bitstream-rs / 29834581278

21 Jul 2026 01:28PM UTC coverage: 64.573%. Remained the same
29834581278

push

github

vigna
Cosmetic changes to LAW Rust development guidelines

21 of 45 new or added lines in 9 files covered. (46.67%)

2570 of 3980 relevant lines covered (64.57%)

2511891.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 [`codes`] module.
24
///
25
/// Both [`Display`] and [`FromStr`] are implemented for this enum in a
26
/// compatible way, making it possible to store a code as a string in a
27
/// configuration file and then parse it back.
28
///
29
/// [`codes`]: crate::codes
30
/// [`Display`]: core::fmt::Display
31
/// [`FromStr`]: core::str::FromStr
32
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33
#[cfg_attr(feature = "mem_dbg", derive(MemDbg, MemSize))]
34
#[cfg_attr(feature = "mem_dbg", mem_size(flat))]
35
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
36
#[non_exhaustive]
37
pub enum Codes {
38
    Unary,
39
    Gamma,
40
    Delta,
41
    Omega,
42
    VByteLe,
43
    VByteBe,
44
    Zeta(usize),
45
    Pi(usize),
46
    Golomb(u64),
47
    ExpGolomb(usize),
48
    Rice(usize),
49
}
50

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

86
    /// Delegates to the [`DynamicCodeRead`] implementation.
87
    ///
88
    /// This inherent method is provided to reduce ambiguity in method
89
    /// resolution.
90
    #[inline(always)]
91
    pub fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
×
92
        &self,
93
        reader: &mut CR,
94
    ) -> Result<u64, CR::Error> {
95
        DynamicCodeRead::read(self, reader)
×
96
    }
97

98
    /// Delegates to the [`DynamicCodeWrite`] implementation.
99
    ///
100
    /// This inherent method is provided to reduce ambiguity in method
101
    /// resolution.
102
    #[inline(always)]
103
    pub fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
×
104
        &self,
105
        writer: &mut CW,
106
        n: u64,
107
    ) -> Result<usize, CW::Error> {
108
        DynamicCodeWrite::write(self, writer, n)
×
109
    }
110

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

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

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

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

296
impl<E: Endianness, CR: CodesRead<E> + ?Sized> StaticCodeRead<E, CR> for Codes {
297
    #[inline(always)]
298
    fn read(&self, reader: &mut CR) -> Result<u64, CR::Error> {
115,032✔
299
        <Self as DynamicCodeRead>::read(self, reader)
345,096✔
300
    }
301
}
302

303
impl<E: Endianness, CW: CodesWrite<E> + ?Sized> StaticCodeWrite<E, CW> for Codes {
304
    #[inline(always)]
305
    fn write(&self, writer: &mut CW, n: u64) -> Result<usize, CW::Error> {
115,032✔
306
        <Self as DynamicCodeWrite>::write(self, writer, n)
460,128✔
307
    }
308
}
309

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

332
#[cfg(feature = "serde")]
333
impl serde::Serialize for Codes {
NEW
334
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
×
NEW
335
        serializer.serialize_str(&self.to_string())
×
336
    }
337
}
338

339
#[cfg(feature = "serde")]
340
impl<'de> serde::Deserialize<'de> for Codes {
NEW
341
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
×
NEW
342
        let s = String::deserialize(deserializer)?;
×
NEW
343
        s.parse().map_err(serde::de::Error::custom)
×
344
    }
345
}
346

347
impl core::fmt::Display for Codes {
348
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
×
349
        match self {
×
350
            Codes::Unary => write!(f, "Unary"),
×
351
            Codes::Gamma => write!(f, "Gamma"),
×
352
            Codes::Delta => write!(f, "Delta"),
×
353
            Codes::Omega => write!(f, "Omega"),
×
354
            Codes::VByteBe => write!(f, "VByteBe"),
×
355
            Codes::VByteLe => write!(f, "VByteLe"),
×
356
            Codes::Zeta(k) => write!(f, "Zeta({})", k),
×
357
            Codes::Pi(k) => write!(f, "Pi({})", k),
×
358
            Codes::Golomb(b) => write!(f, "Golomb({})", b),
×
359
            Codes::ExpGolomb(k) => write!(f, "ExpGolomb({})", k),
×
360
            Codes::Rice(log2_b) => write!(f, "Rice({})", log2_b),
×
361
        }
362
    }
363
}
364

365
fn array_format_error(s: &str) -> [u8; 32] {
10✔
366
    let mut error_buffer = [0u8; 32];
20✔
367
    let len = s.len().min(32);
40✔
368
    error_buffer[..len].copy_from_slice(&s.as_bytes()[..len]);
30✔
369
    error_buffer
10✔
370
}
371

372
impl core::str::FromStr for Codes {
373
    type Err = CodeError;
374

375
    fn from_str(s: &str) -> Result<Self, Self::Err> {
18✔
376
        match s {
18✔
377
            "Unary" => Ok(Codes::Unary),
19✔
378
            "Gamma" => Ok(Codes::Gamma),
17✔
379
            "Delta" => Ok(Codes::Delta),
17✔
380
            "Omega" => Ok(Codes::Omega),
17✔
381
            "VByteBe" => Ok(Codes::VByteBe),
17✔
382
            "VByteLe" => Ok(Codes::VByteLe),
17✔
383

384
            _ => {
385
                // Strict grammar: exactly `Name(param)` with `param` a single
386
                // integer and nothing after the closing parenthesis. The former
387
                // `split` accepted trailing/incomplete text (e.g. `Zeta(3)x`).
388
                let inner = s
32✔
389
                    .strip_suffix(')')
390
                    .ok_or_else(|| CodeError::UnknownCode(array_format_error(s)))?;
23✔
391
                let (name, param) = inner
45✔
392
                    .split_once('(')
393
                    .ok_or_else(|| CodeError::UnknownCode(array_format_error(s)))?;
15✔
394
                if param.contains('(') || param.contains(')') {
58✔
395
                    return Err(CodeError::UnknownCode(array_format_error(s)));
1✔
396
                }
397
                let invalid = || CodeError::InvalidParameter(array_format_error(s));
26✔
398
                match name {
14✔
399
                    // zeta is defined for k in [1..64).
400
                    "Zeta" => {
14✔
401
                        let k: usize = param.parse()?;
15✔
402
                        if k == 0 || k >= 64 {
5✔
403
                            return Err(invalid());
2✔
404
                        }
405
                        Ok(Codes::Zeta(k))
1✔
406
                    }
407
                    // pi, exponential Golomb and Rice shift by their parameter,
408
                    // so it must stay below 64.
409
                    "Pi" => {
10✔
410
                        let k: usize = param.parse()?;
8✔
411
                        if k >= 64 {
2✔
412
                            return Err(invalid());
1✔
413
                        }
414
                        Ok(Codes::Pi(k))
1✔
415
                    }
416
                    "ExpGolomb" => {
8✔
417
                        let k: usize = param.parse()?;
8✔
418
                        if k >= 64 {
2✔
419
                            return Err(invalid());
1✔
420
                        }
421
                        Ok(Codes::ExpGolomb(k))
1✔
422
                    }
423
                    "Rice" => {
6✔
424
                        let k: usize = param.parse()?;
12✔
425
                        if k >= 64 {
3✔
426
                            return Err(invalid());
1✔
427
                        }
428
                        Ok(Codes::Rice(k))
2✔
429
                    }
430
                    // Golomb divides by its modulus, which must be positive.
431
                    "Golomb" => {
3✔
432
                        let b: u64 = param.parse()?;
8✔
433
                        if b == 0 {
2✔
434
                            return Err(invalid());
1✔
435
                        }
436
                        Ok(Codes::Golomb(b))
1✔
437
                    }
438
                    _ => Err(CodeError::UnknownCode(array_format_error(name))),
1✔
439
                }
440
            }
441
        }
442
    }
443
}
444

445
/// Error type for parsing a code from a string.
446
#[derive(Debug, Clone)]
447
pub enum CodeError {
448
    /// Error parsing an integer parameter.
449
    ParseError(core::num::ParseIntError),
450
    /// Unknown code name. Uses a fixed-size array instead of `String` for `no_std` compatibility.
451
    UnknownCode([u8; 32]),
452
    /// A parameter is outside the valid range for its code (for example
453
    /// `Golomb(0)`, `Zeta(0)`, or a shift parameter `>= 64`).
454
    InvalidParameter([u8; 32]),
455
}
456
impl core::error::Error for CodeError {}
457
impl core::fmt::Display for CodeError {
NEW
458
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
×
NEW
459
        match self {
×
NEW
460
            CodeError::ParseError(e) => write!(f, "parse error: {}", e),
×
NEW
461
            CodeError::UnknownCode(s) => {
×
NEW
462
                write!(f, "unknown code: ")?;
×
NEW
463
                for c in s {
×
NEW
464
                    if *c == 0 {
×
NEW
465
                        break;
×
466
                    }
NEW
467
                    write!(f, "{}", *c as char)?;
×
468
                }
NEW
469
                Ok(())
×
470
            }
NEW
471
            CodeError::InvalidParameter(s) => {
×
NEW
472
                write!(f, "invalid parameter for code: ")?;
×
NEW
473
                for c in s {
×
NEW
474
                    if *c == 0 {
×
NEW
475
                        break;
×
476
                    }
NEW
477
                    write!(f, "{}", *c as char)?;
×
478
                }
NEW
479
                Ok(())
×
480
            }
481
        }
482
    }
483
}
484

485
impl From<core::num::ParseIntError> for CodeError {
486
    fn from(e: core::num::ParseIntError) -> Self {
1✔
487
        CodeError::ParseError(e)
1✔
488
    }
489
}
490

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

508
impl DynamicCodeRead for MinimalBinary {
509
    fn read<E: Endianness, R: CodesRead<E> + ?Sized>(
48✔
510
        &self,
511
        reader: &mut R,
512
    ) -> Result<u64, R::Error> {
513
        reader.read_minimal_binary(self.0)
144✔
514
    }
515
}
516

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

527
impl<E: Endianness, CR: CodesRead<E> + ?Sized> StaticCodeRead<E, CR> for MinimalBinary {
528
    fn read(&self, reader: &mut CR) -> Result<u64, CR::Error> {
24✔
529
        <Self as DynamicCodeRead>::read(self, reader)
72✔
530
    }
531
}
532

533
impl<E: Endianness, CW: CodesWrite<E> + ?Sized> StaticCodeWrite<E, CW> for MinimalBinary {
534
    fn write(&self, writer: &mut CW, n: u64) -> Result<usize, CW::Error> {
24✔
535
        <Self as DynamicCodeWrite>::write(self, writer, n)
96✔
536
    }
537
}
538

539
impl CodeLen for MinimalBinary {
540
    fn len(&self, n: u64) -> usize {
48✔
541
        len_minimal_binary(n, self.0)
144✔
542
    }
543
}
544

545
#[cfg(test)]
546
mod parse_tests {
547
    use super::Codes;
548
    use core::str::FromStr;
549

550
    #[test]
551
    fn test_parses_valid_parameterized_codes() {
552
        assert_eq!(Codes::from_str("Unary").unwrap(), Codes::Unary);
553
        assert_eq!(Codes::from_str("Zeta(3)").unwrap(), Codes::Zeta(3));
554
        assert_eq!(Codes::from_str("Pi(2)").unwrap(), Codes::Pi(2));
555
        assert_eq!(Codes::from_str("Golomb(7)").unwrap(), Codes::Golomb(7));
556
        assert_eq!(
557
            Codes::from_str("ExpGolomb(0)").unwrap(),
558
            Codes::ExpGolomb(0)
559
        );
560
        assert_eq!(Codes::from_str("Rice(0)").unwrap(), Codes::Rice(0));
561
        assert_eq!(Codes::from_str("Rice(63)").unwrap(), Codes::Rice(63));
562
    }
563

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

576
    #[test]
577
    fn test_rejects_malformed_grammar() {
578
        assert!(Codes::from_str("Zeta(3").is_err());
579
        assert!(Codes::from_str("Zeta(3)x").is_err());
580
        assert!(Codes::from_str("Zeta(3)(4)").is_err());
581
        assert!(Codes::from_str("Zeta()").is_err());
582
        assert!(Codes::from_str("Nope(1)").is_err());
583
    }
584
}
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