Skip to main content

petr_dispatch/
dense.rs

1use crate::{DispatchError, DispatchResult, OpFrame};
2
3/// Copyable argument for dense dispatch entries.
4///
5/// This avoids heap-owned per-entry strings so the dense table can be a fixed
6/// 256-entry array initialized to NOP.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct DenseOpArg {
9    /// Numeric argument.
10    pub n: i64,
11    /// Static text argument.
12    pub text: &'static str,
13}
14
15impl DenseOpArg {
16    /// Empty argument.
17    pub const ZERO: Self = Self { n: 0, text: "" };
18
19    /// Numeric argument.
20    pub const fn n(n: i64) -> Self { Self { n, text: "" } }
21
22    /// Text argument.
23    pub const fn text(text: &'static str) -> Self { Self { n: 0, text } }
24}
25
26/// Function pointer used by the dense dispatch table.
27pub type DenseOpFn = fn(OpFrame, DenseOpArg) -> DispatchResult<OpFrame>;
28
29/// One fixed dispatch entry.
30#[derive(Clone, Copy)]
31pub struct DenseOpEntry {
32    /// Function to execute.
33    pub func: DenseOpFn,
34    /// Bound argument.
35    pub arg: DenseOpArg,
36    /// Human-readable operation name.
37    pub name: &'static str,
38}
39
40impl core::fmt::Debug for DenseOpEntry {
41    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42        f.debug_struct("DenseOpEntry")
43            .field("name", &self.name)
44            .field("arg", &self.arg)
45            .finish()
46    }
47}
48
49/// NOP operation for missing selectors.
50pub fn dense_nop(frame: OpFrame, _arg: DenseOpArg) -> DispatchResult<OpFrame> { Ok(frame) }
51
52/// Prefix text with a static argument.
53pub fn dense_add_prefix(mut frame: OpFrame, arg: DenseOpArg) -> DispatchResult<OpFrame> {
54    frame.text = format!("{}{}", arg.text, frame.text);
55    Ok(frame)
56}
57
58/// Suffix text with a static argument.
59pub fn dense_add_suffix(mut frame: OpFrame, arg: DenseOpArg) -> DispatchResult<OpFrame> {
60    frame.text = format!("{}{}", frame.text, arg.text);
61    Ok(frame)
62}
63
64/// Add to accumulator.
65pub fn dense_acc_add(mut frame: OpFrame, arg: DenseOpArg) -> DispatchResult<OpFrame> {
66    frame.acc += arg.n;
67    Ok(frame)
68}
69
70/// Multiply accumulator.
71pub fn dense_acc_mul(mut frame: OpFrame, arg: DenseOpArg) -> DispatchResult<OpFrame> {
72    frame.acc *= arg.n;
73    Ok(frame)
74}
75
76/// Validate and normalize a hex/octet string.
77pub fn dense_octet_encode(mut frame: OpFrame, _arg: DenseOpArg) -> DispatchResult<OpFrame> {
78    if !frame.text.chars().all(|c| c.is_ascii_hexdigit()) {
79        return Err(DispatchError::InvalidInput(frame.text));
80    }
81    let value = u32::from_str_radix(&frame.text, 16)
82        .map_err(|_| DispatchError::InvalidInput(frame.text.clone()))?;
83    frame.text = format!("{:X}", value);
84    Ok(frame)
85}
86
87const NOP_ENTRY: DenseOpEntry = DenseOpEntry {
88    func: dense_nop,
89    arg: DenseOpArg::ZERO,
90    name: "nop",
91};
92
93/// A fixed 256-slot selector table with every missing selector pre-filled as NOP.
94///
95/// This removes the `Option`/`match` from the hot dispatch path. It is still not
96/// literally branchless at the CPU level, because calling a function pointer is
97/// an indirect branch. The useful property is that selector choice is table
98/// indexing rather than an `if`/`else` or `match` chain.
99#[derive(Debug, Clone)]
100pub struct DenseOperationTable {
101    entries: [DenseOpEntry; 256],
102}
103
104impl Default for DenseOperationTable {
105    fn default() -> Self { Self::new() }
106}
107
108impl DenseOperationTable {
109    /// Create a dense table full of NOP entries.
110    pub fn new() -> Self { Self { entries: [NOP_ENTRY; 256] } }
111
112    /// Register a selector.
113    pub fn register(&mut self, selector: u8, name: &'static str, func: DenseOpFn, arg: DenseOpArg) {
114        self.entries[selector as usize] = DenseOpEntry { func, arg, name };
115    }
116
117    /// Execute the selector. Missing entries execute NOP.
118    pub fn execute(&self, selector: u8, frame: OpFrame) -> DispatchResult<OpFrame> {
119        let entry = self.entries[selector as usize];
120        (entry.func)(frame, entry.arg)
121    }
122
123    /// Return the static operation name registered for a selector.
124    pub fn name(&self, selector: u8) -> &'static str { self.entries[selector as usize].name }
125}
126
127/// Install a useful dense table.
128pub fn default_dense_table() -> DenseOperationTable {
129    let mut table = DenseOperationTable::new();
130    table.register(0x01, "add_prefix", dense_add_prefix, DenseOpArg::text("Prefix_"));
131    table.register(0x02, "add_suffix", dense_add_suffix, DenseOpArg::text("_Suffix"));
132    table.register(0x20, "octet_encode", dense_octet_encode, DenseOpArg::ZERO);
133    table.register(0x30, "acc_add", dense_acc_add, DenseOpArg::n(1));
134    table.register(0x31, "acc_mul", dense_acc_mul, DenseOpArg::n(2));
135    table
136}