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::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 )
189 .map(|exec_opts| exec_opts.with_debugging())
190 .map_err(|err| serde::de::Error::custom(format!("invalid execution options: {err}")))
191 })
192}
193
194#[cfg(test)]
195mod tests {
196 use toml::toml;
197
198 use super::*;
199
200 #[test]
201 fn debugger_config_empty() {
202 let text = toml::to_string_pretty(&toml! {
203 [inputs]
204 [options]
205 })
206 .unwrap();
207
208 let file = toml::from_str::<DebuggerConfig>(&text).unwrap();
209 assert!(file.inputs.values().is_empty());
210 assert!(file.advice_inputs.stack().is_empty());
211 assert!(file.options.enable_tracing());
212 assert!(file.options.enable_debugging());
213 assert_eq!(file.options.max_cycles(), u32::MAX);
214 assert_eq!(file.options.expected_cycles(), 64);
215 }
216
217 #[test]
218 fn debugger_config_with_options() {
219 let text = toml::to_string_pretty(&toml! {
220 [inputs]
221 [options]
222 max_cycles = 1000
223 })
224 .unwrap();
225
226 let file = DebuggerConfig::parse_str(&text).unwrap();
227 assert!(file.inputs.values().is_empty());
228 assert!(file.advice_inputs.stack().is_empty());
229 assert!(file.options.enable_tracing());
230 assert!(file.options.enable_debugging());
231 assert_eq!(file.options.max_cycles(), 1000);
232 assert_eq!(file.options.expected_cycles(), 64);
233 }
234
235 #[test]
236 fn debugger_config_with_operands() {
237 let text = toml::to_string_pretty(&toml! {
238 [inputs]
239 stack = [1, 2, 3]
240
241 [options]
242 max_cycles = 1000
243 })
244 .unwrap();
245
246 let file = DebuggerConfig::parse_str(&text).unwrap();
247 assert_eq!(file.inputs.values(), &[RawFelt::new(3), RawFelt::new(2), RawFelt::new(1)]);
248 assert!(file.advice_inputs.stack().is_empty());
249 assert!(file.options.enable_tracing());
250 assert!(file.options.enable_debugging());
251 assert_eq!(file.options.max_cycles(), 1000);
252 assert_eq!(file.options.expected_cycles(), 64);
253 }
254
255 #[test]
256 fn debugger_config_with_advice() {
257 let text = toml::to_string_pretty(&toml! {
258 [inputs]
259 stack = [1, 2, 0x3]
260
261 [inputs.advice]
262 stack = [1, 2, 3, 4]
263
264 [[inputs.advice.map]]
265 digest = "0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63"
266 values = [1, 2, 3, 4]
267
268 [options]
269 max_cycles = 1000
270 })
271 .unwrap();
272 let digest = miden_processor::Digest::try_from(
273 "0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63",
274 )
275 .unwrap();
276 let file = DebuggerConfig::parse_str(&text).unwrap_or_else(|err| panic!("{err}"));
277 assert_eq!(file.inputs.values(), &[RawFelt::new(3), RawFelt::new(2), RawFelt::new(1)]);
278 assert_eq!(
279 file.advice_inputs.stack(),
280 &[RawFelt::new(4), RawFelt::new(3), RawFelt::new(2), RawFelt::new(1)]
281 );
282 assert_eq!(
283 file.advice_inputs.mapped_values(&digest),
284 Some([RawFelt::new(1), RawFelt::new(2), RawFelt::new(3), RawFelt::new(4)].as_slice())
285 );
286 assert!(file.options.enable_tracing());
287 assert!(file.options.enable_debugging());
288 assert_eq!(file.options.max_cycles(), 1000);
289 assert_eq!(file.options.expected_cycles(), 64);
290 }
291}