Skip to main content

miden_debug_engine/debug/
memory.rs

1use alloc::string::String;
2use core::{fmt, str::FromStr};
3#[cfg(feature = "std")]
4use std::{
5    boxed::Box,
6    ffi::{OsStr, OsString},
7    string::ToString,
8};
9
10#[cfg(feature = "std")]
11use clap::{Parser, ValueEnum};
12use miden_assembly_syntax::ast::types::Type;
13#[cfg(feature = "std")]
14use miden_assembly_syntax::ast::types::{ArrayType, PointerType};
15
16use super::NativePtr;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct ReadMemoryExpr {
20    pub addr: NativePtr,
21    pub ty: Type,
22    pub count: u8,
23    pub mode: MemoryMode,
24    pub format: FormatType,
25}
26
27#[cfg(feature = "std")]
28impl FromStr for ReadMemoryExpr {
29    type Err = String;
30
31    fn from_str(s: &str) -> Result<Self, Self::Err> {
32        let argv = s.split_whitespace();
33        let args = Read::parse(argv)?;
34
35        let ty = args.ty.unwrap_or_else(|| Type::from(ArrayType::new(Type::Felt, 4)));
36        let addr = match args.mode {
37            MemoryMode::Word => NativePtr::new(args.addr, 0),
38            MemoryMode::Byte => NativePtr::from_ptr(args.addr),
39        };
40        Ok(Self {
41            addr,
42            ty,
43            count: args.count,
44            mode: args.mode,
45            format: args.format,
46        })
47    }
48}
49
50impl fmt::Display for ReadMemoryExpr {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        write!(f, "{}", self.raw_addr())?;
53        write!(f, " -t {}", self.ty_name())?;
54        if self.count != 1 {
55            write!(f, " -c {}", self.count)?;
56        }
57        if self.mode != MemoryMode::Word {
58            write!(f, " -m {}", self.mode)?;
59        }
60        if self.format != FormatType::Decimal {
61            write!(f, " -f {}", self.format)?;
62        }
63        Ok(())
64    }
65}
66
67impl ReadMemoryExpr {
68    fn raw_addr(&self) -> u32 {
69        match self.mode {
70            MemoryMode::Word => self.addr.addr,
71            MemoryMode::Byte => self.addr.addr.saturating_mul(4) + u32::from(self.addr.offset),
72        }
73    }
74
75    fn ty_name(&self) -> &'static str {
76        match &self.ty {
77            Type::I1 => "i1",
78            Type::I8 => "i8",
79            Type::I16 => "i16",
80            Type::I32 => "i32",
81            Type::I64 => "i64",
82            Type::I128 => "i128",
83            Type::U8 => "u8",
84            Type::U16 => "u16",
85            Type::U32 => "u32",
86            Type::U64 => "u64",
87            Type::U128 => "u128",
88            Type::Felt => "felt",
89            Type::Array(array_ty)
90                if array_ty.element_type() == &Type::Felt && array_ty.len() == 4 =>
91            {
92                "word"
93            }
94            Type::Ptr(_) => "ptr",
95            ty => panic!("unsupported memory read type serialization: {ty}"),
96        }
97    }
98}
99
100#[cfg(feature = "std")]
101#[derive(Default, Debug, Parser)]
102#[command(name = "read")]
103pub struct Read {
104    /// The memory address to start reading from
105    #[arg(required(true), value_name = "ADDR", value_parser(parse_address))]
106    pub addr: u32,
107    /// The type of value to read from ADDR, defaults to 'word'
108    #[arg(
109        short = 't',
110        long = "type",
111        value_name = "TYPE",
112        value_parser(TypeParser)
113    )]
114    pub ty: Option<Type>,
115    /// The number of values to read
116    #[arg(short = 'c', long = "count", value_name = "N", default_value_t = 1)]
117    pub count: u8,
118    /// The addressing mode to use
119    #[arg(
120        short = 'm',
121        long = "mode",
122        value_name = "MODE",
123        default_value_t = MemoryMode::Word,
124        value_parser(MemoryModeParser)
125    )]
126    pub mode: MemoryMode,
127    /// The format to use when printing integral values
128    #[arg(
129        short = 'f',
130        long = "format",
131        value_name = "FORMAT",
132        default_value_t = FormatType::Decimal,
133        value_parser(FormatTypeParser)
134    )]
135    pub format: FormatType,
136}
137
138#[cfg(feature = "std")]
139impl Read {
140    pub fn parse<I, S>(argv: I) -> Result<Self, String>
141    where
142        I: IntoIterator<Item = S>,
143        S: Into<OsString> + Clone,
144    {
145        let command = <Self as clap::CommandFactory>::command()
146            .disable_help_flag(true)
147            .disable_version_flag(true)
148            .disable_colored_help(true)
149            .no_binary_name(true);
150
151        let mut matches =
152            command.try_get_matches_from(argv).map_err(|err| render_clap_error(&err))?;
153        <Self as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
154            .map_err(|err| render_clap_error(&err))
155    }
156}
157
158/// Render a clap error as a plain message, without clap's own `error: ` prefix
159/// (callers add their own) or trailing usage/help boilerplate.
160#[cfg(feature = "std")]
161fn render_clap_error(err: &clap::Error) -> String {
162    let rendered = err.to_string();
163    let message = rendered.lines().next().unwrap_or_default();
164    message.strip_prefix("error: ").unwrap_or(message).to_string()
165}
166
167#[cfg(feature = "std")]
168#[doc(hidden)]
169#[derive(Clone)]
170struct TypeParser;
171
172#[cfg(feature = "std")]
173impl clap::builder::TypedValueParser for TypeParser {
174    type Value = Type;
175
176    fn parse_ref(
177        &self,
178        _cmd: &clap::Command,
179        _arg: Option<&clap::Arg>,
180        value: &OsStr,
181    ) -> Result<Self::Value, clap::error::Error> {
182        use clap::error::{Error, ErrorKind};
183
184        let value = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
185
186        Ok(match value {
187            "i1" => Type::I1,
188            "i8" => Type::I8,
189            "i16" => Type::I16,
190            "i32" => Type::I32,
191            "i64" => Type::I64,
192            "i128" => Type::I128,
193            "u8" => Type::U8,
194            "u16" => Type::U16,
195            "u32" => Type::U32,
196            "u64" => Type::U64,
197            "u128" => Type::U128,
198            "felt" => Type::Felt,
199            "word" => Type::from(ArrayType::new(Type::Felt, 4)),
200            "ptr" | "pointer" => Type::from(PointerType::new(Type::U32)),
201            _ => {
202                return Err(Error::raw(
203                    ErrorKind::InvalidValue,
204                    format!("invalid/unsupported type '{value}'"),
205                ));
206            }
207        })
208    }
209}
210
211fn parse_address(s: &str) -> Result<u32, String> {
212    if let Some(s) = s.strip_prefix("0x") {
213        u32::from_str_radix(s, 16).map_err(|err| format!("invalid memory address: {err}"))
214    } else if s.is_empty() {
215        Err(format!("expected memory address at '{s}'"))
216    } else {
217        s.parse::<u32>().map_err(|err| format!("invalid memory address: {err}"))
218    }
219}
220
221#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
222#[cfg_attr(feature = "std", derive(ValueEnum))]
223pub enum MemoryMode {
224    #[default]
225    Word,
226    Byte,
227}
228
229impl fmt::Display for MemoryMode {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        match self {
232            Self::Word => f.write_str("word"),
233            Self::Byte => f.write_str("byte"),
234        }
235    }
236}
237
238impl FromStr for MemoryMode {
239    type Err = String;
240
241    fn from_str(s: &str) -> Result<Self, Self::Err> {
242        match s {
243            "w" | "word" | "words" | "miden" => Ok(Self::Word),
244            "b" | "byte" | "bytes" | "rust" => Ok(Self::Byte),
245            _ => Err(format!("invalid memory mode '{s}'")),
246        }
247    }
248}
249
250#[cfg(feature = "std")]
251#[doc(hidden)]
252#[derive(Clone)]
253struct MemoryModeParser;
254
255#[cfg(feature = "std")]
256impl clap::builder::TypedValueParser for MemoryModeParser {
257    type Value = MemoryMode;
258
259    fn possible_values(
260        &self,
261    ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
262        use clap::builder::PossibleValue;
263        Some(Box::new(
264            [
265                PossibleValue::new("words").aliases(["w", "word", "miden"]),
266                PossibleValue::new("bytes").aliases(["b", "byte", "rust"]),
267            ]
268            .into_iter(),
269        ))
270    }
271
272    fn parse_ref(
273        &self,
274        _cmd: &clap::Command,
275        _arg: Option<&clap::Arg>,
276        value: &OsStr,
277    ) -> Result<Self::Value, clap::error::Error> {
278        use clap::error::{Error, ErrorKind};
279
280        let value = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
281        value.parse().map_err(|err| Error::raw(ErrorKind::InvalidValue, err))
282    }
283}
284
285#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
286pub enum FormatType {
287    #[default]
288    Decimal,
289    Hex,
290    Binary,
291}
292
293impl fmt::Display for FormatType {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        match self {
296            Self::Decimal => f.write_str("decimal"),
297            Self::Hex => f.write_str("hex"),
298            Self::Binary => f.write_str("binary"),
299        }
300    }
301}
302
303impl FromStr for FormatType {
304    type Err = String;
305
306    fn from_str(s: &str) -> Result<Self, Self::Err> {
307        match s {
308            "d" | "decimal" => Ok(Self::Decimal),
309            "x" | "hex" | "hexadecimal" => Ok(Self::Hex),
310            "b" | "bin" | "binary" | "bits" => Ok(Self::Binary),
311            _ => Err(format!("invalid format type '{s}'")),
312        }
313    }
314}
315
316#[cfg(feature = "std")]
317#[doc(hidden)]
318#[derive(Clone)]
319struct FormatTypeParser;
320
321#[cfg(feature = "std")]
322impl clap::builder::TypedValueParser for FormatTypeParser {
323    type Value = FormatType;
324
325    fn possible_values(
326        &self,
327    ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
328        use clap::builder::PossibleValue;
329        Some(Box::new(
330            [
331                PossibleValue::new("decimal").alias("d"),
332                PossibleValue::new("hex").aliases(["x", "hexadecimal"]),
333                PossibleValue::new("binary").aliases(["b", "bin", "bits"]),
334            ]
335            .into_iter(),
336        ))
337    }
338
339    fn parse_ref(
340        &self,
341        _cmd: &clap::Command,
342        _arg: Option<&clap::Arg>,
343        value: &OsStr,
344    ) -> Result<Self::Value, clap::error::Error> {
345        use clap::error::{Error, ErrorKind};
346
347        let value = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
348        value.parse().map_err(|err| Error::raw(ErrorKind::InvalidValue, err))
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use alloc::string::{String, ToString};
355
356    use super::FormatType;
357    use crate::test_utils::write_scalar_bytes;
358
359    #[test]
360    fn write_scalar_bytes_reads_little_endian_u64() {
361        let mut output = String::new();
362
363        write_scalar_bytes(&mut output, "u64", FormatType::Decimal, &[1, 2, 3, 4, 5, 6, 7, 8])
364            .unwrap();
365
366        assert_eq!(output, u64::from_le_bytes([1, 2, 3, 4, 5, 6, 7, 8]).to_string());
367    }
368
369    #[test]
370    fn write_scalar_bytes_reads_little_endian_u16_hex() {
371        let mut output = String::new();
372
373        write_scalar_bytes(&mut output, "u16", FormatType::Hex, &[0x34, 0x12]).unwrap();
374
375        assert_eq!(output, "1234");
376    }
377}