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