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