qubit_codec/buffered/decode_action.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Decode actions returned by buffered decoder policy hooks.
11
12use core::num::NonZeroUsize;
13
14use super::decode_step::DecodeStep;
15
16/// Action selected after a codec decode attempt fails during `transcode`.
17///
18/// # Type Parameters
19///
20/// - `Value`: Decoded output value type.
21#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
22pub enum DecodeAction<Value> {
23 /// More source units are needed before a value can be produced.
24 ///
25 /// When returned by a decode hook, `required_total` must be greater than
26 /// the hook context's available input count. Returning a value that is
27 /// already satisfied is a hook contract violation and panics in the engine.
28 NeedInput {
29 /// Total units required from the current value start.
30 required_total: usize,
31 },
32
33 /// Consume invalid input without producing output.
34 ///
35 /// When returned by a decode hook, `consumed` must not exceed the hook
36 /// context's available input count. Over-consuming is a hook contract
37 /// violation and panics in the engine.
38 Skip {
39 /// Source units to consume.
40 consumed: NonZeroUsize,
41 },
42
43 /// Produce one value and consume source units.
44 ///
45 /// When returned by a decode hook, `consumed` must not exceed the hook
46 /// context's available input count. Over-consuming is a hook contract
47 /// violation and panics in the engine.
48 Emit {
49 /// Value to write to the output buffer.
50 value: Value,
51 /// Source units to consume.
52 consumed: NonZeroUsize,
53 },
54}
55
56impl<Value> DecodeAction<Value> {
57 /// Converts this policy action into the normalized internal decode attempt.
58 ///
59 /// # Parameters
60 ///
61 /// - `input_index`: Absolute source index where the failed decode started.
62 /// - `available`: Source units visible from `input_index`.
63 ///
64 /// # Returns
65 ///
66 /// Returns the internal decode attempt consumed by buffered decode loops.
67 ///
68 /// # Panics
69 ///
70 /// Panics when a hook returns `NeedInput` with `required_total <= available`
71 /// or a consuming action whose consumed count exceeds `available`.
72 #[must_use]
73 #[inline]
74 pub(super) fn into_step(self, input_index: usize, available: usize) -> DecodeStep<Value> {
75 match self {
76 Self::NeedInput { required_total } => {
77 DecodeStep::need_input(Self::missing_input(required_total, available), available)
78 }
79 Self::Skip { consumed } => DecodeStep::skipped(Self::bound_consumed(consumed, available)),
80 Self::Emit { value, consumed } => {
81 DecodeStep::decoded(value, Self::bound_consumed(consumed, available), input_index)
82 }
83 }
84 }
85
86 /// Returns the additional source units required by a need-input action.
87 ///
88 /// # Parameters
89 ///
90 /// - `required_total`: Total source units required from the current value start.
91 /// - `available`: Source units already visible at the current value start.
92 ///
93 /// # Returns
94 ///
95 /// Returns a non-zero additional source-unit count.
96 ///
97 /// # Panics
98 ///
99 /// Panics when `required_total <= available`.
100 #[must_use]
101 #[inline(always)]
102 fn missing_input(required_total: usize, available: usize) -> NonZeroUsize {
103 assert!(
104 required_total > available,
105 "DecodeAction::NeedInput required_total must exceed available input",
106 );
107 let additional = required_total - available;
108 // SAFETY: The assertion above guarantees a positive difference.
109 unsafe { NonZeroUsize::new_unchecked(additional) }
110 }
111
112 /// Validates a policy-reported consumed source-unit count against available input.
113 ///
114 /// # Parameters
115 ///
116 /// - `consumed`: Source units requested by the concrete policy.
117 /// - `available`: Source units visible from the current decode cursor.
118 ///
119 /// # Returns
120 ///
121 /// Returns the validated non-zero consumed count.
122 ///
123 /// # Panics
124 ///
125 /// Panics when `available == 0` or when `consumed > available`.
126 #[must_use]
127 #[inline(always)]
128 fn bound_consumed(consumed: NonZeroUsize, available: usize) -> NonZeroUsize {
129 assert!(available > 0, "DecodeAction cannot consume empty input");
130 assert!(
131 consumed.get() <= available,
132 "DecodeAction consumed units must not exceed available input",
133 );
134 consumed
135 }
136}