From fc08264c727504f6dcc8c0e4f7c61e1773212fdd Mon Sep 17 00:00:00 2001 From: Michael Benfield Date: Fri, 22 Nov 2024 10:32:24 -0800 Subject: [PATCH] Interpreter struct into separate file --- interpreter/src/interpreter.rs | 374 +++++++++++++++++++++++++++++++++ interpreter/src/lib.rs | 360 +------------------------------ 2 files changed, 378 insertions(+), 356 deletions(-) create mode 100644 interpreter/src/interpreter.rs diff --git a/interpreter/src/interpreter.rs b/interpreter/src/interpreter.rs new file mode 100644 index 0000000000..75abf81ada --- /dev/null +++ b/interpreter/src/interpreter.rs @@ -0,0 +1,374 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::*; + +use leo_errors::{CompilerError, InterpreterHalt, LeoError, Result, emitter::Handler}; + +/// Contains the state of interpretation, in the form of the `Cursor`, +/// as well as information needed to interact with the user, like +/// the breakpoints. +pub struct Interpreter { + pub cursor: Cursor<'static>, + actions: Vec, + handler: Handler, + node_builder: NodeBuilder, + breakpoints: Vec, + filename_to_program: HashMap, +} + +#[derive(Clone, Debug)] +pub struct Breakpoint { + pub program: String, + pub line: usize, +} + +#[derive(Clone, Debug)] +pub enum InterpreterAction { + LeoInterpretInto(String), + LeoInterpretOver(String), + RunFuture(usize), + Breakpoint(Breakpoint), + PrintRegister(u64), + Into, + Over, + Step, + Run, +} + +impl Interpreter { + pub fn new<'a, P: 'a + AsRef, Q: 'a + AsRef>( + leo_source_files: impl IntoIterator, + aleo_source_files: impl IntoIterator, + signer: SvmAddress, + block_height: u32, + ) -> Result { + Self::new_impl( + &mut leo_source_files.into_iter().map(|p| p.as_ref()), + &mut aleo_source_files.into_iter().map(|p| p.as_ref()), + signer, + block_height, + ) + } + + fn get_ast(path: &Path, handler: &Handler, node_builder: &NodeBuilder) -> Result { + let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?; + let filename = FileName::Real(path.to_path_buf()); + let source_file = with_session_globals(|s| s.source_map.new_source(&text, filename)); + leo_parser::parse_ast::(handler, node_builder, &text, source_file.start_pos) + } + + fn new_impl( + leo_source_files: &mut dyn Iterator, + aleo_source_files: &mut dyn Iterator, + signer: SvmAddress, + block_height: u32, + ) -> Result { + let handler = Handler::default(); + let node_builder = Default::default(); + let mut cursor: Cursor<'_> = Cursor::new( + true, // really_async + signer, + block_height, + ); + let mut filename_to_program = HashMap::new(); + for path in leo_source_files { + let ast = Self::get_ast(path, &handler, &node_builder)?; + // TODO: This leak is silly. + let ast = Box::leak(Box::new(ast)); + for (&program, scope) in ast.ast.program_scopes.iter() { + filename_to_program.insert(path.to_path_buf(), program.to_string()); + for (name, function) in scope.functions.iter() { + cursor.functions.insert(GlobalId { program, name: *name }, FunctionVariant::Leo(function)); + } + + for (name, composite) in scope.structs.iter() { + cursor.structs.insert( + GlobalId { program, name: *name }, + composite.members.iter().map(|member| member.identifier.name).collect(), + ); + } + + for (name, _mapping) in scope.mappings.iter() { + cursor.mappings.insert(GlobalId { program, name: *name }, HashMap::new()); + } + + for (name, const_declaration) in scope.consts.iter() { + cursor.frames.push(Frame { + step: 0, + element: Element::Expression(&const_declaration.value), + user_initiated: false, + }); + cursor.over()?; + let value = cursor.values.pop().unwrap(); + cursor.globals.insert(GlobalId { program, name: *name }, value); + } + } + } + + for path in aleo_source_files { + let aleo_program = Self::get_aleo_program(path)?; + // TODO: Another goofy leak. + let aleo_program = Box::leak(Box::new(aleo_program)); + let program = snarkvm_identifier_to_symbol(aleo_program.id().name()); + filename_to_program.insert(path.to_path_buf(), program.to_string()); + + for (name, struct_type) in aleo_program.structs().iter() { + cursor.structs.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + struct_type.members().keys().map(snarkvm_identifier_to_symbol).collect(), + ); + } + + for (name, record_type) in aleo_program.records().iter() { + cursor.structs.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + record_type.entries().keys().map(snarkvm_identifier_to_symbol).collect(), + ); + } + + for (name, _mapping) in aleo_program.mappings().iter() { + cursor.mappings.insert(GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, HashMap::new()); + } + + for (name, function) in aleo_program.functions().iter() { + cursor.functions.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + FunctionVariant::AleoFunction(function), + ); + } + + for (name, closure) in aleo_program.closures().iter() { + cursor.functions.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + FunctionVariant::AleoClosure(closure), + ); + } + } + + Ok(Interpreter { + cursor, + handler, + node_builder, + actions: Vec::new(), + breakpoints: Vec::new(), + filename_to_program, + }) + } + + fn get_aleo_program(path: &Path) -> Result> { + let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?; + let program = text.parse()?; + Ok(program) + } + + pub fn action(&mut self, act: InterpreterAction) -> Result> { + use InterpreterAction::*; + + let ret = match &act { + RunFuture(n) => { + let future = self.cursor.futures.remove(*n); + for async_exec in future.0.into_iter().rev() { + self.cursor.values.extend(async_exec.arguments); + self.cursor.frames.push(Frame { + step: 0, + element: Element::DelayedCall(async_exec.function), + user_initiated: true, + }); + } + self.cursor.step()? + } + LeoInterpretInto(s) | LeoInterpretOver(s) => { + let s = s.trim(); + if s.ends_with(';') { + let statement = leo_parser::parse_statement::(&self.handler, &self.node_builder, s) + .map_err(|_e| { + LeoError::InterpreterHalt(InterpreterHalt::new("failed to parse statement".into())) + })?; + // TODO: This leak is silly. + let stmt = Box::leak(Box::new(statement)); + + // The spans of the code the user wrote at the REPL are meaningless, so get rid of them. + kill_span_statement(stmt); + self.cursor.frames.push(Frame { step: 0, element: Element::Statement(stmt), user_initiated: true }); + } else { + let expression = leo_parser::parse_expression::(&self.handler, &self.node_builder, s) + .map_err(|_e| { + LeoError::InterpreterHalt(InterpreterHalt::new("failed to parse expression".into())) + })?; + // TODO: This leak is silly. + let expr = Box::leak(Box::new(expression)); + + // The spans of the code the user wrote at the REPL are meaningless, so get rid of them. + kill_span_expression(expr); + self.cursor.frames.push(Frame { + step: 0, + element: Element::Expression(expr), + user_initiated: true, + }); + }; + + if matches!(act, LeoInterpretOver(..)) { + self.cursor.over()? + } else { + StepResult { finished: false, value: None } + } + } + + Step => self.cursor.whole_step()?, + + Into => self.cursor.step()?, + + Over => self.cursor.over()?, + + Breakpoint(breakpoint) => { + self.breakpoints.push(breakpoint.clone()); + StepResult { finished: false, value: None } + } + + PrintRegister(register_index) => { + let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.cursor.frames.last() + else { + halt_no_span!("cannot print register - not currently interpreting Aleo VM code"); + }; + + if let Some(value) = registers.get(register_index) { + StepResult { finished: false, value: Some(value.clone()) } + } else { + halt_no_span!("no such register {register_index}"); + } + } + + Run => { + while !self.cursor.frames.is_empty() { + if let Some((program, line)) = self.current_program_and_line() { + if self.breakpoints.iter().any(|bp| bp.program == program && bp.line == line) { + return Ok(None); + } + } + self.cursor.step()?; + } + StepResult { finished: false, value: None } + } + }; + + self.actions.push(act); + + Ok(ret.value) + } + + pub fn view_current(&self) -> Option { + if let Some(span) = self.current_span() { + if span != Default::default() { + return with_session_globals(|s| s.source_map.contents_of_span(span)); + } + } + + Some(match self.cursor.frames.last()?.element { + Element::Statement(statement) => format!("{statement}"), + Element::Expression(expression) => format!("{expression}"), + Element::Block { block, .. } => format!("{block}"), + Element::DelayedCall(gid) => format!("Delayed call to {gid}"), + Element::AleoExecution { context, instruction_index, .. } => match context { + AleoContext::Closure(closure) => closure.instructions().get(instruction_index).map(|i| format!("{i}")), + AleoContext::Function(function) => { + function.instructions().get(instruction_index).map(|i| format!("{i}")) + } + AleoContext::Finalize(finalize) => finalize.commands().get(instruction_index).map(|i| format!("{i}")), + } + .unwrap_or_else(|| "...".to_string()), + }) + } + + pub fn view_current_in_context(&self) -> Option { + if let Some(Frame { element: Element::AleoExecution { context, instruction_index, .. }, .. }) = + self.cursor.frames.last() + { + // For Aleo VM code, there are no spans; just print out the code without referring to the source code. + + fn write_all(items: impl Iterator, instruction_index: usize) -> String { + let mut result = String::new(); + for (i, item) in items.enumerate() { + if i == instruction_index { + let temp = format!(" {item}").red(); + writeln!(&mut result, "{temp}").expect("write"); + } else { + writeln!(&mut result, " {item}").expect("write"); + } + } + result + } + + let (heading, inputs, instructions, outputs) = match context { + AleoContext::Closure(closure) => ( + format!("closure {}\n", closure.name()), + write_all(closure.inputs().iter(), usize::MAX), + write_all(closure.instructions().iter(), *instruction_index), + write_all(closure.outputs().iter(), usize::MAX), + ), + AleoContext::Function(function) => ( + format!("function {}\n", function.name()), + write_all(function.inputs().iter(), usize::MAX), + write_all(function.instructions().iter(), *instruction_index), + write_all(function.outputs().iter(), usize::MAX), + ), + AleoContext::Finalize(finalize) => ( + format!("finalize {}\n", finalize.name()), + write_all(finalize.inputs().iter(), usize::MAX), + write_all(finalize.commands().iter(), *instruction_index), + "".to_string(), + ), + }; + + Some(format!("{heading}{inputs}{instructions}{outputs}")) + } else { + // For Leo code, we use spans to print the original source code. + let span = self.current_span()?; + if span == Default::default() { + return None; + } + with_session_globals(|s| { + let source_file = s.source_map.find_source_file(span.lo)?; + let first_span = Span::new(source_file.start_pos, span.lo); + let last_span = Span::new(span.hi, source_file.end_pos); + Some(format!( + "{}{}{}", + s.source_map.contents_of_span(first_span)?, + s.source_map.contents_of_span(span)?.red(), + s.source_map.contents_of_span(last_span)?, + )) + }) + } + } + + fn current_program_and_line(&self) -> Option<(String, usize)> { + if let Some(span) = self.current_span() { + if let Some(location) = with_session_globals(|s| s.source_map.span_to_location(span)) { + let line = location.line_start; + if let FileName::Real(name) = &location.source_file.name { + if let Some(program) = self.filename_to_program.get(name) { + return Some((program.clone(), line)); + } + } + } + } + None + } + + fn current_span(&self) -> Option { + self.cursor.frames.last().map(|f| f.element.span()) + } +} diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs index de188c29d5..dd64c6c6d8 100644 --- a/interpreter/src/lib.rs +++ b/interpreter/src/lib.rs @@ -15,7 +15,7 @@ // along with the Leo library. If not, see . use leo_ast::{AccessExpression, AssertVariant, Ast, ConsoleFunction, Expression, Node as _, NodeBuilder, Statement}; -use leo_errors::{CompilerError, InterpreterHalt, LeoError, Result, emitter::Handler}; +use leo_errors::{InterpreterHalt, LeoError, Result}; use leo_span::{Span, source_map::FileName, symbol::with_session_globals}; use snarkvm::prelude::{Program, TestnetV0}; @@ -34,366 +34,14 @@ use util::*; mod cursor; use cursor::*; +mod interpreter; +use interpreter::*; + mod cursor_aleo; mod value; use value::*; -/// Contains the state of interpretation, in the form of the `Cursor`, -/// as well as information needed to interact with the user, like -/// the breakpoints. -struct Interpreter { - cursor: Cursor<'static>, - actions: Vec, - handler: Handler, - node_builder: NodeBuilder, - breakpoints: Vec, - filename_to_program: HashMap, -} - -#[derive(Clone, Debug)] -struct Breakpoint { - program: String, - line: usize, -} - -#[derive(Clone, Debug)] -enum InterpreterAction { - LeoInterpretInto(String), - LeoInterpretOver(String), - RunFuture(usize), - Breakpoint(Breakpoint), - PrintRegister(u64), - Into, - Over, - Step, - Run, -} - -impl Interpreter { - fn new<'a, P: 'a + AsRef, Q: 'a + AsRef>( - leo_source_files: impl IntoIterator, - aleo_source_files: impl IntoIterator, - signer: SvmAddress, - block_height: u32, - ) -> Result { - Self::new_impl( - &mut leo_source_files.into_iter().map(|p| p.as_ref()), - &mut aleo_source_files.into_iter().map(|p| p.as_ref()), - signer, - block_height, - ) - } - - fn get_ast(path: &Path, handler: &Handler, node_builder: &NodeBuilder) -> Result { - let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?; - let filename = FileName::Real(path.to_path_buf()); - let source_file = with_session_globals(|s| s.source_map.new_source(&text, filename)); - leo_parser::parse_ast::(handler, node_builder, &text, source_file.start_pos) - } - - fn new_impl( - leo_source_files: &mut dyn Iterator, - aleo_source_files: &mut dyn Iterator, - signer: SvmAddress, - block_height: u32, - ) -> Result { - let handler = Handler::default(); - let node_builder = Default::default(); - let mut cursor: Cursor<'_> = Cursor::new( - true, // really_async - signer, - block_height, - ); - let mut filename_to_program = HashMap::new(); - for path in leo_source_files { - let ast = Self::get_ast(path, &handler, &node_builder)?; - // TODO: This leak is silly. - let ast = Box::leak(Box::new(ast)); - for (&program, scope) in ast.ast.program_scopes.iter() { - filename_to_program.insert(path.to_path_buf(), program.to_string()); - for (name, function) in scope.functions.iter() { - cursor.functions.insert(GlobalId { program, name: *name }, FunctionVariant::Leo(function)); - } - - for (name, composite) in scope.structs.iter() { - cursor.structs.insert( - GlobalId { program, name: *name }, - composite.members.iter().map(|member| member.identifier.name).collect(), - ); - } - - for (name, _mapping) in scope.mappings.iter() { - cursor.mappings.insert(GlobalId { program, name: *name }, HashMap::new()); - } - - for (name, const_declaration) in scope.consts.iter() { - cursor.frames.push(Frame { - step: 0, - element: Element::Expression(&const_declaration.value), - user_initiated: false, - }); - cursor.over()?; - let value = cursor.values.pop().unwrap(); - cursor.globals.insert(GlobalId { program, name: *name }, value); - } - } - } - - for path in aleo_source_files { - let aleo_program = Self::get_aleo_program(path)?; - // TODO: Another goofy leak. - let aleo_program = Box::leak(Box::new(aleo_program)); - let program = snarkvm_identifier_to_symbol(aleo_program.id().name()); - filename_to_program.insert(path.to_path_buf(), program.to_string()); - - for (name, struct_type) in aleo_program.structs().iter() { - cursor.structs.insert( - GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, - struct_type.members().keys().map(snarkvm_identifier_to_symbol).collect(), - ); - } - - for (name, record_type) in aleo_program.records().iter() { - cursor.structs.insert( - GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, - record_type.entries().keys().map(snarkvm_identifier_to_symbol).collect(), - ); - } - - for (name, _mapping) in aleo_program.mappings().iter() { - cursor.mappings.insert(GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, HashMap::new()); - } - - for (name, function) in aleo_program.functions().iter() { - cursor.functions.insert( - GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, - FunctionVariant::AleoFunction(function), - ); - } - - for (name, closure) in aleo_program.closures().iter() { - cursor.functions.insert( - GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, - FunctionVariant::AleoClosure(closure), - ); - } - } - - Ok(Interpreter { - cursor, - handler, - node_builder, - actions: Vec::new(), - breakpoints: Vec::new(), - filename_to_program, - }) - } - - fn get_aleo_program(path: &Path) -> Result> { - let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?; - let program = text.parse()?; - Ok(program) - } - - fn action(&mut self, act: InterpreterAction) -> Result> { - use InterpreterAction::*; - - let ret = match &act { - RunFuture(n) => { - let future = self.cursor.futures.remove(*n); - for async_exec in future.0.into_iter().rev() { - self.cursor.values.extend(async_exec.arguments); - self.cursor.frames.push(Frame { - step: 0, - element: Element::DelayedCall(async_exec.function), - user_initiated: true, - }); - } - self.cursor.step()? - } - LeoInterpretInto(s) | LeoInterpretOver(s) => { - let s = s.trim(); - if s.ends_with(';') { - let statement = leo_parser::parse_statement::(&self.handler, &self.node_builder, s) - .map_err(|_e| { - LeoError::InterpreterHalt(InterpreterHalt::new("failed to parse statement".into())) - })?; - // TODO: This leak is silly. - let stmt = Box::leak(Box::new(statement)); - - // The spans of the code the user wrote at the REPL are meaningless, so get rid of them. - kill_span_statement(stmt); - self.cursor.frames.push(Frame { step: 0, element: Element::Statement(stmt), user_initiated: true }); - } else { - let expression = leo_parser::parse_expression::(&self.handler, &self.node_builder, s) - .map_err(|_e| { - LeoError::InterpreterHalt(InterpreterHalt::new("failed to parse expression".into())) - })?; - // TODO: This leak is silly. - let expr = Box::leak(Box::new(expression)); - - // The spans of the code the user wrote at the REPL are meaningless, so get rid of them. - kill_span_expression(expr); - self.cursor.frames.push(Frame { - step: 0, - element: Element::Expression(expr), - user_initiated: true, - }); - }; - - if matches!(act, LeoInterpretOver(..)) { - self.cursor.over()? - } else { - StepResult { finished: false, value: None } - } - } - - Step => self.cursor.whole_step()?, - - Into => self.cursor.step()?, - - Over => self.cursor.over()?, - - Breakpoint(breakpoint) => { - self.breakpoints.push(breakpoint.clone()); - StepResult { finished: false, value: None } - } - - PrintRegister(register_index) => { - let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.cursor.frames.last() - else { - halt_no_span!("cannot print register - not currently interpreting Aleo VM code"); - }; - - if let Some(value) = registers.get(register_index) { - StepResult { finished: false, value: Some(value.clone()) } - } else { - halt_no_span!("no such register {register_index}"); - } - } - - Run => { - while !self.cursor.frames.is_empty() { - if let Some((program, line)) = self.current_program_and_line() { - if self.breakpoints.iter().any(|bp| bp.program == program && bp.line == line) { - return Ok(None); - } - } - self.cursor.step()?; - } - StepResult { finished: false, value: None } - } - }; - - self.actions.push(act); - - Ok(ret.value) - } - - fn view_current(&self) -> Option { - if let Some(span) = self.current_span() { - if span != Default::default() { - return with_session_globals(|s| s.source_map.contents_of_span(span)); - } - } - - Some(match self.cursor.frames.last()?.element { - Element::Statement(statement) => format!("{statement}"), - Element::Expression(expression) => format!("{expression}"), - Element::Block { block, .. } => format!("{block}"), - Element::DelayedCall(gid) => format!("Delayed call to {gid}"), - Element::AleoExecution { context, instruction_index, .. } => match context { - AleoContext::Closure(closure) => closure.instructions().get(instruction_index).map(|i| format!("{i}")), - AleoContext::Function(function) => { - function.instructions().get(instruction_index).map(|i| format!("{i}")) - } - AleoContext::Finalize(finalize) => finalize.commands().get(instruction_index).map(|i| format!("{i}")), - } - .unwrap_or_else(|| "...".to_string()), - }) - } - - fn view_current_in_context(&self) -> Option { - if let Some(Frame { element: Element::AleoExecution { context, instruction_index, .. }, .. }) = - self.cursor.frames.last() - { - // For Aleo VM code, there are no spans; just print out the code without referring to the source code. - - fn write_all(items: impl Iterator, instruction_index: usize) -> String { - let mut result = String::new(); - for (i, item) in items.enumerate() { - if i == instruction_index { - let temp = format!(" {item}").red(); - writeln!(&mut result, "{temp}").expect("write"); - } else { - writeln!(&mut result, " {item}").expect("write"); - } - } - result - } - - let (heading, inputs, instructions, outputs) = match context { - AleoContext::Closure(closure) => ( - format!("closure {}\n", closure.name()), - write_all(closure.inputs().iter(), usize::MAX), - write_all(closure.instructions().iter(), *instruction_index), - write_all(closure.outputs().iter(), usize::MAX), - ), - AleoContext::Function(function) => ( - format!("function {}\n", function.name()), - write_all(function.inputs().iter(), usize::MAX), - write_all(function.instructions().iter(), *instruction_index), - write_all(function.outputs().iter(), usize::MAX), - ), - AleoContext::Finalize(finalize) => ( - format!("finalize {}\n", finalize.name()), - write_all(finalize.inputs().iter(), usize::MAX), - write_all(finalize.commands().iter(), *instruction_index), - "".to_string(), - ), - }; - - Some(format!("{heading}{inputs}{instructions}{outputs}")) - } else { - // For Leo code, we use spans to print the original source code. - let span = self.current_span()?; - if span == Default::default() { - return None; - } - with_session_globals(|s| { - let source_file = s.source_map.find_source_file(span.lo)?; - let first_span = Span::new(source_file.start_pos, span.lo); - let last_span = Span::new(span.hi, source_file.end_pos); - Some(format!( - "{}{}{}", - s.source_map.contents_of_span(first_span)?, - s.source_map.contents_of_span(span)?.red(), - s.source_map.contents_of_span(last_span)?, - )) - }) - } - } - - fn current_program_and_line(&self) -> Option<(String, usize)> { - if let Some(span) = self.current_span() { - if let Some(location) = with_session_globals(|s| s.source_map.span_to_location(span)) { - let line = location.line_start; - if let FileName::Real(name) = &location.source_file.name { - if let Some(program) = self.filename_to_program.get(name) { - return Some((program.clone(), line)); - } - } - } - } - None - } - - fn current_span(&self) -> Option { - self.cursor.frames.last().map(|f| f.element.span()) - } -} - const INTRO: &str = "This is the Leo Interpreter. Try the command `#help`."; const HELP: &str = "