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 block_timestamp: i64,
152 match_str: &str,
153 network: NetworkName,
154) -> Result<(Vec<TestFunction>, IndexMap<Location, Result<()>>)> {
155 let mut interpreter =
156 Interpreter::new(leo_filenames, aleo_filenames, private_key, block_height, block_timestamp, network)?;
157
158 let mut native_test_functions = Vec::new();
159
160 let private_key_symbol = Symbol::intern("private_key");
161
162 let mut result = IndexMap::new();
163
164 for (id, function) in interpreter.cursor.functions.clone().into_iter() {
165 let FunctionVariant::Leo(function) = function else {
167 continue;
168 };
169
170 let should_fail = function.annotations.iter().any(|annotation| annotation.identifier.name == sym::should_fail);
171
172 let str_matches = || id.to_string().contains(match_str);
173
174 let Some(annotation) = function.annotations.iter().find(|annotation| annotation.identifier.name == sym::test)
176 else {
177 continue;
178 };
179
180 if !str_matches() {
182 continue;
183 }
184
185 assert!(function.input.is_empty(), "Type checking should ensure test functions have no inputs.");
186
187 if function.variant.is_transition() {
188 let private_key = annotation.map.get(&private_key_symbol).cloned();
190 native_test_functions.push(TestFunction {
191 program: id.program.to_string(),
192 function: id.path.iter().format("::").to_string(),
193 should_fail,
194 private_key,
195 });
196 continue;
197 }
198
199 assert!(function.variant.is_script(), "Type checking should ensure test functions are transitions or scripts.");
200
201 let call = CallExpression {
202 function: function.identifier.into(),
203 const_arguments: vec![], arguments: Vec::new(),
205 program: Some(id.program),
206 span: Default::default(),
207 id: interpreter.node_builder.next_id(),
208 };
209
210 let statement: Statement = ExpressionStatement {
211 expression: call.into(),
212 span: Default::default(),
213 id: interpreter.node_builder.next_id(),
214 }
215 .into();
216
217 interpreter.cursor.frames.push(Frame {
218 step: 0,
219 element: Element::Statement(statement),
220 user_initiated: false,
221 });
222
223 let run_result = interpreter.cursor.over();
224
225 match (run_result, should_fail) {
226 (Ok(..), true) => {
227 result.insert(
228 id,
229 Err(InterpreterHalt::new("Test succeeded when failure was expected.".to_string()).into()),
230 );
231 }
232 (Ok(..), false) => {
233 result.insert(id, Ok(()));
234 }
235 (Err(..), true) => {
236 result.insert(id, Ok(()));
237 }
238 (Err(err), false) => {
239 result.insert(id, Err(err));
240 }
241 }
242
243 interpreter.cursor.clear();
245 }
246
247 Ok((native_test_functions, result))
248}
249
250pub fn interpret(
253 leo_filenames: &[(PathBuf, Vec<PathBuf>)], aleo_filenames: &[PathBuf],
255 private_key: String,
256 block_height: u32,
257 block_timestamp: i64,
258 tui: bool,
259 network: NetworkName,
260) -> Result<()> {
261 let mut interpreter =
262 Interpreter::new(leo_filenames, aleo_filenames, private_key, block_height, block_timestamp, network)?;
263
264 let mut user_interface: Box<dyn Ui> =
265 if tui { Box::new(ratatui_ui::RatatuiUi::new()) } else { Box::new(dialoguer_input::DialoguerUi::new()) };
266
267 let mut futures = Vec::new();
268 let mut watchpoints = Vec::new();
269 let mut message = INTRO.to_string();
270 let mut result = String::new();
271
272 loop {
273 futures.clear();
274 watchpoints.clear();
275
276 let (code, highlight) = if let Some((code, lo, hi)) = interpreter.view_current_in_context() {
277 (code.to_string(), Some((lo, hi)))
278 } else if let Some(v) = interpreter.view_current() {
279 (v.to_string(), None)
280 } else {
281 ("".to_string(), None)
282 };
283
284 futures.extend(interpreter.cursor.futures.iter().map(|f| f.to_string()));
285
286 interpreter.update_watchpoints()?;
287
288 watchpoints.extend(interpreter.watchpoints.iter().map(|watchpoint| {
289 format!("{:>15} = {}", watchpoint.code, if let Some(s) = &watchpoint.last_result { &**s } else { "?" })
290 }));
291
292 let user_data = ui::UserData {
293 code: &code,
294 highlight,
295 message: &message,
296 futures: &futures,
297 watchpoints: &watchpoints,
298 result: if result.is_empty() { None } else { Some(&result) },
299 };
300
301 user_interface.display_user_data(&user_data);
302
303 message.clear();
304 result.clear();
305
306 let user_input = user_interface.receive_user_input();
307
308 let (command, rest) = tokenize_user_input(&user_input);
309
310 let action = match (command, rest) {
311 ("", "") => continue,
312 ("#h" | "#help", "") => {
313 message = HELP.to_string();
314 continue;
315 }
316 ("#i" | "#into", "") => InterpreterAction::Into,
317 ("#i" | "#into", rest) => InterpreterAction::LeoInterpretInto(rest.into()),
318 ("#s" | "#step", "") => InterpreterAction::Step,
319 ("#o" | "#over", "") => InterpreterAction::Over,
320 ("#r" | "#run", "") => InterpreterAction::Run,
321 ("#q" | "#quit", "") => return Ok(()),
322 ("#f" | "#future", rest) => {
323 if let Ok(num) = rest.trim().parse::<usize>() {
324 if num >= interpreter.cursor.futures.len() {
325 message = "No such future.".to_string();
326 continue;
327 }
328 InterpreterAction::RunFuture(num)
329 } else {
330 message = "Failed to parse future.".to_string();
331 continue;
332 }
333 }
334 ("#restore", "") => {
335 if !interpreter.restore_cursor() {
336 message = "No saved state to restore".to_string();
337 }
338 continue;
339 }
340 ("#b" | "#break", rest) => {
341 let Some(breakpoint) = parse_breakpoint(rest) else {
342 message = "Failed to parse breakpoint".to_string();
343 continue;
344 };
345 InterpreterAction::Breakpoint(breakpoint)
346 }
347 ("#p" | "#print", rest) => {
348 let without_r = rest.strip_prefix("r").unwrap_or(rest);
349 if let Ok(num) = without_r.parse::<u64>() {
350 InterpreterAction::PrintRegister(num)
351 } else {
352 message = "Failed to parse register number".to_string();
353 continue;
354 }
355 }
356 ("#w" | "#watch", rest) => InterpreterAction::Watch(rest.to_string()),
357 ("#set_program", rest) => {
358 interpreter.cursor.set_program(rest);
359 continue;
360 }
361 ("", rest) => InterpreterAction::LeoInterpretOver(rest.to_string()),
362 _ => {
363 message = "Failed to parse command".to_string();
364 continue;
365 }
366 };
367
368 if matches!(action, InterpreterAction::LeoInterpretInto(..) | InterpreterAction::LeoInterpretOver(..)) {
369 interpreter.save_cursor();
370 }
371
372 match interpreter.action(action) {
373 Ok(Some(value)) => {
374 result = value.to_string();
375 }
376 Ok(None) => {}
377 Err(LeoError::InterpreterHalt(interpreter_halt)) => {
378 message = format!("Halted: {interpreter_halt}");
379 }
380 Err(e) => return Err(e),
381 }
382 }
383}
384
385fn tokenize_user_input(input: &str) -> (&str, &str) {
386 let input = input.trim();
387
388 if !input.starts_with("#") {
389 return ("", input);
390 }
391
392 let Some((first, rest)) = input.split_once(' ') else {
393 return (input, "");
394 };
395
396 (first.trim(), rest.trim())
397}