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