/ interpreter / src / lib.rs
lib.rs
  1  // Copyright (C) 2019-2025 Alpha-Delta Network Inc.
  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 adl_ast::{
 18      Ast, CallExpression, ExpressionStatement, Location, NetworkName, Node as _, NodeBuilder, Path, Statement,
 19      interpreter_value::Value,
 20  };
 21  use adl_errors::{AdlError, InterpreterHalt, Result};
 22  use adl_span::{Span, Symbol, source_map::FileName, sym, with_session_globals};
 23  
 24  use alphavm::prelude::{Program, TestnetV0};
 25  
 26  use indexmap::IndexMap;
 27  use itertools::Itertools;
 28  use std::{
 29      collections::HashMap,
 30      fmt::{Display, Write as _},
 31      fs,
 32      path::PathBuf,
 33  };
 34  
 35  #[cfg(test)]
 36  mod test;
 37  
 38  #[cfg(test)]
 39  mod test_interpreter;
 40  
 41  mod util;
 42  use util::*;
 43  
 44  mod cursor;
 45  use cursor::*;
 46  
 47  mod interpreter;
 48  use interpreter::*;
 49  
 50  mod cursor_alpha;
 51  
 52  mod ui;
 53  use ui::Ui;
 54  
 55  mod dialoguer_input;
 56  
 57  mod ratatui_ui;
 58  
 59  const INTRO: &str = "This is the Adl Interpreter. Try the command `#help`.";
 60  
 61  const HELP: &str = "
 62  You probably want to start by running a function or transition.
 63  For instance
 64  #into program.alpha/main()
 65  Once a function is running, commands include
 66  #into    to evaluate into the next expression or statement;
 67  #step    to take one step towards evaluating the current expression or statement;
 68  #over    to complete evaluating the current expression or statement;
 69  #run     to finish evaluating
 70  #quit    to quit the interpreter.
 71  
 72  You can set a breakpoint with
 73  #break program_name line_number
 74  
 75  When executing Alpha VM code, you can print the value of a register like this:
 76  #print 2
 77  
 78  Some of the commands may be run with one letter abbreviations, such as #i.
 79  
 80  Note that this interpreter is not line oriented as in many common debuggers;
 81  rather it is oriented around expressions and statements.
 82  As you step into code, individual expressions or statements will
 83  be evaluated one by one, including arguments of function calls.
 84  
 85  You may simply enter Adl expressions or statements on the command line
 86  to evaluate. For instance, if you want to see the value of a variable w:
 87  w
 88  If you want to set w to a new value:
 89  w = z + 2u8;
 90  
 91  Note that statements (like the assignment above) must end with a semicolon.
 92  
 93  If there are futures available to be executed, they will be listed by
 94  numerical index, and you may run them using `#future` (or `#f`); for instance
 95  #future 0
 96  
 97  The interpreter begins in a global context, not in any Adl program. You can set
 98  the current program with
 99  
