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 #[arg(required(true), value_name = "ADDR", value_parser(parse_address))]
97 pub addr: u32,
98 #[arg(
100 short = 't',
101 long = "type",
102 value_name = "TYPE",
103 value_parser(TypeParser)
104 )]
105 pub ty: Option<Type>,
106 #[arg(short = 'c', long = "count", value_name = "N", default_value_t = 1)]
108 pub count: u8,
109 #[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 #[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
147fn 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}