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