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