/ interpreter / src / interpreter.rs
interpreter.rs
  1  // Copyright (C) 2019-2025 ADnet Contributors
  2  // This file is part of the ADL library.
  3  
  4  // The ADL library is free software: you can redistribute it and/or modify
  5  // it under the terms of the GNU General Public License as published by
  6  // the Free Software Foundation, either version 3 of the License, or
  7  // (at your option) any later version.
  8  
  9  // The ADL library is distributed in the hope that it will be useful,
 10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 12  // GNU General Public License for more details.
 13  
 14  // You should have received a copy of the GNU General Public License
 15  // along with the ADL library. If not, see <https://www.gnu.org/licenses/>.
 16  
 17  use super::*;
 18  use adl_ast::{NetworkName, interpreter_value::AsyncExecution};
 19  use adl_errors::{AdlError, CompilerError, Handler, InterpreterHalt, Result};
 20  
 21  /// Contains the state of interpretation, in the form of the `Cursor`,
 22  /// as well as information needed to interact with the user, like
 23  /// the breakpoints.
 24  pub 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      /// The network.
 35      network: NetworkName,
 36  }
 37  
 38  #[derive(Clone, Debug)]
 39  pub struct Breakpoint {
 40      pub program: String,
 41      pub line: usize,
 42  }
 43  
 44  #[derive(Clone, Debug)]
 45  pub struct Watchpoint {
 46      pub code: String,
 47      pub last_result: Option<String>,
 48  }
 49  
 50  #[derive(Clone, Debug)]
 51  pub 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  
 64  impl Interpreter {
 65      pub fn new<'a, Q: 'a + AsRef<std::path::Path> + ?Sized>(
 66          adl_source_files: &[(PathBuf, Vec<PathBuf>)], // Leo source files and their modules
 67          alpha_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              adl_source_files,
 75              &mut alpha_source_files.into_iter().map(|p| p.as_ref()),
 76              private_key,
 77              block_height,
 78              block_timestamp,
 79              network,
 80          )
 81      }
 82  
 83      /// Parses a Leo source file and its modules into an `Ast`.
 84      ///
 85      /// # Arguments
 86      /// - `path`: The path to the main `.adl` source file (e.g. `main.adl`).
 87      /// - `modules`: A list of paths to module `.adl` files associated with the main file.
 88      /// - `handler`: The compiler's diagnostic handler for reporting errors.
 89      /// - `node_builder`: Utility for constructing unique node IDs in the AST.
 90      /// - `network`: The target network.
 91      ///
 92      /// # Returns
 93      /// - `Ok(Ast)`: If parsing succeeds.
 94      /// - `Err(CompilerError)`: If file I/O or parsing fails.
 95      ///
 96      /// # Behavior
 97      /// - Reads the contents of the main file and all modules.
 98      /// - Registers each source file with the compiler's source map (via `with_session_globals`).
 99      /// - Invokes the parser to produce the full AST including modules.
100      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          adl_parser::parse_ast(handler.clone(), node_builder, &source_file, &modules, network)
119      }
120  
121      fn new_impl(
122          adl_source_files: &[(PathBuf, Vec<PathBuf>)],
123          alpha_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, // really_async
133              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 adl_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.composites.iter() {
151                      cursor.composites.insert(
152                          vec![*name],
153                          composite
154                              .members
155                              .iter()
156                              .map(|adl_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.composites.iter() {
195                      cursor.composites.insert(
196                          to_absolute_path(*name),
197                          composite
198                              .members
199                              .iter()
200                              .map(|adl_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 alpha_source_files {
222              let alpha_program = Self::get_alpha_program(path)?;
223              let program = snarkvm_identifier_to_symbol(alpha_program.id().name());
224              filename_to_program.insert(path.to_path_buf(), program.to_string());
225  
226              for (name, struct_type) in alpha_program.structs().iter() {
227                  cursor.composites.insert(
228                      vec![snarkvm_identifier_to_symbol(name)],
229                      struct_type
230                          .members()
231                          .iter()
232                          .map(|(id, type_)| {
233                              (adl_ast::Identifier::from(id).name, adl_ast::Type::from_snarkvm(type_, None))
234                          })
235                          .collect::<IndexMap<_, _>>(),
236                  );
237              }
238  
239              for (name, record_type) in alpha_program.records().iter() {
240                  use snarkvm::prelude::EntryType;
241                  cursor.composites.insert(
242                      vec![snarkvm_identifier_to_symbol(name)],
243                      record_type
244                          .entries()
245                          .iter()
246                          .map(|(id, entry)| {
247                              // Destructure to get the inner type `t` directly
248                              let t = match entry {
249                                  EntryType::Public(t) | EntryType::Private(t) | EntryType::Constant(t) => t,
250                              };
251  
252                              (adl_ast::Identifier::from(id).name, adl_ast::Type::from_snarkvm(t, None))
253                          })
254                          .collect::<IndexMap<_, _>>(),
255                  );
256              }
257  
258              for (name, _mapping) in alpha_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 alpha_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 alpha_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      /// Returns false if there was no saved cursor to restore.
298      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_alpha_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      /// Returns true if any watchpoints changed.
314      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(AdlError::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                                  // Keep track of all the known variables up to this point.
359                                  // These are available to use inside the block when we actually execute it.
360                                  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 = adl_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                          AdlError::InterpreterHalt(InterpreterHalt::new("failed to parse statement".into()))
383                      })?;
384                      // The spans of the code the user wrote at the REPL are meaningless, so get rid of them.
385                      self.cursor.frames.push(Frame {
386                          step: 0,
387                          element: Element::Statement(statement),
388                          user_initiated: true,
389                      });
390                  } else {
391                      let expression = adl_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                          AdlError::InterpreterHalt(InterpreterHalt::new(format!("Failed to parse expression: {e}")))
400                      })?;
401                      // The spans of the code the user wrote at the REPL are meaningless, so get rid of them.
402                      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              // For Aleo VM code, there are no spans; just print out the code without referring to the source code.
495  
496              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              // For Leo code, we use spans to print the original source code.
541              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  }