Interpreter.

This commit is contained in:
Michael Benfield 2024-10-23 14:29:23 -07:00
parent bac534fed4
commit f2c016782f
12 changed files with 3255 additions and 2 deletions

16
Cargo.lock generated
View File

@ -1561,6 +1561,21 @@ dependencies = [
"thiserror",
]
[[package]]
name = "leo-interpreter"
version = "2.3.0"
dependencies = [
"colored",
"indexmap 2.6.0",
"leo-ast",
"leo-errors",
"leo-parser",
"leo-passes",
"leo-span",
"snarkvm",
"snarkvm-circuit",
]
[[package]]
name = "leo-lang"
version = "2.3.1"
@ -1577,6 +1592,7 @@ dependencies = [
"leo-ast",
"leo-compiler",
"leo-errors",
"leo-interpreter",
"leo-package",
"leo-retriever",
"leo-span",

View File

@ -38,6 +38,7 @@ members = [
"compiler/span",
"docs/grammar",
"errors",
"interpreter",
"leo/package",
"tests/test-framework",
"utils/disassembler",
@ -60,6 +61,10 @@ version = "2.3.1"
path = "./errors"
version = "2.3.1"
[workspace.dependencies.leo-interpreter]
path = "./interpreter"
version = "2.3.0"
[workspace.dependencies.leo-package]
path = "./leo/package"
version = "2.3.1"
@ -84,6 +89,9 @@ version = "2.3.1"
version = "0.1.24"
default-features = false
[workspace.dependencies.colored]
version = "2.0"
[workspace.dependencies.indexmap]
version = "2.6"
features = [ "serde" ]
@ -99,6 +107,7 @@ default-features = false
version = "1.11.1"
[workspace.dependencies.snarkvm]
# path = "../SnarkVM"
version = "1.0.0"
[workspace.dependencies.serde]
@ -145,6 +154,9 @@ workspace = true
[dependencies.leo-errors]
workspace = true
[dependencies.leo-interpreter]
workspace = true
[dependencies.leo-package]
workspace = true
@ -165,6 +177,7 @@ version = "4.5"
features = [ "derive", "env", "color", "unstable-styles" ]
[dependencies.colored]
workspace = true
version = "2.0"
[dependencies.dotenvy]

View File

@ -22,7 +22,7 @@
use crate::{Token, tokenizer::*};
use leo_ast::*;
use leo_errors::{Result, emitter::Handler};
use leo_errors::{ParserError, Result, emitter::Handler};
use leo_span::{Span, span::BytePos};
use snarkvm::prelude::Network;
@ -49,3 +49,25 @@ pub fn parse<N: Network>(
tokens.parse_program()
}
pub fn parse_expression<N: Network>(handler: &Handler, node_builder: &NodeBuilder, source: &str) -> Result<Expression> {
let mut context = ParserContext::<N>::new(handler, node_builder, crate::tokenize(source, BytePos(0))?);
let expression = context.parse_expression()?;
if context.token.token == Token::Eof {
Ok(expression)
} else {
Err(ParserError::unexpected(context.token.token, Token::Eof, context.token.span).into())
}
}
pub fn parse_statement<N: Network>(handler: &Handler, node_builder: &NodeBuilder, source: &str) -> Result<Statement> {
let mut context = ParserContext::<N>::new(handler, node_builder, crate::tokenize(source, BytePos(0))?);
let statement = context.parse_statement()?;
if context.token.token == Token::Eof {
Ok(statement)
} else {
Err(ParserError::unexpected(context.token.token, Token::Eof, context.token.span).into())
}
}

View File

@ -83,7 +83,7 @@ impl SourceMap {
}
/// Find the source file containing `pos`.
fn find_source_file(&self, pos: BytePos) -> Option<Rc<SourceFile>> {
pub fn find_source_file(&self, pos: BytePos) -> Option<Rc<SourceFile>> {
Some(self.inner.borrow().source_files[self.find_source_file_index(pos)?].clone())
}

View File

@ -0,0 +1,50 @@
// 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 <https://www.gnu.org/licenses/>.
use std::fmt;
use leo_span::Span;
/// Represents the interpreter halting, which should not be considered an
/// actual runtime error.
#[derive(Clone, Debug, Error)]
pub struct InterpreterHalt {
/// Optional Span where the halt occurred.
span: Option<Span>,
/// User visible message.
message: String,
}
impl InterpreterHalt {
pub fn new(message: String) -> Self {
InterpreterHalt { span: None, message }
}
pub fn new_spanned(message: String, span: Span) -> Self {
InterpreterHalt { span: Some(span), message }
}
pub fn span(&self) -> Option<Span> {
self.span
}
}
impl fmt::Display for InterpreterHalt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}

View File

@ -37,6 +37,9 @@ pub use self::flattener::*;
pub mod loop_unroller;
pub use self::loop_unroller::*;
pub mod interpreter_halt;
pub use self::interpreter_halt::*;
/// Contains the Package error definitions.
pub mod package;
pub use self::package::*;
@ -70,6 +73,8 @@ pub enum LeoError {
/// Represents a Compiler Error in a Leo Error.
#[error(transparent)]
CompilerError(#[from] CompilerError),
#[error(transparent)]
InterpreterHalt(#[from] InterpreterHalt),
/// Represents a Package Error in a Leo Error.
#[error(transparent)]
PackageError(#[from] PackageError),
@ -118,6 +123,7 @@ impl LeoError {
UtilError(error) => error.error_code(),
LastErrorCode(_) => unreachable!(),
Anyhow(_) => "SnarkVM Error".to_string(), // todo: implement error codes for snarkvm errors.
InterpreterHalt(_) => "Interpreter Halt".to_string(),
}
}
@ -138,6 +144,7 @@ impl LeoError {
UtilError(error) => error.exit_code(),
LastErrorCode(code) => *code,
Anyhow(_) => 11000, // todo: implement exit codes for snarkvm errors.
InterpreterHalt(_) => 1,
}
}
}

46
interpreter/Cargo.toml Normal file
View File

@ -0,0 +1,46 @@
[package]
name = "leo-interpreter"
version = "2.3.0"
authors = [ "The Leo Team <leo@provable.com>" ]
description = "Interpreter for the Leo programming language"
homepage = "https://leo-lang.org"
repository = "https://github.com/ProvableHQ/leo"
keywords = [
"aleo",
"cryptography",
"leo",
"programming-language",
"zero-knowledge"
]
categories = [ "compilers", "cryptography", "web-programming" ]
include = [ "Cargo.toml", "src", "README.md", "LICENSE.md" ]
license = "GPL-3.0"
edition = "2021"
rust-version = "1.69"
[dependencies.snarkvm-circuit]
version = "1.0.0"
[dependencies.snarkvm]
workspace = true
[dependencies.leo-ast]
workspace = true
[dependencies.leo-passes]
workspace = true
[dependencies.leo-errors]
workspace = true
[dependencies.leo-parser]
workspace = true
[dependencies.leo-span]
workspace = true
[dependencies.colored]
workspace = true
[dependencies.indexmap]
workspace = true

2778
interpreter/src/cursor.rs Normal file

File diff suppressed because it is too large Load Diff

270
interpreter/src/lib.rs Normal file
View File

@ -0,0 +1,270 @@
// 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 <https://www.gnu.org/licenses/>.
use std::{collections::HashMap, fmt::Display, fs, path::Path};
use colored::*;
use snarkvm::prelude::TestnetV0;
use leo_ast::{Ast, Node as _, NodeBuilder};
use leo_passes::{Pass as _, SymbolTableCreator, TypeChecker, TypeTable};
use leo_span::{Span, source_map::FileName, symbol::with_session_globals};
use leo_errors::{CompilerError, InterpreterHalt, LeoError, Result, emitter::Handler};
mod cursor;
use cursor::*;
pub struct Interpreter {
cursor: Cursor<'static>,
cursor_initial: Cursor<'static>,
actions: Vec<InterpreterAction>,
handler: Handler,
node_builder: NodeBuilder,
breakpoints: Vec<Breakpoint>,
}
#[derive(Clone, Debug)]
pub struct Breakpoint {
program: String,
line: usize,
}
#[derive(Clone, Debug)]
pub enum InterpreterAction {
LeoInterpretInto(String),
LeoInterpretOver(String),
Into,
Over,
Step,
Breakpoint(Breakpoint),
Run,
}
impl Interpreter {
pub fn new<'a, P: 'a + AsRef<Path>>(source_files: impl IntoIterator<Item = &'a P>) -> Result<Self> {
Self::new_impl(&mut source_files.into_iter().map(|p| p.as_ref()))
}
fn get_ast(path: &Path, handler: &Handler, node_builder: &NodeBuilder) -> Result<Ast> {
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::<TestnetV0>(handler, node_builder, &text, source_file.start_pos)
}
fn new_impl(source_files: &mut dyn Iterator<Item = &Path>) -> Result<Self> {
let handler = Handler::default();
let node_builder = Default::default();
let mut cursor: Cursor<'_> = Cursor::default();
for path in source_files {
let ast = Self::get_ast(path, &handler, &node_builder)?;
let symbol_table = SymbolTableCreator::do_pass((&ast, &handler))?;
let type_table = TypeTable::default();
TypeChecker::<TestnetV0>::do_pass((
&ast,
&handler,
symbol_table,
&type_table,
10, // conditional_block_max_depth
false, // disable_conditional_branch_type_checking
))?;
// TODO: This leak is silly.
let ast = Box::leak(Box::new(ast));
for (&program, scope) in ast.ast.program_scopes.iter() {
for (name, function) in scope.functions.iter() {
cursor.functions.insert(GlobalId { program, name: *name }, 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.structs.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);
}
}
}
let cursor_initial = cursor.clone();
Ok(Interpreter { cursor, cursor_initial, handler, node_builder, actions: Vec::new(), breakpoints: Vec::new() })
}
fn action(&mut self, act: InterpreterAction) -> Result<Option<Value>> {
use InterpreterAction::*;
let ret = match &act {
LeoInterpretInto(s) | LeoInterpretOver(s) => {
let s = s.trim();
if s.ends_with(';') {
let statement = leo_parser::parse_statement::<TestnetV0>(&self.handler, &self.node_builder, s)
.map_err(|_e| {
LeoError::InterpreterHalt(InterpreterHalt::new("failed to parse statement".into()))
})?;
// TOOD: This leak is silly.
let stmt = Box::leak(Box::new(statement));
self.cursor.frames.push(Frame { step: 0, element: Element::Statement(stmt), user_initiated: true });
} else {
let expression = leo_parser::parse_expression::<TestnetV0>(&self.handler, &self.node_builder, s)
.map_err(|_e| {
LeoError::InterpreterHalt(InterpreterHalt::new("failed to parse expression".into()))
})?;
// TOOD: This leak is silly.
let expr = Box::leak(Box::new(expression));
expr.set_span(Default::default());
self.cursor.frames.push(Frame {
step: 0,
element: Element::Expression(expr),
user_initiated: true,
});
};
if matches!(act, LeoInterpretOver(..)) { self.cursor.over()? } else { self.cursor.step()? }
}
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 }
}
Run => {
while !self.cursor.frames.is_empty() {
self.cursor.step()?;
}
StepResult { finished: false, value: None }
}
};
self.actions.push(act);
Ok(ret.value)
}
pub fn view_current(&self) -> Option<impl Display> {
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::Empty => tc_fail!(),
})
}
pub fn view_current_in_context(&self) -> Option<impl Display> {
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_span(&self) -> Option<Span> {
self.cursor.frames.last().map(|f| f.element.span())
}
}
const INSTRUCTIONS: &str = "
This is the Leo Interpreter. You probably want to start by running a function or transition.
For instance
#into program.aleo/main()
Once a function is running, commands include
#into to evaluate into the next expression or statement;
#step to take one step towards evaluating the current expression or statement;
#over to complete evaluating the current expression or statement;
#run to finish evaluating
#quit to exit the interpreter.
You may also use one letter abbreviations for these commands, such as #i.
Finally, you may simply enter expressions or statements on the command line
to evaluate. For instance, if you want to see the value of a variable w:
w
If you want to set w to a new value:
w = z + 2u8;
";
pub fn interpret(filenames: &[String]) -> Result<()> {
let mut interpreter = Interpreter::new(filenames.iter())?;
let mut buffer = String::new();
println!("{}", INSTRUCTIONS);
loop {
buffer.clear();
if let Some(v) = interpreter.view_current_in_context() {
println!("{v}");
} else if let Some(v) = interpreter.view_current() {
println!("{v}");
}
std::io::stdin().read_line(&mut buffer).expect("read_line");
let action = match buffer.trim() {
"#i" | "#into" => InterpreterAction::Into,
"#s" | "#step" => InterpreterAction::Step,
"#o" | "#over" => InterpreterAction::Over,
"#r" | "#run" => InterpreterAction::Run,
"#q" | "#quit" => return Ok(()),
s => {
if let Some(rest) = s.strip_prefix("#into ").or(s.strip_prefix("#i ")) {
InterpreterAction::LeoInterpretInto(rest.trim().into())
} else {
InterpreterAction::LeoInterpretOver(s.trim().into())
}
}
};
match interpreter.action(action) {
Ok(Some(value)) => println!("result: {value}"),
Ok(None) => {}
Err(LeoError::InterpreterHalt(interpreter_halt)) => println!("Halted: {interpreter_halt}"),
Err(e) => return Err(e),
}
}
}

