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