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 #[arg(required(true), value_name = "ADDR", value_parser(parse_address))]
106 pub addr: u32,
107 #[arg(
109 short = 't',
110 long = "type",
111 value_name = "TYPE",
112 value_parser(TypeParser)
113 )]
114 pub ty: Option<Type>,
115 #[arg(short = 'c', long = "count", value_name = "N", default_value_t = 1)]
117 pub count: u8,
118 #[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 #[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#[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}