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