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 && let Ok(line) = strings[1].parse::<usize>()
127 {
128 let program = strings[0].strip_suffix(".aleo").unwrap_or(strings[0]).to_string();
129 return Some(Breakpoint { program, line });
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 private_key: String,
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, private_key, 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 interpreter.cursor.clear();
243 }
244
245 Ok((native_test_functions, result))
246}
247
248pub fn interpret(
251 leo_filenames: &[(PathBuf, Vec<PathBuf>)], aleo_filenames: &[PathBuf],
253 private_key: String,
254 block_height: u32,
255 tui: bool,
256 network: NetworkName,
257) -> Result<()> {
258 let mut interpreter = Interpreter::new(leo_filenames, aleo_filenames, private_key, block_height, network)?;
259
260 let mut user_interface: Box<dyn Ui> =
261 if tui { Box::new(ratatui_ui::RatatuiUi::new()) } else { Box::new(dialoguer_input::DialoguerUi::new()) };
262
263 let mut code = String::new();
264 let mut futures = Vec::new();
265 let mut watchpoints = Vec::new();
266 let mut message = INTRO.to_string();
267 let mut result = String::new();
268
269 loop {
270 code.clear();
271 futures.clear();
272 watchpoints.clear();
273
274 let (code, highlight) = if let Some((code, lo, hi)) = interpreter.view_current_in_context() {
275 (code.to_string(), Some((lo, hi)))
276 } else if let Some(v) = interpreter.view_current() {
277 (v.to_string(), None)
278 } else {
279 ("".to_string(), None)
280 };
281
282 futures.extend(interpreter.cursor.futures.iter().map(|f| f.to_string()));
283
284 interpreter.update_watchpoints()?;
285
286 watchpoints.extend(interpreter.watchpoints.iter().map(|watchpoint| {
287 format!("{:>15} = {}", watchpoint.code, if let Some(s) = &watchpoint.last_result { &**s } else { "?" })
288 }));
289
290 let user_data = ui::UserData {
291 code: &code,
292 highlight,
293 message: &message,
294 futures: &futures,
295 watchpoints: &watchpoints,
296 result: if result.is_empty() { None } else { Some(&result) },
297 };
298
299 user_interface.display_user_data(&user_data);
300
301 message.clear();
302 result.clear();
303
304 let user_input = user_interface.receive_user_input();
305
306 let (command, rest) = tokenize_user_input(&user_input);
307
308 let action = match (command, rest) {
309 ("", "") => continue,
310 ("#h" | "#help", "") => {
311 message = HELP.to_string();
312 continue;
313 }
314 ("#i" | "#into", "") => InterpreterAction::Into,
315 ("#i" | "#into", rest) => InterpreterAction::LeoInterpretInto(rest.into()),
316 ("#s" | "#step", "") => InterpreterAction::Step,
317 ("#o" | "#over", "") => InterpreterAction::Over,
318 ("#r" | "#run", "") => InterpreterAction::Run,
319 ("#q" | "#quit", "") => return Ok(()),
320 ("#f" | "#future", rest) => {
321 if let Ok(num) = rest.trim().parse::<usize>() {
322 if num >= interpreter.cursor.futures.len() {
323 message = "No such future.".to_string();
324 continue;
325 }
326 InterpreterAction::RunFuture(num)
327 } else {
328 message = "Failed to parse future.".to_string();
329 continue;
330 }
331 }
332 ("#restore", "") => {
333 if !interpreter.restore_cursor() {
334 message = "No saved state to restore".to_string();
335 }
336 continue;
337 }
338 ("#b" | "#break", rest) => {
339 let Some(breakpoint) = parse_breakpoint(rest) else {
340 message = "Failed to parse breakpoint".to_string();
341 continue;
342 };
343 InterpreterAction::Breakpoint(breakpoint)
344 }
345 ("#p" | "#print", rest) => {
346 let without_r = rest.strip_prefix("r").unwrap_or(rest);
347 if let Ok(num) = without_r.parse::<u64>() {
348 InterpreterAction::PrintRegister(num)
349 } else {
350 message = "Failed to parse register number".to_string();
351 continue;
352 }
353 }
354 ("#w" | "#watch", rest) => InterpreterAction::Watch(rest.to_string()),
355 ("#set_program", rest) => {
356 interpreter.cursor.set_program(rest);
357 continue;
358 }
359 ("", rest) => InterpreterAction::LeoInterpretOver(rest.to_string()),
360 _ => {
361 message = "Failed to parse command".to_string();
362 continue;
363 }
364 };
365
366 if matches!(action, InterpreterAction::LeoInterpretInto(..) | InterpreterAction::LeoInterpretOver(..)) {
367 interpreter.save_cursor();
368 }
369
370 match interpreter.action(action) {
371 Ok(Some(value)) => {
372 result = value.to_string();
373 }
374 Ok(None) => {}
375 Err(LeoError::InterpreterHalt(interpreter_halt)) => {
376 message = format!("Halted: {interpreter_halt}");
377 }
378 Err(e) => return Err(e),
379 }
380 }
381}
382
383fn tokenize_user_input(input: &str) -> (&str, &str) {
384 let input = input.trim();
385
386 if !input.starts_with("#") {
387 return ("", input);
388 }
389
390 let Some((first, rest)) = input.split_once(' ') else {
391 return (input, "");
392 };
393
394 (first.trim(), rest.trim())
395}