interpreter.rs
1 // Copyright (C) 2019-2025 ADnet Contributors 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 super::*; 18 use adl_ast::{NetworkName, interpreter_value::AsyncExecution}; 19 use adl_errors::{AdlError, CompilerError, Handler, InterpreterHalt, Result}; 20 21 /// Contains the state of interpretation, in the form of the `Cursor`, 22 /// as well as information needed to interact with the user, like 23 /// the breakpoints. 24 pub struct Interpreter { 25 pub cursor: Cursor, 26 actions: Vec<InterpreterAction>, 27 handler: Handler, 28 pub node_builder: NodeBuilder, 29 breakpoints: Vec<Breakpoint>, 30 pub watchpoints: Vec<Watchpoint>, 31 saved_cursors: Vec<Cursor>, 32 filename_to_program: HashMap<PathBuf, String>, 33 parsed_inputs: u32, 34 /// The network. 35 network: NetworkName, 36 } 37 38 #[derive(Clone, Debug)] 39 pub struct Breakpoint { 40 pub program: String, 41 pub line: usize, 42 } 43 44 #[derive(Clone, Debug)] 45 pub struct Watchpoint { 46 pub code: String, 47 pub last_result: Option<String>, 48 } 49 50 #[derive(Clone, Debug)] 51 pub enum InterpreterAction { 52 LeoInterpretInto(String), 53 LeoInterpretOver(String), 54 Watch(String), 55 RunFuture(usize), 56 Breakpoint(Breakpoint), 57 PrintRegister(u64), 58 Into, 59 Over, 60 Step, 61 Run, 62 } 63 64 impl Interpreter { 65 pub fn new<'a, Q: 'a + AsRef<std::path::Path> + ?Sized>( 66 adl_source_files: &[(PathBuf, Vec<PathBuf>)], // Leo source files and their modules 67 alpha_source_files: impl IntoIterator<Item = &'a Q>, 68 private_key: String, 69 block_height: u32, 70 block_timestamp: i64, 71 network: NetworkName, 72 ) -> Result<Self> { 73 Self::new_impl( 74 adl_source_files, 75 &mut alpha_source_files.into_iter().map(|p| p.as_ref()), 76 private_key, 77 block_height, 78 block_timestamp, 79 network, 80 ) 81 } 82 83 /// Parses a Leo source file and its modules into an `Ast`. 84 /// 85 /// # Arguments 86 /// - `path`: The path to the main `.adl` source file (e.g. `main.adl`). 87 /// - `modules`: A list of paths to module `.adl` files associated with the main file. 88 /// - `handler`: The compiler's diagnostic handler for reporting errors. 89 /// - `node_builder`: Utility for constructing unique node IDs in the AST. 90 /// - `network`: The target network. 91 /// 92 /// # Returns 93 /// - `Ok(Ast)`: If parsing succeeds. 94 /// - `Err(CompilerError)`: If file I/O or parsing fails. 95 /// 96 /// # Behavior 97 /// - Reads the contents of the main file and all modules. 98 /// - Registers each source file with the compiler's source map (via `with_session_globals`). 99 /// - Invokes the parser to produce the full AST including modules. 100 fn get_ast( 101 path: &std::path::PathBuf, 102 modules: &[std::path::PathBuf], 103 handler: &Handler, 104 node_builder: &NodeBuilder, 105 network: NetworkName, 106 ) -> Result<Ast> { 107 let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?; 108 let source_file = with_session_globals(|s| s.source_map.new_source(&text, FileName::Real(path.to_path_buf()))); 109 110 let modules = modules 111 .iter() 112 .map(|filename| { 113 let source = fs::read_to_string(filename).unwrap(); 114 with_session_globals(|s| s.source_map.new_source(&source, FileName::Real(filename.to_path_buf()))) 115 }) 116 .collect::<Vec<_>>(); 117 118 adl_parser::parse_ast(handler.clone(), node_builder, &source_file, &modules, network) 119 } 120 121 fn new_impl( 122 adl_source_files: &[(PathBuf, Vec<PathBuf>)], 123 alpha_source_files: &mut dyn Iterator<Item = &std::path::Path>, 124 private_key: String, 125 block_height: u32, 126 block_timestamp: i64, 127 network: NetworkName, 128 ) -> Result<Self> { 129 let handler = Handler::default(); 130 let node_builder = Default::default(); 131 let mut cursor: Cursor = Cursor::new( 132 true, // really_async 133 private_key, 134 block_height, 135 block_timestamp, 136 network, 137 ); 138 let mut filename_to_program = HashMap::new(); 139 140 for (path, modules) in adl_source_files { 141 let ast = Self::get_ast(path, modules, &handler, &node_builder, network)?; 142 for (&program, scope) in ast.ast.program_scopes.iter() { 143 filename_to_program.insert(path.to_path_buf(), program.to_string()); 144 for (name, function) in scope.functions.iter() { 145 cursor 146 .functions 147 .insert(Location::new(program, vec![*name]), FunctionVariant::Leo(function.clone())); 148 } 149 150 for (name, composite) in scope.composites.iter() { 151 cursor.composites.insert( 152 vec![*name], 153 composite 154 .members 155 .iter() 156 .map(|adl_ast::Member { identifier, type_, .. }| (identifier.name, type_.clone())) 157 .collect::<IndexMap<_, _>>(), 158 ); 159 } 160 161 for (name, _mapping) in scope.mappings.iter() { 162 cursor.mappings.insert(Location::new(program, vec![*name]), HashMap::new()); 163 } 164 165 for (name, const_declaration) in scope.consts.iter() { 166 cursor.frames.push(Frame { 167 step: 0, 168 element: Element::Expression( 169 const_declaration.value.clone(), 170 Some(const_declaration.type_.clone()), 171 ), 172 user_initiated: false, 173 }); 174 cursor.over()?; 175 let value = cursor.values.pop().unwrap(); 176 cursor.globals.insert(Location::new(program, vec![*name]), value); 177 } 178 } 179 180 for (mod_path, module) in ast.ast.modules.iter() { 181 let program = module.program_name; 182 let to_absolute_path = |name: Symbol| { 183 let mut full_name = mod_path.clone(); 184 full_name.push(name); 185 full_name 186 }; 187 for (name, function) in module.functions.iter() { 188 cursor.functions.insert( 189 Location::new(program, to_absolute_path(*name)), 190 FunctionVariant::Leo(function.clone()), 191 ); 192 } 193 194 for (name, composite) in module.composites.iter() { 195 cursor.composites.insert( 196 to_absolute_path(*name), 197 composite 198 .members 199 .iter() 200 .map(|adl_ast::Member { identifier, type_, .. }| (identifier.name, type_.clone())) 201 .collect::<IndexMap<_, _>>(), 202 ); 203 } 204 205 for (name, const_declaration) in module.consts.iter() { 206 cursor.frames.push(Frame { 207 step: 0, 208 element: Element::Expression( 209 const_declaration.value.clone(), 210 Some(const_declaration.type_.clone()), 211 ), 212 user_initiated: false, 213 }); 214 cursor.over()?; 215 let value = cursor.values.pop().unwrap(); 216 cursor.globals.insert(Location::new(program, to_absolute_path(*name)), value); 217 } 218 } 219 } 220 221 for path in alpha_source_files { 222 let alpha_program = Self::get_alpha_program(path)?; 223 let program = snarkvm_identifier_to_symbol(alpha_program.id().name()); 224 filename_to_program.insert(path.to_path_buf(), program.to_string()); 225 226 for (name, struct_type) in alpha_program.structs().iter() { 227 cursor.composites.insert( 228 vec![snarkvm_identifier_to_symbol(name)], 229 struct_type 230 .members() 231 .iter() 232 .map(|(id, type_)| { 233 (adl_ast::Identifier::from(id).name, adl_ast::Type::from_snarkvm(type_, None)) 234 }) 235 .collect::<IndexMap<_, _>>(), 236 ); 237 } 238 239 for (name, record_type) in alpha_program.records().iter() { 240 use snarkvm::prelude::EntryType; 241 cursor.composites.insert( 242 vec![snarkvm_identifier_to_symbol(name)], 243 record_type 244 .entries() 245 .iter() 246 .map(|(id, entry)| { 247 // Destructure to get the inner type `t` directly 248 let t = match entry { 249 EntryType::Public(t) | EntryType::Private(t) | EntryType::Constant(t) => t, 250 }; 251 252 (adl_ast::Identifier::from(id).name, adl_ast::Type::from_snarkvm(t, None)) 253 }) 254 .collect::<IndexMap<_, _>>(), 255 ); 256 } 257 258 for (name, _mapping) in alpha_program.mappings().iter() { 259 cursor 260 .mappings 261 .insert(Location::new(program, vec![snarkvm_identifier_to_symbol(name)]), HashMap::new()); 262 } 263 264 for (name, function) in alpha_program.functions().iter() { 265 cursor.functions.insert( 266 Location::new(program, vec![snarkvm_identifier_to_symbol(name)]), 267 FunctionVariant::AleoFunction(function.clone()), 268 ); 269 } 270 271 for (name, closure) in alpha_program.closures().iter() { 272 cursor.functions.insert( 273 Location::new(program, vec![snarkvm_identifier_to_symbol(name)]), 274 FunctionVariant::AleoClosure(closure.clone()), 275 ); 276 } 277 } 278 279 Ok(Interpreter { 280 cursor, 281 handler, 282 node_builder, 283 actions: Vec::new(), 284 breakpoints: Vec::new(), 285 watchpoints: Vec::new(), 286 saved_cursors: Vec::new(), 287 filename_to_program, 288 parsed_inputs: 0, 289 network, 290 }) 291 } 292 293 pub fn save_cursor(&mut self) { 294 self.saved_cursors.push(self.cursor.clone()); 295 } 296 297 /// Returns false if there was no saved cursor to restore. 298 pub fn restore_cursor(&mut self) -> bool { 299 if let Some(old_cursor) = self.saved_cursors.pop() { 300 self.cursor = old_cursor; 301 true 302 } else { 303 false 304 } 305 } 306 307 fn get_alpha_program(path: &std::path::Path) -> Result<Program<TestnetV0>> { 308 let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?; 309 let program = text.parse()?; 310 Ok(program) 311 } 312 313 /// Returns true if any watchpoints changed. 314 pub fn update_watchpoints(&mut self) -> Result<bool> { 315 let mut changed = false; 316 let safe_cursor = self.cursor.clone(); 317 318 for i in 0..self.watchpoints.len() { 319 let code = self.watchpoints[i].code.clone(); 320 let new_value = match self.action(InterpreterAction::LeoInterpretOver(code)) { 321 Ok(None) => None, 322 Ok(Some(ret)) => Some(ret.to_string()), 323 Err(AdlError::InterpreterHalt(halt)) => { 324 self.cursor = safe_cursor.clone(); 325 Some(halt.to_string()) 326 } 327 Err(e) => return Err(e), 328 }; 329 if self.watchpoints[i].last_result != new_value { 330 changed = true; 331 self.watchpoints[i].last_result = new_value; 332 } 333 } 334 Ok(changed) 335 } 336 337 pub fn action(&mut self, act: InterpreterAction) -> Result<Option<Value>> { 338 use InterpreterAction::*; 339 340 let ret = match &act { 341 RunFuture(n) => { 342 let future = self.cursor.futures.remove(*n); 343 match future { 344 AsyncExecution::AsyncFunctionCall { function, arguments } => { 345 self.cursor.values.extend(arguments); 346 self.cursor.frames.push(Frame { 347 step: 0, 348 element: Element::DelayedCall(function), 349 user_initiated: true, 350 }); 351 } 352 AsyncExecution::AsyncBlock { containing_function, block, names, .. } => { 353 self.cursor.frames.push(Frame { 354 step: 0, 355 element: Element::DelayedAsyncBlock { 356 program: containing_function.program, 357 block, 358 // Keep track of all the known variables up to this point. 359 // These are available to use inside the block when we actually execute it. 360 names: names.clone().into_iter().collect(), 361 }, 362 user_initiated: false, 363 }); 364 } 365 } 366 self.cursor.step()? 367 } 368 LeoInterpretInto(s) | LeoInterpretOver(s) => { 369 let filename = FileName::Custom(format!("user_input{:04}", self.parsed_inputs)); 370 self.parsed_inputs += 1; 371 let source_file = with_session_globals(|globals| globals.source_map.new_source(s, filename)); 372 let s = s.trim(); 373 if s.ends_with(';') { 374 let statement = adl_parser::parse_statement( 375 self.handler.clone(), 376 &self.node_builder, 377 s, 378 source_file.absolute_start, 379 self.network, 380 ) 381 .map_err(|_e| { 382 AdlError::InterpreterHalt(InterpreterHalt::new("failed to parse statement".into())) 383 })?; 384 // The spans of the code the user wrote at the REPL are meaningless, so get rid of them. 385 self.cursor.frames.push(Frame { 386 step: 0, 387 element: Element::Statement(statement), 388 user_initiated: true, 389 }); 390 } else { 391 let expression = adl_parser::parse_expression( 392 self.handler.clone(), 393 &self.node_builder, 394 s, 395 source_file.absolute_start, 396 self.network, 397 ) 398 .map_err(|e| { 399 AdlError::InterpreterHalt(InterpreterHalt::new(format!("Failed to parse expression: {e}"))) 400 })?; 401 // The spans of the code the user wrote at the REPL are meaningless, so get rid of them. 402 self.cursor.frames.push(Frame { 403 step: 0, 404 element: Element::Expression(expression, None), 405 user_initiated: true, 406 }); 407 }; 408 409 if matches!(act, LeoInterpretOver(..)) { 410 self.cursor.over()? 411 } else { 412 StepResult { finished: false, value: None } 413 } 414 } 415 416 Step => self.cursor.whole_step()?, 417 418 Into => self.cursor.step()?, 419 420 Over => self.cursor.over()?, 421 422 Breakpoint(breakpoint) => { 423 self.breakpoints.push(breakpoint.clone()); 424 StepResult { finished: false, value: None } 425 } 426 427 Watch(code) => { 428 self.watchpoints.push(Watchpoint { code: code.clone(), last_result: None }); 429 StepResult { finished: false, value: None } 430 } 431 432 PrintRegister(register_index) => { 433 let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.cursor.frames.last() 434 else { 435 halt_no_span!("cannot print register - not currently interpreting Aleo VM code"); 436 }; 437 438 if let Some(value) = registers.get(register_index) { 439 StepResult { finished: false, value: Some(value.clone()) } 440 } else { 441 halt_no_span!("no such register {register_index}"); 442 } 443 } 444 445 Run => { 446 while !self.cursor.frames.is_empty() { 447 if let Some((program, line)) = self.current_program_and_line() 448 && self.breakpoints.iter().any(|bp| bp.program == program && bp.line == line) 449 { 450 return Ok(None); 451 } 452 self.cursor.step()?; 453 if self.update_watchpoints()? { 454 return Ok(None); 455 } 456 } 457 StepResult { finished: false, value: None } 458 } 459 }; 460 461 self.actions.push(act); 462 463 Ok(ret.value) 464 } 465 466 pub fn view_current(&self) -> Option<impl Display> { 467 if let Some(span) = self.current_span() 468 && span != Default::default() 469 { 470 return with_session_globals(|s| s.source_map.contents_of_span(span)); 471 } 472 473 Some(match &self.cursor.frames.last()?.element { 474 Element::Statement(statement) => format!("{statement}"), 475 Element::Expression(expression, _) => format!("{expression}"), 476 Element::Block { block, .. } => format!("{block}"), 477 Element::DelayedCall(gid) => format!("Delayed call to {gid}"), 478 Element::DelayedAsyncBlock { .. } => "Delayed async block".to_string(), 479 Element::AleoExecution { context, instruction_index, .. } => match &**context { 480 AleoContext::Closure(closure) => closure.instructions().get(*instruction_index).map(|i| format!("{i}")), 481 AleoContext::Function(function) => { 482 function.instructions().get(*instruction_index).map(|i| format!("{i}")) 483 } 484 AleoContext::Finalize(finalize) => finalize.commands().get(*instruction_index).map(|i| format!("{i}")), 485 } 486 .unwrap_or_else(|| "...".to_string()), 487 }) 488 } 489 490 pub fn view_current_in_context(&self) -> Option<(impl Display, usize, usize)> { 491 if let Some(Frame { element: Element::AleoExecution { context, instruction_index, .. }, .. }) = 492 self.cursor.frames.last() 493 { 494 // For Aleo VM code, there are no spans; just print out the code without referring to the source code. 495 496 fn write_all<I: Display>( 497 items: impl Iterator<Item = I>, 498 instruction_index: usize, 499 result: &mut String, 500 start: &mut usize, 501 stop: &mut usize, 502 ) { 503 for (i, item) in items.enumerate() { 504 if i == instruction_index { 505 *start = result.len(); 506 } 507 writeln!(result, " {item}").expect("write shouldn't fail"); 508 if i == instruction_index { 509 *stop = result.len(); 510 } 511 } 512 } 513 514 let mut result = String::new(); 515 let mut start: usize = 0usize; 516 let mut stop: usize = 0usize; 517 518 match &**context { 519 AleoContext::Closure(closure) => { 520 writeln!(&mut result, "closure {}", closure.name()).expect("write shouldn't fail"); 521 write_all(closure.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); 522 write_all(closure.instructions().iter(), *instruction_index, &mut result, &mut start, &mut stop); 523 write_all(closure.outputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); 524 } 525 AleoContext::Function(function) => { 526 writeln!(&mut result, "function {}", function.name()).expect("write shouldn't fail"); 527 write_all(function.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); 528 write_all(function.instructions().iter(), *instruction_index, &mut result, &mut start, &mut stop); 529 write_all(function.outputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); 530 } 531 AleoContext::Finalize(finalize) => { 532 writeln!(&mut result, "finalize {}", finalize.name()).expect("write shouldn't fail"); 533 write_all(finalize.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); 534 write_all(finalize.commands().iter(), *instruction_index, &mut result, &mut start, &mut stop); 535 } 536 } 537 538 Some((result, start, stop)) 539 } else { 540 // For Leo code, we use spans to print the original source code. 541 let span = self.current_span()?; 542 if span == Default::default() { 543 return None; 544 } 545 with_session_globals(|s| { 546 let source_file = s.source_map.find_source_file(span.lo)?; 547 let first_span = Span::new(source_file.absolute_start, span.lo); 548 let last_span = Span::new(span.hi, source_file.absolute_end); 549 let mut result = String::new(); 550 result.push_str(&s.source_map.contents_of_span(first_span)?); 551 let start = result.len(); 552 result.push_str(&s.source_map.contents_of_span(span)?); 553 let stop = result.len(); 554 result.push_str(&s.source_map.contents_of_span(last_span)?); 555 Some((result, start, stop)) 556 }) 557 } 558 } 559 560 fn current_program_and_line(&self) -> Option<(String, usize)> { 561 if let Some(span) = self.current_span() 562 && let Some(source_file) = with_session_globals(|s| s.source_map.find_source_file(span.lo)) 563 { 564 let (line, _) = source_file.line_col(span.lo); 565 if let FileName::Real(name) = &source_file.name 566 && let Some(program) = self.filename_to_program.get(name) 567 { 568 return Some((program.clone(), line as usize + 1)); 569 } 570 } 571 None 572 } 573 574 fn current_span(&self) -> Option<Span> { 575 self.cursor.frames.last().map(|f| f.element.span()) 576 } 577 }