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 = command.try_get_matches_from(argv).map_err(|err| err.to_string())?;
141        <Self as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
142            .map_err(|err| err.to_string())
143    }
144}
145
146#[doc(hidden)]
147#[derive(Clone)]
148struct TypeParser;
149impl clap::builder::TypedValueParser for TypeParser {
150    type Value = Type;
151
152    fn parse_ref(
153        &self,
154        _cmd: &clap::Command,
155        _arg: Option<&clap::Arg>,
156        value: &OsStr,
157    ) -> Result<Self::Value, clap::error::Error> {
158        use clap::error::{Error, ErrorKind};
159
160        let value = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
161
162        Ok(match value {
163            "i1" => Type::I1,
164            "i8" => Type::I8,
165            "i16" => Type::I16,
166            "i32" => Type::I32,
167            "i64" => Type::I64,
168            "i128" => Type::I128,
169            "u8" => Type::U8,
170            "u16" => Type::U16,
171            "u32" => Type::U32,
172            "u64" => Type::U64,
173            "u128" => Type::U128,
174            "felt" => Type::Felt,
175            "word" => Type::from(ArrayType::new(Type::Felt, 4)),
176            "ptr" | "pointer" => Type::from(PointerType::new(Type::U32)),
177            _ => {
178                return Err(Error::raw(
179                    ErrorKind::InvalidValue,
180                    format!("invalid/unsupported type '{value}'"),
181                ));
182            }
183        })
184    }
185}
186
187fn parse_address(s: &str) -> Result<u32, String> {
188    if let Some(s) = s.strip_prefix("0x") {
189        u32::from_str_radix(s, 16).map_err(|err| format!("invalid memory address: {err}"))
190    } else if s.is_empty() {
191        Err(format!("expected memory address at '{s}'"))
192    } else {
193        s.parse::<u32>().map_err(|err| format!("invalid memory address: {err}"))
194    }
195}
196
197#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
198pub enum MemoryMode {
199    #[default]
200    Word,
201    Byte,
202}
203impl fmt::Display for MemoryMode {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        match self {
206            Self::Word => f.write_str("word"),
207            Self::Byte => f.write_str("byte"),
208        }
209    }
210}
211impl FromStr for MemoryMode {
212    type Err = String;
213
214    fn from_str(s: &str) -> Result<Self, Self::Err> {
215        match s {
216            "w" | "word" | "words" | "miden" => Ok(Self::Word),
217            "b" | "byte" | "bytes" | "rust" => Ok(Self::Byte),
218            _ => Err(format!("invalid memory mode '{s}'")),
219        }
220    }
221}
222
223#[doc(hidden)]
224#[derive(Clone)]
225struct MemoryModeParser;
226impl clap::builder::TypedValueParser for MemoryModeParser {
227    type Value = MemoryMode;
228
229    fn possible_values(
230        &self,
231    ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
232        use clap::builder::PossibleValue;
233        Some(Box::new(
234            [
235                PossibleValue::new("words").aliases(["w", "word", "miden"]),
236                PossibleValue::new("bytes").aliases(["b", "byte", "rust"]),
237            ]
238            .into_iter(),
239        ))
240    }
241
242    fn parse_ref(
243        &self,
244        _cmd: &clap::Command,
245        _arg: Option<&clap::Arg>,
246        value: &OsStr,
247    ) -> Result<Self::Value, clap::error::Error> {
248        use clap::error::{Error, ErrorKind};
249
250        let value = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
251        value.parse().map_err(|err| Error::raw(ErrorKind::InvalidValue, err))
252    }
253}
254
255#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
256pub enum FormatType {
257    #[default]
258    Decimal,
259    Hex,
260    Binary,
261}
262impl fmt::Display for FormatType {
263    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264        match self {
265            Self::Decimal => f.write_str("decimal"),
266            Self::Hex => f.write_str("hex"),
267            Self::Binary => f.write_str("binary"),
268        }
269    }
270}
271impl FromStr for FormatType {
272    type Err = String;
273
274    fn from_str(s: &str) -> Result<Self, Self::Err> {
275        match s {
276            "d" | "decimal" => Ok(Self::Decimal),
277            "x" | "hex" | "hexadecimal" => Ok(Self::Hex),
278            "b" | "bin" | "binary" | "bits" => Ok(Self::Binary),
279            _ => Err(format!("invalid format type '{s}'")),
280        }
281    }
282}
283
284#[doc(hidden)]
285#[derive(Clone)]
286struct FormatTypeParser;
287impl clap::builder::TypedValueParser for FormatTypeParser {
288    type Value = FormatType;
289
290    fn possible_values(
291        &self,
292    ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
293        use clap::builder::PossibleValue;
294        Some(Box::new(
295            [
296                PossibleValue::new("decimal").alias("d"),
297                PossibleValue::new("hex").aliases(["x", "hexadecimal"]),
298                PossibleValue::new("binary").aliases(["b", "bin", "bits"]),
299            ]
300            .into_iter(),
301        ))
302    }
303
304    fn parse_ref(
305        &self,
306        _cmd: &clap::Command,
307        _arg: Option<&clap::Arg>,
308        value: &OsStr,
309    ) -> Result<Self::Value, clap::error::Error> {
310        use clap::error::{Error, ErrorKind};
311
312        let value = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
313        value.parse().map_err(|err| Error::raw(ErrorKind::InvalidValue, err))
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::FormatType;
320    use crate::test_utils::write_scalar_bytes;
321
322    #[test]
323    fn write_scalar_bytes_reads_little_endian_u64() {
324        let mut output = String::new();
325
326        write_scalar_bytes(&mut output, "u64", FormatType::Decimal, &[1, 2, 3, 4, 5, 6, 7, 8])
327            .unwrap();
328
329        assert_eq!(output, u64::from_le_bytes([1, 2, 3, 4, 5, 6, 7, 8]).to_string());
330    }
331
332    #[test]
333    fn write_scalar_bytes_reads_little_endian_u16_hex() {
334        let mut output = String::new();
335
336        write_scalar_bytes(&mut output, "u16", FormatType::Hex, &[0x34, 0x12]).unwrap();
337
338        assert_eq!(output, "1234");
339    }
340}