Skip to main content

dsi_bitstream/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 MIT
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
15use super::*;
16#[cfg(feature = "serde")]
17use alloc::string::{String, ToString};
18#[cfg(feature = "mem_dbg")]
19use 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]
37pub 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
51impl 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 {
78        match self {
79            Self::Zeta(1) | Self::ExpGolomb(0) | Self::Pi(0) => Self::Gamma,
80            Self::Rice(0) | Self::Golomb(1) => Self::Unary,
81            Self::Golomb(b) if b.is_power_of_two() => Self::Rice(b.trailing_zeros() as usize),
82            other => other,
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
247impl DynamicCodeRead for Codes {
248    #[inline]
249    fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
250        &self,
251        reader: &mut CR,
252    ) -> Result<u64, CR::Error> {
253        Ok(match self.canonicalize() {
254            Codes::Unary => reader.read_unary()?,
255            Codes::Gamma => reader.read_gamma()?,
256            Codes::Delta => reader.read_delta()?,
257            Codes::Omega => reader.read_omega()?,
258            Codes::VByteBe => reader.read_vbyte_be()?,
259            Codes::VByteLe => reader.read_vbyte_le()?,
260            Codes::Zeta(3) => reader.read_zeta3()?,
261            Codes::Zeta(k) => reader.read_zeta(k)?,
262            Codes::Pi(2) => reader.read_pi2()?,
263            Codes::Pi(k) => reader.read_pi(k)?,
264            Codes::Golomb(b) => reader.read_golomb(b)?,
265            Codes::ExpGolomb(k) => reader.read_exp_golomb(k)?,
266            Codes::Rice(log2_b) => reader.read_rice(log2_b)?,
267        })
268    }
269}
270
271impl DynamicCodeWrite for Codes {
272    #[inline]
273    fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
274        &self,
275        writer: &mut CW,
276        n: u64,
277    ) -> Result<usize, CW::Error> {
278        Ok(match self.canonicalize() {
279            Codes::Unary => writer.write_unary(n)?,
280            Codes::Gamma => writer.write_gamma(n)?,
281            Codes::Delta => writer.write_delta(n)?,
282            Codes::Omega => writer.write_omega(n)?,
283            Codes::VByteBe => writer.write_vbyte_be(n)?,
284            Codes::VByteLe => writer.write_vbyte_le(n)?,
285            Codes::Zeta(3) => writer.write_zeta3(n)?,
286            Codes::Zeta(k) => writer.write_zeta(n, k)?,
287            Codes::Pi(2) => writer.write_pi2(n)?,
288            Codes::Pi(k) => writer.write_pi(n, k)?,
289            Codes::Golomb(b) => writer.write_golomb(n, b)?,
290            Codes::ExpGolomb(k) => writer.write_exp_golomb(n, k)?,
291            Codes::Rice(log2_b) => writer.write_rice(n, log2_b)?,
292        })
293    }
294}
295
296impl<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> {
299        <Self as DynamicCodeRead>::read(self, reader)
300    }
301}
302
303impl<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> {
306        <Self as DynamicCodeWrite>::write(self, writer, n)
307    }
308}
309
310impl CodeLen for Codes {
311    #[inline]
312    fn len(&self, n: u64) -> usize {
313        match self.canonicalize() {
314            // The checked conversion avoids truncation on 32-bit platforms
315            Codes::Unary => n
316                .checked_add(1)
317                .and_then(|len| usize::try_from(len).ok())
318                .expect("unary code length does not fit in a usize"),
319            Codes::Gamma => len_gamma(n),
320            Codes::Delta => len_delta(n),
321            Codes::Omega => len_omega(n),
322            Codes::VByteLe | Codes::VByteBe => bit_len_vbyte(n),
323            Codes::Zeta(k) => len_zeta(n, k),
324            Codes::Pi(k) => len_pi(n, k),
325            Codes::Golomb(b) => len_golomb(n, b),
326            Codes::ExpGolomb(k) => len_exp_golomb(n, k),
327            Codes::Rice(log2_b) => len_rice(n, log2_b),
328        }
329    }
330}
331
332#[cfg(feature = "serde")]
333impl serde::Serialize for Codes {
334    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
335        serializer.serialize_str(&self.to_string())
336    }
337}
338
339#[cfg(feature = "serde")]
340impl<'de> serde::Deserialize<'de> for Codes {
341    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
342        let s = String::deserialize(deserializer)?;
343        s.parse().map_err(serde::de::Error::custom)
344    }
345}
346
347impl 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
365fn array_format_error(s: &str) -> [u8; 32] {
366    let mut error_buffer = [0u8; 32];
367    let len = s.len().min(32);
368    error_buffer[..len].copy_from_slice(&s.as_bytes()[..len]);
369    error_buffer
370}
371
372impl core::str::FromStr for Codes {
373    type Err = CodeError;
374
375    fn from_str(s: &str) -> Result<Self, Self::Err> {
376        match s {
377            "Unary" => Ok(Codes::Unary),
378            "Gamma" => Ok(Codes::Gamma),
379            "Delta" => Ok(Codes::Delta),
380            "Omega" => Ok(Codes::Omega),
381            "VByteBe" => Ok(Codes::VByteBe),
382            "VByteLe" => Ok(Codes::VByteLe),
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
389                    .strip_suffix(')')
390                    .ok_or_else(|| CodeError::UnknownCode(array_format_error(s)))?;
391                let (name, param) = inner
392                    .split_once('(')
393                    .ok_or_else(|| CodeError::UnknownCode(array_format_error(s)))?;
394                if param.contains('(') || param.contains(')') {
395                    return Err(CodeError::UnknownCode(array_format_error(s)));
396                }
397                let invalid = || CodeError::InvalidParameter(array_format_error(s));
398                match name {
399                    // zeta is defined for k in [1..64).
400                    "Zeta" => {
401                        let k: usize = param.parse()?;
402                        if k == 0 || k >= 64 {
403                            return Err(invalid());
404                        }
405                        Ok(Codes::Zeta(k))
406                    }
407                    // pi, exponential Golomb and Rice shift by their parameter,
408                    // so it must stay below 64.
409                    "Pi" => {
410                        let k: usize = param.parse()?;
411                        if k >= 64 {
412                            return Err(invalid());
413                        }
414                        Ok(Codes::Pi(k))
415                    }
416                    "ExpGolomb" => {
417                        let k: usize = param.parse()?;
418                        if k >= 64 {
419                            return Err(invalid());
420                        }
421                        Ok(Codes::ExpGolomb(k))
422                    }
423                    "Rice" => {
424                        let k: usize = param.parse()?;
425                        if k >= 64 {
426                            return Err(invalid());
427                        }
428                        Ok(Codes::Rice(k))
429                    }
430                    // Golomb divides by its modulus, which must be positive.
431                    "Golomb" => {
432                        let b: u64 = param.parse()?;
433                        if b == 0 {
434                            return Err(invalid());
435                        }
436                        Ok(Codes::Golomb(b))
437                    }
438                    _ => Err(CodeError::UnknownCode(array_format_error(name))),
439                }
440            }
441        }
442    }
443}
444
445/// Error type for parsing a code from a string.
446#[derive(Debug, Clone)]
447pub 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}
456impl core::error::Error for CodeError {}
457impl core::fmt::Display for CodeError {
458    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
459        match self {
460            CodeError::ParseError(e) => write!(f, "parse error: {}", e),
461            CodeError::UnknownCode(s) => {
462                write!(f, "unknown code: ")?;
463                for c in s {
464                    if *c == 0 {
465                        break;
466                    }
467                    write!(f, "{}", *c as char)?;
468                }
469                Ok(())
470            }
471            CodeError::InvalidParameter(s) => {
472                write!(f, "invalid parameter for code: ")?;
473                for c in s {
474                    if *c == 0 {
475                        break;
476                    }
477                    write!(f, "{}", *c as char)?;
478                }
479                Ok(())
480            }
481        }
482    }
483}
484
485impl From<core::num::ParseIntError> for CodeError {
486    fn from(e: core::num::ParseIntError) -> Self {
487        CodeError::ParseError(e)
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)]
503pub struct MinimalBinary(
504    /// The upper bound of the minimal binary code.
505    pub u64,
506);
507
508impl DynamicCodeRead for MinimalBinary {
509    fn read<E: Endianness, R: CodesRead<E> + ?Sized>(
510        &self,
511        reader: &mut R,
512    ) -> Result<u64, R::Error> {
513        reader.read_minimal_binary(self.0)
514    }
515}
516
517impl DynamicCodeWrite for MinimalBinary {
518    fn write<E: Endianness, W: CodesWrite<E> + ?Sized>(
519        &self,
520        writer: &mut W,
521        n: u64,
522    ) -> Result<usize, W::Error> {
523        writer.write_minimal_binary(n, self.0)
524    }
525}
526
527impl<E: Endianness, CR: CodesRead<E> + ?Sized> StaticCodeRead<E, CR> for MinimalBinary {
528    fn read(&self, reader: &mut CR) -> Result<u64, CR::Error> {
529        <Self as DynamicCodeRead>::read(self, reader)
530    }
531}
532
533impl<E: Endianness, CW: CodesWrite<E> + ?Sized> StaticCodeWrite<E, CW> for MinimalBinary {
534    fn write(&self, writer: &mut CW, n: u64) -> Result<usize, CW::Error> {
535        <Self as DynamicCodeWrite>::write(self, writer, n)
536    }
537}
538
539impl CodeLen for MinimalBinary {
540    fn len(&self, n: u64) -> usize {
541        len_minimal_binary(n, self.0)
542    }
543}
544
545#[cfg(test)]
546mod 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}