Skip to main content

miden_debug_engine/debug/
memory.rs

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