View File

@ -82,6 +82,11 @@ enum Commands {
#[clap(flatten)]
command: LeoBuild,
},
#[clap(about = "Interpret the current package")]
Interpret {
#[clap(flatten)]
command: LeoInterpret,
},
#[clap(about = "Add a new on-chain or local dependency to the current package.")]
Add {
#[clap(flatten)]
@ -138,6 +143,7 @@ pub fn run_with_args(cli: CLI) -> Result<()> {
Commands::Account { command } => command.try_execute(context),
Commands::New { command } => command.try_execute(context),
Commands::Build { command } => command.try_execute(context),
Commands::Interpret { command } => command.try_execute(context),
Commands::Query { command } => command.try_execute(context),
Commands::Clean { command } => command.try_execute(context),
Commands::Deploy { command } => command.try_execute(context),

View File

@ -0,0 +1,42 @@
// 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 <https://www.gnu.org/licenses/>.
use super::*;
/// Deploys an Aleo program.
#[derive(Parser, Debug)]
pub struct LeoInterpret {
#[clap(long)]
pub(crate) files: Vec<String>,
}
impl Command for LeoInterpret {
type Input = ();
type Output = ();
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Leo")
}
fn prelude(&self, _context: Context) -> Result<Self::Input> {
Ok(())
}
fn apply(self, _context: Context, _: Self::Input) -> Result<Self::Output> {
leo_interpreter::interpret(&self.files)?;
Ok(())
}
}

View File

@ -35,6 +35,9 @@ pub use example::LeoExample;
pub mod execute;
pub use execute::LeoExecute;
pub mod interpret;
pub use interpret::LeoInterpret;
pub mod query;
pub use query::LeoQuery;