1use std::{
2 collections::BTreeMap,
3 ffi::OsStr,
4 path::{Path, PathBuf},
5};
6
7use miden_processor::{AdviceInputs, ExecutionOptions, Felt as RawFelt, StackInputs};
8use serde::Deserialize;
9
10use crate::Felt;
11
12#[derive(Debug, Clone, Default, Deserialize)]
13#[serde(try_from = "DebuggerConfigFile")]
14pub struct DebuggerConfig {
15 pub inputs: StackInputs,
16 pub advice_inputs: AdviceInputs,
17 pub options: ExecutionOptions,
18}
19
20impl TryFrom<DebuggerConfigFile> for DebuggerConfig {
21 type Error = String;
22
23 #[inline]
24 fn try_from(mut file: DebuggerConfigFile) -> Result<Self, Self::Error> {
25 Self::from_inputs_file(file, None)
26 }
27}
28
29impl DebuggerConfig {
30 pub fn parse_file<P>(path: P) -> std::io::Result<Self>
31 where
32 P: AsRef<std::path::Path>,
33 {
34 let path = path.as_ref();
35 let content = std::fs::read_to_string(path)?;
36
37 let file = toml::from_str::<DebuggerConfigFile>(&content).map_err(std::io::Error::other)?;
38 Self::from_inputs_file(file, path.parent().map(|p| p.to_path_buf()))
39 .map_err(std::io::Error::other)
40 }
41
42 pub fn parse_str(content: &str) -> Result<Self, String> {
43 let file = toml::from_str::<DebuggerConfigFile>(content).map_err(|err| err.to_string())?;
44
45 Self::from_inputs_file(file, None)
46 }
47
48 fn from_inputs_file(
49 mut file: DebuggerConfigFile,
50 _cwd: Option<PathBuf>,
51 ) -> Result<Self, String> {
52 let inputs = StackInputs::new(file.inputs.stack.into_iter().map(|felt| felt.0).collect())
53 .map_err(|err| format!("invalid value for 'stack': {err}"))?;
54 let advice_inputs = AdviceInputs::default()
55 .with_stack(file.inputs.advice.stack.into_iter().rev().map(|felt| felt.0))
56 .with_map(file.inputs.advice.map.into_iter().map(|entry| {
57 (entry.digest.0, entry.values.into_iter().map(|felt| felt.0).collect::<Vec<_>>())
58 }));
59
60 Ok(Self {
61 inputs,
62 advice_inputs,
63 options: file.options,
64 })
65 }
66}
67
68#[derive(Debug, Clone, Default, Deserialize)]
69#[serde(default)]
70struct DebuggerConfigFile {
71 inputs: Inputs,
72 #[serde(deserialize_with = "deserialize_execution_options")]
73 options: ExecutionOptions,
74}
75
76#[derive(Debug, Clone, Default, Deserialize)]
77#[serde(default)]
78struct Inputs {
79 stack: Vec<crate::Felt>,
81 advice: Advice,
83}
84
85#[derive(Debug, Clone, Default, Deserialize)]
86#[serde(default)]
87struct Advice {
88 stack: Vec<crate::Felt>,
90 map: Vec<AdviceMapEntry>,
92}
93
94#[derive(Debug, Clone, Deserialize)]
95struct AdviceMapEntry {
96 digest: Digest,
97 #[serde(default)]
99 values: Vec<crate::Felt>,
100}
101
102impl clap::builder::ValueParserFactory for DebuggerConfig {
103 type Parser = DebuggerConfigParser;
104
105 fn value_parser() -> Self::Parser {
106 DebuggerConfigParser
107 }
108}
109
110#[doc(hidden)]
111#[derive(Clone)]
112pub struct DebuggerConfigParser;
113impl clap::builder::TypedValueParser for DebuggerConfigParser {
114 type Value = DebuggerConfig;
115
116 fn parse_ref(
117 &self,
118 _cmd: &clap::Command,
119 _arg: Option<&clap::Arg>,
120 value: &OsStr,
121 ) -> Result<Self::Value, clap::error::Error> {
122 use clap::error::{Error, ErrorKind};
123
124 let inputs_path = Path::new(value);
125 if !inputs_path.is_file() {
126 return Err(Error::raw(
127 ErrorKind::InvalidValue,
128 format!("invalid inputs file: '{}' is not a file", inputs_path.display()),
129 ));
130 }
131
132 let content = std::fs::read_to_string(inputs_path).map_err(|err| {
133 Error::raw(ErrorKind::ValueValidation, format!("failed to read inputs file: {err}"))
134 })?;
135 let inputs_file = toml::from_str::<DebuggerConfigFile>(&content).map_err(|err| {
136 Error::raw(ErrorKind::ValueValidation, format!("invalid inputs file: {err}"))
137 })?;
138
139 DebuggerConfig::from_inputs_file(inputs_file, Some(inputs_path.to_path_buf())).map_err(
140 |err| Error::raw(ErrorKind::ValueValidation, format!("invalid inputs file: {err}")),
141 )
142 }
143}
144
145#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
146struct Digest(miden_processor::Digest);
147impl<'de> Deserialize<'de> for Digest {
148 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
149 where
150 D: serde::Deserializer<'de>,
151 {
152 let digest = String::deserialize(deserializer)?;
153 miden_processor::Digest::try_from(&digest)
154 .map_err(|err| serde::de::Error::custom(format!("invalid digest: {err}")))
155 .map(Self)
156 }
157}
158
159fn deserialize_rodata_bytes<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
160where
161 D: serde::Deserializer<'de>,
162{
163 use midenc_hir::constants::ConstantData;
164
165 String::deserialize(deserializer).and_then(|hex| {
166 ConstantData::from_str_be(hex.as_str())
167 .map_err(|err| serde::de::Error::custom(format!("invalid rodata: {err}")))
168 .map(Vec::<u8>::from)
169 })
170}
171
172fn deserialize_execution_options<'de, D>(deserializer: D) -> Result<ExecutionOptions, D::Error>
173where
174 D: serde::Deserializer<'de>,
175{
176 #[derive(Default, Deserialize)]
177 #[serde(default)]
178 struct ExecOptions {
179 max_cycles: Option<u32>,
180 expected_cycles: u32,
181 }
182
183 ExecOptions::deserialize(deserializer).and_then(|opts| {
184 ExecutionOptions::new(
185 opts.max_cycles,
186 opts.expected_cycles,
187 true,
188 true,
189 )
190 .map(|exec_opts| exec_opts.with_debugging(true))
191 .map_err(|err| serde::de::Error::custom(format!("invalid execution options: {err}")))
192 })
193}
194
195#[cfg(test)]
196mod tests {
197 use toml::toml;
198
199 use super::*;
200
201 #[test]
202 fn debugger_config_empty() {
203 let text = toml::to_string_pretty(&toml! {
204 [inputs]
205 [options]
206 })
207 .unwrap();
208
209 let file = toml::from_str::<DebuggerConfig>(&text).unwrap();
210 let expected_inputs = StackInputs::new(vec![]).unwrap();
211 assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
212 assert!(file.advice_inputs.stack().is_empty());
213 assert!(file.options.enable_tracing());
214 assert!(file.options.enable_debugging());
215 assert_eq!(file.options.max_cycles(), u32::MAX);
216 assert_eq!(file.options.expected_cycles(), 64);
217 }
218
219 #[test]
220 fn debugger_config_with_options() {
221 let text = toml::to_string_pretty(&toml! {
222 [inputs]
223 [options]
224 max_cycles = 1000
225 })
226 .unwrap();
227
228 let file = DebuggerConfig::parse_str(&text).unwrap();
229 let expected_inputs = StackInputs::new(vec![]).unwrap();
230 assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
231 assert!(file.advice_inputs.stack().is_empty());
232 assert!(file.options.enable_tracing());
233 assert!(file.options.enable_debugging());
234 assert_eq!(file.options.max_cycles(), 1000);
235 assert_eq!(file.options.expected_cycles(), 64);
236 }
237
238 #[test]
239 fn debugger_config_with_operands() {
240 let text = toml::to_string_pretty(&toml! {
241 [inputs]
242 stack = [1, 2, 3]
243
244 [options]
245 max_cycles = 1000
246 })
247 .unwrap();
248
249 let file = DebuggerConfig::parse_str(&text).unwrap();
250 let expected_inputs =
251 StackInputs::new(vec![RawFelt::new(1), RawFelt::new(2), RawFelt::new(3)]).unwrap();
252 assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
253 assert!(file.advice_inputs.stack().is_empty());
254 assert!(file.options.enable_tracing());
255 assert!(file.options.enable_debugging());
256 assert_eq!(file.options.max_cycles(), 1000);
257 assert_eq!(file.options.expected_cycles(), 64);
258 }
259
260 #[test]
261 fn debugger_config_with_advice() {
262 let text = toml::to_string_pretty(&toml! {
263 [inputs]
264 stack = [1, 2, 0x3]
265
266 [inputs.advice]
267 stack = [1, 2, 3, 4]
268
269 [[inputs.advice.map]]
270 digest = "0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63"
271 values = [1, 2, 3, 4]
272
273 [options]
274 max_cycles = 1000
275 })
276 .unwrap();
277 let digest = miden_processor::Digest::try_from(
278 "0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63",
279 )
280 .unwrap();
281 let file = DebuggerConfig::parse_str(&text).unwrap_or_else(|err| panic!("{err}"));
282 let expected_inputs =
283 StackInputs::new(vec![RawFelt::new(1), RawFelt::new(2), RawFelt::new(3)]).unwrap();
284 assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
285 assert_eq!(
286 file.advice_inputs.stack(),
287 &[RawFelt::new(4), RawFelt::new(3), RawFelt::new(2), RawFelt::new(1)]
288 );
289 assert_eq!(
290 file.advice_inputs.mapped_values(&digest),
291 Some([RawFelt::new(1), RawFelt::new(2), RawFelt::new(3), RawFelt::new(4)].as_slice())
292 );
293 assert!(file.options.enable_tracing());
294 assert!(file.options.enable_debugging());
295 assert_eq!(file.options.max_cycles(), 1000);
296 assert_eq!(file.options.expected_cycles(), 64);
297 }
298}