1use super::*;
18use leo_ast::{NetworkName, interpreter_value::AsyncExecution};
19use leo_errors::{CompilerError, Handler, InterpreterHalt, LeoError, Result};
20
21pub struct Interpreter {
25 pub cursor: Cursor,
26 actions: Vec<InterpreterAction>,
27 handler: Handler,
28 pub node_builder: NodeBuilder,
29 breakpoints: Vec<Breakpoint>,
30 pub watchpoints: Vec<Watchpoint>,
31 saved_cursors: Vec<Cursor>,
32 filename_to_program: HashMap<PathBuf, String>,
33 parsed_inputs: u32,
34 network: NetworkName,
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, Q: 'a + AsRef<std::path::Path> + ?Sized>(
66 leo_source_files: &[(PathBuf, Vec<PathBuf>)], aleo_source_files: impl IntoIterator<Item = &'a Q>,
68 private_key: String,
69 block_height: u32,
70 block_timestamp: i64,
71 network: NetworkName,
72 ) -> Result<Self> {
73 Self::new_impl(
74 leo_source_files,
75 &mut aleo_source_files.into_iter().map(|p| p.as_ref()),
76 private_key,
77 block_height,
78 block_timestamp,
79 network,
80 )
81 }
82
83 fn get_ast(
101 path: &std::path::PathBuf,
102 modules: &[std::path::PathBuf],
103 handler: &Handler,
104 node_builder: &NodeBuilder,
105 network: NetworkName,
106 ) -> Result<Ast> {
107 let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?;
108 let source_file = with_session_globals(|s| s.source_map.new_source(&text, FileName::Real(path.to_path_buf())));
109
110 let modules = modules
111 .iter()
112 .map(|filename| {
113 let source = fs::read_to_string(filename).unwrap();
114 with_session_globals(|s| s.source_map.new_source(&source, FileName::Real(filename.to_path_buf())))
115 })
116 .collect::<Vec<_>>();
117
118 leo_parser::parse_ast(handler.clone(), node_builder, &source_file, &modules, network)
119 }
120
121 fn new_impl(
122 leo_source_files: &[(PathBuf, Vec<PathBuf>)],
123 aleo_source_files: &mut dyn Iterator<Item = &std::path::Path>,
124 private_key: String,
125 block_height: u32,
126 block_timestamp: i64,
127 network: NetworkName,
128 ) -> Result<Self> {
129 let handler = Handler::default();
130 let node_builder = Default::default();
131 let mut cursor: Cursor = Cursor::new(
132 true, private_key,
134 block_height,
135 block_timestamp,
136 network,
137 );
138 let mut filename_to_program = HashMap::new();
139
140 for (path, modules) in leo_source_files {
141 let ast = Self::get_ast(path, modules, &handler, &node_builder, network)?;
142 for (&program, scope) in ast.ast.program_scopes.iter() {
143 filename_to_program.insert(path.to_path_buf(), program.to_string());
144 for (name, function) in scope.functions.iter() {
145 cursor
146 .functions
147 .insert(Location::new(program, vec![*name]), FunctionVariant::Leo(function.clone()));
148 }
149
150 for (name, composite) in scope.structs.iter() {
151 cursor.structs.insert(
152 vec![*name],
153 composite
154 .members
155 .iter()
156 .map(|leo_ast::Member { identifier, type_, .. }| (identifier.name, type_.clone()))
157 .collect::<IndexMap<_, _>>(),
158 );
159 }
160
161 for (name, _mapping) in scope.mappings.iter() {
162 cursor.mappings.insert(Location::new(program, vec![*name]), HashMap::new());
163 }
164
165 for (name, const_declaration) in scope.consts.iter() {
166 cursor.frames.push(Frame {
167 step: 0,
168 element: Element::Expression(
169 const_declaration.value.clone(),
170 Some(const_declaration.type_.clone()),
171 ),
172 user_initiated: false,
173 });
174 cursor.over()?;
175 let value = cursor.values.pop().unwrap();
176 cursor.globals.insert(Location::new(program, vec![*name]), value);
177 }
178 }
179
180 for (mod_path, module) in ast.ast.modules.iter() {
181 let program = module.program_name;
182 let to_absolute_path = |name: Symbol| {
183 let mut full_name = mod_path.clone();
184 full_name.push(name);
185 full_name
186 };
187 for (name, function) in module.functions.iter() {
188 cursor.functions.insert(
189 Location::new(program, to_absolute_path(*name)),
190 FunctionVariant::Leo(function.clone()),
191 );
192 }
193
194 for (name, composite) in module.structs.iter() {
195 cursor.structs.insert(
196 to_absolute_path(*name),
197 composite
198 .members
199 .iter()
200 .map(|leo_ast::Member { identifier, type_, .. }| (identifier.name, type_.clone()))
201 .collect::<IndexMap<_, _>>(),
202 );
203 }
204
205 for (name, const_declaration) in module.consts.iter() {
206 cursor.frames.push(Frame {
207 step: 0,
208 element: Element::Expression(
209 const_declaration.value.clone(),
210 Some(const_declaration.type_.clone()),
211 ),
212 user_initiated: false,
213 });
214 cursor.over()?;
215 let value = cursor.values.pop().unwrap();
216 cursor.globals.insert(Location::new(program, to_absolute_path(*name)), value);
217 }
218 }
219 }
220
221 for path in aleo_source_files {
222 let aleo_program = Self::get_aleo_program(path)?;
223 let program = snarkvm_identifier_to_symbol(aleo_program.id().name());
224 filename_to_program.insert(path.to_path_buf(), program.to_string());
225
226 for (name, struct_type) in aleo_program.structs().iter() {
227 cursor.structs.insert(
228 vec![snarkvm_identifier_to_symbol(name)],
229 struct_type
230 .members()
231 .iter()
232 .map(|(id, type_)| {
233 (leo_ast::Identifier::from(id).name, leo_ast::Type::from_snarkvm(type_, None))
234 })
235 .collect::<IndexMap<_, _>>(),
236 );
237 }
238
239 for (name, record_type) in aleo_program.records().iter() {
240 use snarkvm::prelude::EntryType;
241 cursor.structs.insert(
242 vec![snarkvm_identifier_to_symbol(name)],
243 record_type
244 .entries()
245 .iter()
246 .map(|(id, entry)| {
247 let t = match entry {
249 EntryType::Public(t) | EntryType::Private(t) | EntryType::Constant(t) => t,
250 };
251
252 (leo_ast::Identifier::from(id).name, leo_ast::Type::from_snarkvm(t, None))
253 })
254 .collect::<IndexMap<_, _>>(),
255 );
256 }
257
258 for (name, _mapping) in aleo_program.mappings().iter() {
259 cursor
260 .mappings
261 .insert(Location::new(program, vec![snarkvm_identifier_to_symbol(name)]), HashMap::new());
262 }
263
264 for (name, function) in aleo_program.functions().iter() {
265 cursor.functions.insert(
266 Location::new(program, vec![snarkvm_identifier_to_symbol(name)]),
267 FunctionVariant::AleoFunction(function.clone()),
268 );
269 }
270
271 for (name, closure) in aleo_program.closures().iter() {
272 cursor.functions.insert(
273 Location::new(program, vec![snarkvm_identifier_to_symbol(name)]),
274 FunctionVariant::AleoClosure(closure.clone()),
275 );
276 }
277 }
278
279 Ok(Interpreter {
280 cursor,
281 handler,
282 node_builder,
283 actions: Vec::new(),
284 breakpoints: Vec::new(),
285 watchpoints: Vec::new(),
286 saved_cursors: Vec::new(),
287 filename_to_program,
288 parsed_inputs: 0,
289 network,
290 })
291 }
292
293 pub fn save_cursor(&mut self) {
294 self.saved_cursors.push(self.cursor.clone());
295 }
296
297 pub fn restore_cursor(&mut self) -> bool {
299 if let Some(old_cursor) = self.saved_cursors.pop() {
300 self.cursor = old_cursor;
301 true
302 } else {
303 false
304 }
305 }
306
307 fn get_aleo_program(path: &std::path::Path) -> Result<Program<TestnetV0>> {
308 let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?;
309 let program = text.parse()?;
310 Ok(program)
311 }
312
313 pub fn update_watchpoints(&mut self) -> Result<bool> {
315 let mut changed = false;
316 let safe_cursor = self.cursor.clone();
317
318 for i in 0..self.watchpoints.len() {
319 let code = self.watchpoints[i].code.clone();
320 let new_value = match self.action(InterpreterAction::LeoInterpretOver(code)) {
321 Ok(None) => None,
322 Ok(Some(ret)) => Some(ret.to_string()),
323 Err(LeoError::InterpreterHalt(halt)) => {
324 self.cursor = safe_cursor.clone();
325 Some(halt.to_string())
326 }
327 Err(e) => return Err(e),
328 };
329 if self.watchpoints[i].last_result != new_value {
330 changed = true;
331 self.watchpoints[i].last_result = new_value;
332 }
333 }
334 Ok(changed)
335 }
336
337 pub fn action(&mut self, act: InterpreterAction) -> Result<Option<Value>> {
338 use InterpreterAction::*;
339
340 let ret = match &act {
341 RunFuture(n) => {
342 let future = self.cursor.futures.remove(*n);
343 match future {
344 AsyncExecution::AsyncFunctionCall { function, arguments } => {
345 self.cursor.values.extend(arguments);
346 self.cursor.frames.push(Frame {
347 step: 0,
348 element: Element::DelayedCall(function),
349 user_initiated: true,
350 });
351 }
352 AsyncExecution::AsyncBlock { containing_function, block, names, .. } => {
353 self.cursor.frames.push(Frame {
354 step: 0,
355 element: Element::DelayedAsyncBlock {
356 program: containing_function.program,
357 block,
358 names: names.clone().into_iter().collect(),
361 },
362 user_initiated: false,
363 });
364 }
365 }
366 self.cursor.step()?
367 }
368 LeoInterpretInto(s) | LeoInterpretOver(s) => {
369 let filename = FileName::Custom(format!("user_input{:04}", self.parsed_inputs));
370 self.parsed_inputs += 1;
371 let source_file = with_session_globals(|globals| globals.source_map.new_source(s, filename));
372 let s = s.trim();
373 if s.ends_with(';') {
374 let statement = leo_parser::parse_statement(
375 self.handler.clone(),
376 &self.node_builder,
377 s,
378 source_file.absolute_start,
379 self.network,
380 )
381 .map_err(|_e| {
382 LeoError::InterpreterHalt(InterpreterHalt::new("failed to parse statement".into()))
383 })?;
384 self.cursor.frames.push(Frame {
386 step: 0,
387 element: Element::Statement(statement),
388 user_initiated: true,
389 });
390 } else {
391 let expression = leo_parser::parse_expression(
392 self.handler.clone(),
393 &self.node_builder,
394 s,
395 source_file.absolute_start,
396 self.network,
397 )
398 .map_err(|e| {
399 LeoError::InterpreterHalt(InterpreterHalt::new(format!("Failed to parse expression: {e}")))
400 })?;
401 self.cursor.frames.push(Frame {
403 step: 0,
404 element: Element::Expression(expression, None),
405 user_initiated: true,
406 });
407 };
408
409 if matches!(act, LeoInterpretOver(..)) {
410 self.cursor.over()?
411 } else {
412 StepResult { finished: false, value: None }
413 }
414 }
415
416 Step => self.cursor.whole_step()?,
417
418 Into => self.cursor.step()?,
419
420 Over => self.cursor.over()?,
421
422 Breakpoint(breakpoint) => {
423 self.breakpoints.push(breakpoint.clone());
424 StepResult { finished: false, value: None }
425 }
426
427 Watch(code) => {
428 self.watchpoints.push(Watchpoint { code: code.clone(), last_result: None });
429 StepResult { finished: false, value: None }
430 }
431
432 PrintRegister(register_index) => {
433 let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.cursor.frames.last()
434 else {
435 halt_no_span!("cannot print register - not currently interpreting Aleo VM code");
436 };
437
438 if let Some(value) = registers.get(register_index) {
439 StepResult { finished: false, value: Some(value.clone()) }
440 } else {
441 halt_no_span!("no such register {register_index}");
442 }
443 }
444
445 Run => {
446 while !self.cursor.frames.is_empty() {
447 if let Some((program, line)) = self.current_program_and_line()
448 && self.breakpoints.iter().any(|bp| bp.program == program && bp.line == line)
449 {
450 return Ok(None);
451 }
452 self.cursor.step()?;
453 if self.update_watchpoints()? {
454 return Ok(None);
455 }
456 }
457 StepResult { finished: false, value: None }
458 }
459 };
460
461 self.actions.push(act);
462
463 Ok(ret.value)
464 }
465
466 pub fn view_current(&self) -> Option<impl Display> {
467 if let Some(span) = self.current_span()
468 && span != Default::default()
469 {
470 return with_session_globals(|s| s.source_map.contents_of_span(span));
471 }
472
473 Some(match &self.cursor.frames.last()?.element {
474 Element::Statement(statement) => format!("{statement}"),
475 Element::Expression(expression, _) => format!("{expression}"),
476 Element::Block { block, .. } => format!("{block}"),
477 Element::DelayedCall(gid) => format!("Delayed call to {gid}"),
478 Element::DelayedAsyncBlock { .. } => "Delayed async block".to_string(),
479 Element::AleoExecution { context, instruction_index, .. } => match &**context {
480 AleoContext::Closure(closure) => closure.instructions().get(*instruction_index).map(|i| format!("{i}")),
481 AleoContext::Function(function) => {
482 function.instructions().get(*instruction_index).map(|i| format!("{i}"))
483 }
484 AleoContext::Finalize(finalize) => finalize.commands().get(*instruction_index).map(|i| format!("{i}")),
485 }
486 .unwrap_or_else(|| "...".to_string()),
487 })
488 }
489
490 pub fn view_current_in_context(&self) -> Option<(impl Display, usize, usize)> {
491 if let Some(Frame { element: Element::AleoExecution { context, instruction_index, .. }, .. }) =
492 self.cursor.frames.last()
493 {
494 fn write_all<I: Display>(
497 items: impl Iterator<Item = I>,
498 instruction_index: usize,
499 result: &mut String,
500 start: &mut usize,
501 stop: &mut usize,
502 ) {
503 for (i, item) in items.enumerate() {
504 if i == instruction_index {
505 *start = result.len();
506 }
507 writeln!(result, " {item}").expect("write shouldn't fail");
508 if i == instruction_index {
509 *stop = result.len();
510 }
511 }
512 }
513
514 let mut result = String::new();
515 let mut start: usize = 0usize;
516 let mut stop: usize = 0usize;
517
518 match &**context {
519 AleoContext::Closure(closure) => {
520 writeln!(&mut result, "closure {}", closure.name()).expect("write shouldn't fail");
521 write_all(closure.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize);
522 write_all(closure.instructions().iter(), *instruction_index, &mut result, &mut start, &mut stop);
523 write_all(closure.outputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize);
524 }
525 AleoContext::Function(function) => {
526 writeln!(&mut result, "function {}", function.name()).expect("write shouldn't fail");
527 write_all(function.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize);
528 write_all(function.instructions().iter(), *instruction_index, &mut result, &mut start, &mut stop);
529 write_all(function.outputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize);
530 }
531 AleoContext::Finalize(finalize) => {
532 writeln!(&mut result, "finalize {}", finalize.name()).expect("write shouldn't fail");
533 write_all(finalize.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize);
534 write_all(finalize.commands().iter(), *instruction_index, &mut result, &mut start, &mut stop);
535 }
536 }
537
538 Some((result, start, stop))
539 } else {
540 let span = self.current_span()?;
542 if span == Default::default() {
543 return None;
544 }
545 with_session_globals(|s| {
546 let source_file = s.source_map.find_source_file(span.lo)?;
547 let first_span = Span::new(source_file.absolute_start, span.lo);
548 let last_span = Span::new(span.hi, source_file.absolute_end);
549 let mut result = String::new();
550 result.push_str(&s.source_map.contents_of_span(first_span)?);
551 let start = result.len();
552 result.push_str(&s.source_map.contents_of_span(span)?);
553 let stop = result.len();
554 result.push_str(&s.source_map.contents_of_span(last_span)?);
555 Some((result, start, stop))
556 })
557 }
558 }
559
560 fn current_program_and_line(&self) -> Option<(String, usize)> {
561 if let Some(span) = self.current_span()
562 && let Some(source_file) = with_session_globals(|s| s.source_map.find_source_file(span.lo))
563 {
564 let (line, _) = source_file.line_col(span.lo);
565 if let FileName::Real(name) = &source_file.name
566 && let Some(program) = self.filename_to_program.get(name)
567 {
568 return Some((program.clone(), line as usize + 1));
569 }
570 }
571 None
572 }
573
574 fn current_span(&self) -> Option<Span> {
575 self.cursor.frames.last().map(|f| f.element.span())
576 }
577}