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