1use super::*;
18use leo_ast::interpreter_value::{GlobalId, Value};
19use leo_errors::{CompilerError, Handler, InterpreterHalt, LeoError, Result};
20use leo_passes::{CompilerState, Pass, SymbolTableCreation, TypeChecking, TypeCheckingInput};
21use snarkvm::prelude::Network;
22
23pub struct Interpreter {
27 pub cursor: Cursor,
28 actions: Vec<InterpreterAction>,
29 handler: Handler,
30 pub node_builder: NodeBuilder,
31 breakpoints: Vec<Breakpoint>,
32 pub watchpoints: Vec<Watchpoint>,
33 saved_cursors: Vec<Cursor>,
34 filename_to_program: HashMap<PathBuf, String>,
35 parsed_inputs: u32,
36}
37
38#[derive(Clone, Debug)]
39pub struct Breakpoint {
40 pub program: String,
41 pub line: usize,
42}
43
44#[derive(Clone, Debug)]
45pub struct Watchpoint {
46 pub code: String,
47 pub last_result: Option<String>,
48}
49
50#[derive(Clone, Debug)]
51pub enum InterpreterAction {
52 LeoInterpretInto(String),
53 LeoInterpretOver(String),
54 Watch(String),
55 RunFuture(usize),
56 Breakpoint(Breakpoint),
57 PrintRegister(u64),
58 Into,
59 Over,
60 Step,
61 Run,
62}
63
64impl Interpreter {
65 pub fn new<'a, P: 'a + AsRef<Path>, Q: 'a + AsRef<Path>>(
66 leo_source_files: impl IntoIterator<Item = &'a P>,
67 aleo_source_files: impl IntoIterator<Item = &'a Q>,
68 signer: SvmAddress,
69 block_height: u32,
70 test_flow: bool, ) -> Result<Self> {
72 Self::new_impl(
73 &mut leo_source_files.into_iter().map(|p| p.as_ref()),
74 &mut aleo_source_files.into_iter().map(|p| p.as_ref()),
75 signer,
76 block_height,
77 test_flow,
78 )
79 }
80
81 fn get_ast(path: &Path, state: &mut CompilerState) -> Result<Ast> {
82 let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?;
83 let filename = FileName::Real(path.to_path_buf());
84 let source_file = with_session_globals(|s| s.source_map.new_source(&text, filename));
85
86 state.ast = leo_parser::parse_ast::<TestnetV0>(
88 state.handler.clone(),
89 &state.node_builder,
90 &text,
91 source_file.absolute_start,
92 )?;
93
94 SymbolTableCreation::do_pass((), state)?;
97 TypeChecking::do_pass(
98 TypeCheckingInput {
99 max_array_elements: TestnetV0::MAX_ARRAY_ELEMENTS,
100 max_mappings: TestnetV0::MAX_MAPPINGS,
101 max_functions: TestnetV0::MAX_FUNCTIONS,
102 },
103 state,
104 )?;
105
106 Ok(state.ast.clone())
107 }
108
109 fn new_impl(
110 leo_source_files: &mut dyn Iterator<Item = &Path>,
111 aleo_source_files: &mut dyn Iterator<Item = &Path>,
112 signer: SvmAddress,
113 block_height: u32,
114 test_flow: bool,
115 ) -> Result<Self> {
116 let mut cursor: Cursor = Cursor::new(
117 true, signer,
119 block_height,
120 );
121 let mut filename_to_program = HashMap::new();
122
123 let mut state = CompilerState { is_test: test_flow, ..Default::default() };
124
125 for path in leo_source_files {
126 let ast = Self::get_ast(path, &mut state)?;
127 for (&program, scope) in ast.ast.program_scopes.iter() {
128 filename_to_program.insert(path.to_path_buf(), program.to_string());
129 for (name, function) in scope.functions.iter() {
130 cursor.functions.insert(GlobalId { program, name: *name }, FunctionVariant::Leo(function.clone()));
131 }
132
133 for (name, composite) in scope.structs.iter() {
134 cursor.structs.insert(
135 GlobalId { program, name: *name },
136 composite.members.iter().map(|member| member.identifier.name).collect(),
137 );
138 }
139
140 for (name, _mapping) in scope.mappings.iter() {
141 cursor.mappings.insert(GlobalId { program, name: *name }, HashMap::new());
142 }
143
144 for (name, const_declaration) in scope.consts.iter() {
145 cursor.frames.push(Frame {
146 step: 0,
147 element: Element::Expression(const_declaration.value.clone()),
148 user_initiated: false,
149 });
150 cursor.over()?;
151 let value = cursor.values.pop().unwrap();
152 cursor.globals.insert(GlobalId { program, name: *name }, value);
153 }
154 }
155 }
156
157 for path in aleo_source_files {
158 let aleo_program = Self::get_aleo_program(path)?;
159 let program = snarkvm_identifier_to_symbol(aleo_program.id().name());
160 filename_to_program.insert(path.to_path_buf(), program.to_string());
161
162 for (name, struct_type) in aleo_program.structs().iter() {
163 cursor.structs.insert(
164 GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
165 struct_type.members().keys().map(snarkvm_identifier_to_symbol).collect(),
166 );
167 }
168
169 for (name, record_type) in aleo_program.records().iter() {
170 cursor.structs.insert(
171 GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
172 record_type.entries().keys().map(snarkvm_identifier_to_symbol).collect(),
173 );
174 }
175
176 for (name, _mapping) in aleo_program.mappings().iter() {
177 cursor.mappings.insert(GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, HashMap::new());
178 }
179
180 for (name, function) in aleo_program.functions().iter() {
181 cursor.functions.insert(
182 GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
183 FunctionVariant::AleoFunction(function.clone()),
184 );
185 }
186
187 for (name, closure) in aleo_program.closures().iter() {
188 cursor.functions.insert(
189 GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
190 FunctionVariant::AleoClosure(closure.clone()),
191 );
192 }
193 }
194
195 cursor.type_table = state.type_table;
198
199 Ok(Interpreter {
200 cursor,
201 handler: state.handler.clone(),
202 node_builder: state.node_builder.clone(),
203 actions: Vec::new(),
204 breakpoints: Vec::new(),
205 watchpoints: Vec::new(),
206 saved_cursors: Vec::new(),
207 filename_to_program,
208 parsed_inputs: 0,
209 })
210 }
211
212 pub fn save_cursor(&mut self) {
213 self.saved_cursors.push(self.cursor.clone());
214 }
215
216 pub fn restore_cursor(&mut self) -> bool {
218 if let Some(old_cursor) = self.saved_cursors.pop() {
219 self.cursor = old_cursor;
220 true
221 } else {
222 false
223 }
224 }
225
226 fn get_aleo_program(path: &Path) -> Result<Program<TestnetV0>> {
227 let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?;
228 let program = text.parse()?;
229 Ok(program)
230 }
231
232 pub fn update_watchpoints(&mut self) -> Result<bool> {
234 let mut changed = false;
235 let safe_cursor = self.cursor.clone();
236
237 for i in 0..self.watchpoints.len() {
238 let code = self.watchpoints[i].code.clone();
239 let new_value = match self.action(InterpreterAction::LeoInterpretOver(code)) {
240 Ok(None) => None,
241 Ok(Some(ret)) => Some(ret.to_string()),
242 Err(LeoError::InterpreterHalt(halt)) => {
243 self.cursor = safe_cursor.clone();
244 Some(halt.to_string())
245 }
246 Err(e) => return Err(e),
247 };
248 if self.watchpoints[i].last_result != new_value {
249 changed = true;
250 self.watchpoints[i].last_result = new_value;
251 }
252 }
253 Ok(changed)
254 }
255
256 pub fn action(&mut self, act: InterpreterAction) -> Result<Option<Value>> {
257 use InterpreterAction::*;
258
259 let ret = match &act {
260 RunFuture(n) => {
261 let future = self.cursor.futures.remove(*n);
262 for async_exec in future.0.into_iter().rev() {
263 self.cursor.values.extend(async_exec.arguments);
264 self.cursor.frames.push(Frame {
265 step: 0,
266 element: Element::DelayedCall(async_exec.function),
267 user_initiated: true,
268 });
269 }
270 self.cursor.step()?
271 }
272 LeoInterpretInto(s) | LeoInterpretOver(s) => {
273 let filename = FileName::Custom(format!("user_input{:04}", self.parsed_inputs));
274 self.parsed_inputs += 1;
275 let source_file = with_session_globals(|globals| globals.source_map.new_source(s, filename));
276 let s = s.trim();
277 if s.ends_with(';') {
278 let statement = leo_parser::parse_statement::<TestnetV0>(
279 self.handler.clone(),
280 &self.node_builder,
281 s,
282 source_file.absolute_start,
283 )
284 .map_err(|_e| {
285 LeoError::InterpreterHalt(InterpreterHalt::new("failed to parse statement".into()))
286 })?;
287 self.cursor.frames.push(Frame {
289 step: 0,
290 element: Element::Statement(statement),
291 user_initiated: true,
292 });
293 } else {
294 let expression = leo_parser::parse_expression::<TestnetV0>(
295 self.handler.clone(),
296 &self.node_builder,
297 s,
298 source_file.absolute_start,
299 )
300 .map_err(|e| {
301 LeoError::InterpreterHalt(InterpreterHalt::new(format!("Failed to parse expression: {e}")))
302 })?;
303 self.cursor.frames.push(Frame {
305 step: 0,
306 element: Element::Expression(expression),
307 user_initiated: true,
308 });
309 };
310
311 if matches!(act, LeoInterpretOver(..)) {
312 self.cursor.over()?
313 } else {
314 StepResult { finished: false, value: None }
315 }
316 }
317
318 Step => self.cursor.whole_step()?,
319
320 Into => self.cursor.step()?,
321
322 Over => self.cursor.over()?,
323
324 Breakpoint(breakpoint) => {
325 self.breakpoints.push(breakpoint.clone());
326 StepResult { finished: false, value: None }
327 }
328
329 Watch(code) => {
330 self.watchpoints.push(Watchpoint { code: code.clone(), last_result: None });
331 StepResult { finished: false, value: None }
332 }
333
334 PrintRegister(register_index) => {
335 let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.cursor.frames.last()
336 else {
337 halt_no_span!("cannot print register - not currently interpreting Aleo VM code");
338 };
339
340 if let Some(value) = registers.get(register_index) {
341 StepResult { finished: false, value: Some(value.clone()) }
342 } else {
343 halt_no_span!("no such register {register_index}");
344 }
345 }
346
347 Run => {
348 while !self.cursor.frames.is_empty() {
349 if let Some((program, line)) = self.current_program_and_line() {
350 if self.breakpoints.iter().any(|bp| bp.program == program && bp.line == line) {
351 return Ok(None);
352 }
353 }
354 self.cursor.step()?;
355 if self.update_watchpoints()? {
356 return Ok(None);
357 }
358 }
359 StepResult { finished: false, value: None }
360 }
361 };
362
363 self.actions.push(act);
364
365 Ok(ret.value)
366 }
367
368 pub fn view_current(&self) -> Option<impl Display> {
369 if let Some(span) = self.current_span() {
370 if span != Default::default() {
371 return with_session_globals(|s| s.source_map.contents_of_span(span));
372 }
373 }
374
375 Some(match &self.cursor.frames.last()?.element {
376 Element::Statement(statement) => format!("{statement}"),
377 Element::Expression(expression) => format!("{expression}"),
378 Element::Block { block, .. } => format!("{block}"),
379 Element::DelayedCall(gid) => format!("Delayed call to {gid}"),
380 Element::AleoExecution { context, instruction_index, .. } => match &**context {
381 AleoContext::Closure(closure) => closure.instructions().get(*instruction_index).map(|i| format!("{i}")),
382 AleoContext::Function(function) => {
383 function.instructions().get(*instruction_index).map(|i| format!("{i}"))
384 }
385 AleoContext::Finalize(finalize) => finalize.commands().get(*instruction_index).map(|i| format!("{i}")),
386 }
387 .unwrap_or_else(|| "...".to_string()),
388 })
389 }
390
391 pub fn view_current_in_context(&self) -> Option<(impl Display, usize, usize)> {
392 if let Some(Frame { element: Element::AleoExecution { context, instruction_index, .. }, .. }) =
393 self.cursor.frames.last()
394 {
395 fn write_all<I: Display>(
398 items: impl Iterator<Item = I>,
399 instruction_index: usize,
400 result: &mut String,
401 start: &mut usize,
402 stop: &mut usize,
403 ) {
404 for (i, item) in items.enumerate() {
405 if i == instruction_index {
406 *start = result.len();
407 }
408 writeln!(result, " {item}").expect("write shouldn't fail");
409 if i == instruction_index {
410 *stop = result.len();
411 }
412 }
413 }
414
415 let mut result = String::new();
416 let mut start: usize = 0usize;
417 let mut stop: usize = 0usize;
418
419 match &**context {
420 AleoContext::Closure(closure) => {
421 writeln!(&mut result, "closure {}", closure.name()).expect("write shouldn't fail");
422 write_all(closure.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize);
423 write_all(closure.instructions().iter(), *instruction_index, &mut result, &mut start, &mut stop);
424 write_all(closure.outputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize);
425 }
426 AleoContext::Function(function) => {
427 writeln!(&mut result, "function {}", function.name()).expect("write shouldn't fail");
428 write_all(function.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize);
429 write_all(function.instructions().iter(), *instruction_index, &mut result, &mut start, &mut stop);
430 write_all(function.outputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize);
431 }
432 AleoContext::Finalize(finalize) => {
433 writeln!(&mut result, "finalize {}", finalize.name()).expect("write shouldn't fail");
434 write_all(finalize.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize);
435 write_all(finalize.commands().iter(), *instruction_index, &mut result, &mut start, &mut stop);
436 }
437 }
438
439 Some((result, start, stop))
440 } else {
441 let span = self.current_span()?;
443 if span == Default::default() {
444 return None;
445 }
446 with_session_globals(|s| {
447 let source_file = s.source_map.find_source_file(span.lo)?;
448 let first_span = Span::new(source_file.absolute_start, span.lo);
449 let last_span = Span::new(span.hi, source_file.absolute_end);
450 let mut result = String::new();
451 result.push_str(&s.source_map.contents_of_span(first_span)?);
452 let start = result.len();
453 result.push_str(&s.source_map.contents_of_span(span)?);
454 let stop = result.len();
455 result.push_str(&s.source_map.contents_of_span(last_span)?);
456 Some((result, start, stop))
457 })
458 }
459 }
460
461 fn current_program_and_line(&self) -> Option<(String, usize)> {
462 if let Some(span) = self.current_span() {
463 if let Some(source_file) = with_session_globals(|s| s.source_map.find_source_file(span.lo)) {
464 let (line, _) = source_file.line_col(span.lo);
465 if let FileName::Real(name) = &source_file.name {
466 if let Some(program) = self.filename_to_program.get(name) {
467 return Some((program.clone(), line as usize + 1));
468 }
469 }
470 }
471 }
472 None
473 }
474
475 fn current_span(&self) -> Option<Span> {
476 self.cursor.frames.last().map(|f| f.element.span())
477 }
478}