100  #set_program program_name
101  
102  This allows you to refer to structs and other items in the indicated program.
103  
104  The interpreter may enter an invalid state, often due to Adl code entered at the
105  REPL. In this case, you may use the command
106  
107  #restore
108  
109  Which will restore to the last saved state of the interpreter. Any time you
110  enter Adl code at the prompt, interpreter state is saved.
111  
112  Input history is available - use the up and down arrow keys.
113  ";
114  
115  fn parse_breakpoint(s: &str) -> Option<Breakpoint> {
116      let strings: Vec<&str> = s.split_whitespace().collect();
117      if strings.len() == 2
118          && let Ok(line) = strings[1].parse::<usize>()
119      {
120          let program = strings[0].strip_suffix(".alpha").unwrap_or(strings[0]).to_string();
121          return Some(Breakpoint { program, line });
122      }
123      None
124  }
125  
126  pub struct TestFunction {
127      pub program: String,
128      pub function: String,
129      pub should_fail: bool,
130      pub private_key: Option<String>,
131  }
132  
133  /// Run interpreter tests and return data about native tests.
134  // It's slightly goofy to have this function responsible for both of these tasks, but
135  // it's expedient as the `Interpreter` will already parse all the files and collect
136  // all the functions with annotations.
137  #[allow(clippy::type_complexity)]
138  pub fn find_and_run_tests(
139      adl_filenames: &[(PathBuf, Vec<PathBuf>)], // Adl source files and their modules
140      alphastd_filenames: &[PathBuf],
141      private_key: String,
142      block_height: u32,
143      block_timestamp: i64,
144      match_str: &str,
145      network: NetworkName,
146  ) -> Result<(Vec<TestFunction>, IndexMap<Location, Result<()>>)> {
147      let mut interpreter =
148          Interpreter::new(adl_filenames, alphastd_filenames, private_key, block_height, block_timestamp, network)?;
149  
150      let mut native_test_functions = Vec::new();
151  
152      let private_key_symbol = Symbol::intern("private_key");
153  
154      let mut result = IndexMap::new();
155  
156      for (id, function) in interpreter.cursor.functions.clone().into_iter() {
157          // Only Adl functions may be tests.
158          let FunctionVariant::Adl(function) = function else {
159              continue;
160          };
161  
162          let should_fail = function.annotations.iter().any(|annotation| annotation.identifier.name == sym::should_fail);
163  
164          let str_matches = || id.to_string().contains(match_str);
165  
166          // If this function is not annotated with @test, skip it.
167          let Some(annotation) = function.annotations.iter().find(|annotation| annotation.identifier.name == sym::test)
168          else {
169              continue;
170          };
171  
172          // If the name doesn't match, skip it.
173          if !str_matches() {
174              continue;
175          }
176  
177          assert!(function.input.is_empty(), "Type checking should ensure test functions have no inputs.");
178  
179          if function.variant.is_transition() {
180              // It's a native test; just store it and move on.
181              let private_key = annotation.map.get(&private_key_symbol).cloned();
182              native_test_functions.push(TestFunction {
183                  program: id.program.to_string(),
184                  function: id.path.iter().format("::").to_string(),
185                  should_fail,
186                  private_key,
187              });
188              continue;
189          }
190  
191          assert!(function.variant.is_script(), "Type checking should ensure test functions are transitions or scripts.");
192  
193          let call = CallExpression {
194              function: function.identifier.into(),
195              const_arguments: vec![], // scripts don't have const parameters for now
196              arguments: Vec::new(),
197              program: Some(id.program),
198              span: Default::default(),
199              id: interpreter.node_builder.next_id(),
200          };
201  
202          let statement: Statement = ExpressionStatement {
203              expression: call.into(),
204              span: Default::default(),
205              id: interpreter.node_builder.next_id(),
206          }
207          .into();
208  
209          interpreter.cursor.frames.push(Frame {
210              step: 0,
211              element: Element::Statement(statement),
212              user_initiated: false,
213          });
214  
215          let run_result = interpreter.cursor.over();
216  
217          match (run_result, should_fail) {
218              (Ok(..), true) => {
219                  result.insert(
220                      id,
221                      Err(InterpreterHalt::new("Test succeeded when failure was expected.".to_string()).into()),
222                  );
223              }
224              (Ok(..), false) => {
225                  result.insert(id, Ok(()));
226              }
227              (Err(..), true) => {
228                  result.insert(id, Ok(()));
229              }
230              (Err(err), false) => {
231                  result.insert(id, Err(err));
232              }
233          }
234  
235          // Clear the state for the next test.
236          interpreter.cursor.clear();
237      }
238  
239      Ok((native_test_functions, result))
240  }
241  
242  /// Load all the Adl source files indicated and open the interpreter
243  /// to commands from the user.
244  pub fn interpret(
245      adl_filenames: &[(PathBuf, Vec<PathBuf>)], // Adl source files and their modules
246      alphastd_filenames: &[PathBuf],
247      private_key: String,
248      block_height: u32,
249      block_timestamp: i64,
250      tui: bool,
251      network: NetworkName,
252  ) -> Result<()> {
253      let mut interpreter =
254          Interpreter::new(adl_filenames, alphastd_filenames, private_key, block_height, block_timestamp, network)?;
255  
256      let mut user_interface: Box<dyn Ui> =
257          if tui { Box::new(ratatui_ui::RatatuiUi::new()) } else { Box::new(dialoguer_input::DialoguerUi::new()) };
258  
259      let mut futures = Vec::new();
260      let mut watchpoints = Vec::new();
261      let mut message = INTRO.to_string();
262      let mut result = String::new();
263  
264      loop {
265          futures.clear();
266          watchpoints.clear();
267  
268          let (code, highlight) = if let Some((code, lo, hi)) = interpreter.view_current_in_context() {
269              (code.to_string(), Some((lo, hi)))
270          } else if let Some(v) = interpreter.view_current() {
271              (v.to_string(), None)
272          } else {
273              ("".to_string(), None)
274          };
275  
276          futures.extend(interpreter.cursor.futures.iter().map(|f| f.to_string()));
277  
278          interpreter.update_watchpoints()?;
279  
280          watchpoints.extend(interpreter.watchpoints.iter().map(|watchpoint| {
281              format!("{:>15} = {}", watchpoint.code, if let Some(s) = &watchpoint.last_result { &**s } else { "?" })
282          }));
283  
284          let user_data = ui::UserData {
285              code: &code,
286              highlight,
287              message: &message,
288              futures: &futures,
289              watchpoints: &watchpoints,
290              result: if result.is_empty() { None } else { Some(&result) },
291          };
292  
293          user_interface.display_user_data(&user_data);
294  
295          message.clear();
296          result.clear();
297  
298          let user_input = user_interface.receive_user_input();
299  
300          let (command, rest) = tokenize_user_input(&user_input);
301  
302          let action = match (command, rest) {
303              ("", "") => continue,
304              ("#h" | "#help", "") => {
305                  message = HELP.to_string();
306                  continue;
307              }
308              ("#i" | "#into", "") => InterpreterAction::Into,
309              ("#i" | "#into", rest) => InterpreterAction::AdlInterpretInto(rest.into()),
310              ("#s" | "#step", "") => InterpreterAction::Step,
311              ("#o" | "#over", "") => InterpreterAction::Over,
312              ("#r" | "#run", "") => InterpreterAction::Run,
313              ("#q" | "#quit", "") => return Ok(()),
314              ("#f" | "#future", rest) => {
315                  if let Ok(num) = rest.trim().parse::<usize>() {
316                      if num >= interpreter.cursor.futures.len() {
317                          message = "No such future.".to_string();
318                          continue;
319                      }
320                      InterpreterAction::RunFuture(num)
321                  } else {
322                      message = "Failed to parse future.".to_string();
323                      continue;
324                  }
325              }
326              ("#restore", "") => {
327                  if !interpreter.restore_cursor() {
328                      message = "No saved state to restore".to_string();
329                  }
330                  continue;
331              }
332              ("#b" | "#break", rest) => {
333                  let Some(breakpoint) = parse_breakpoint(rest) else {
334                      message = "Failed to parse breakpoint".to_string();
335                      continue;
336                  };
337                  InterpreterAction::Breakpoint(breakpoint)
338              }
339              ("#p" | "#print", rest) => {
340                  let without_r = rest.strip_prefix("r").unwrap_or(rest);
341                  if let Ok(num) = without_r.parse::<u64>() {
342                      InterpreterAction::PrintRegister(num)
343                  } else {
344                      message = "Failed to parse register number".to_string();
345                      continue;
346                  }
347              }
348              ("#w" | "#watch", rest) => InterpreterAction::Watch(rest.to_string()),
349              ("#set_program", rest) => {
350                  interpreter.cursor.set_program(rest);
351                  continue;
352              }
353              ("", rest) => InterpreterAction::AdlInterpretOver(rest.to_string()),
354              _ => {
355                  message = "Failed to parse command".to_string();
356                  continue;
357              }
358          };
359  
360          if matches!(action, InterpreterAction::AdlInterpretInto(..) | InterpreterAction::AdlInterpretOver(..)) {
361              interpreter.save_cursor();
362          }
363  
364          match interpreter.action(action) {
365              Ok(Some(value)) => {
366                  result = value.to_string();
367              }
368              Ok(None) => {}
369              Err(AdlError::InterpreterHalt(interpreter_halt)) => {
370                  message = format!("Halted: {interpreter_halt}");
371              }
372              Err(e) => return Err(e),
373          }
374      }
375  }
376  
377  fn tokenize_user_input(input: &str) -> (&str, &str) {
378      let input = input.trim();
379  
380      if !input.starts_with("#") {
381          return ("", input);
382      }
383  
384      let Some((first, rest)) = input.split_once(' ') else {
385          return (input, "");
386      };
387  
388      (first.trim(), rest.trim())
389  }