Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Interpreter.
Browse files Browse the repository at this point in the history
mikebenfield committed Dec 2, 2024
1 parent d9fe41a commit dd68615
Showing 26 changed files with 5,349 additions and 11 deletions.
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 20 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" ]
@@ -95,6 +103,10 @@ version = "0.13.0"
version = "0.8"
default-features = false

[workspace.dependencies.rand_chacha]
version = "0.3.0"
default-features = false

[workspace.dependencies.regex]
version = "1.11.1"

@@ -109,6 +121,9 @@ features = [ "derive", "rc" ]
version = "1.0"
features = [ "preserve_order" ]

[workspace.dependencies.tempfile]
version = "3.13"

[workspace.dependencies.toml]
version = "0.8"
features = [ "preserve_order" ]
@@ -145,6 +160,9 @@ workspace = true
[dependencies.leo-errors]
workspace = true

[dependencies.leo-interpreter]
workspace = true

[dependencies.leo-package]
workspace = true

@@ -165,7 +183,7 @@ version = "4.5"
features = [ "derive", "env", "color", "unstable-styles" ]

[dependencies.colored]
version = "2.0"
workspace = true

[dependencies.dotenvy]
version = "0.15.7"
@@ -177,8 +195,7 @@ workspace = true
workspace = true

[dependencies.rand_chacha]
version = "0.3.0"
default-features = false
workspace = true

[dependencies.self_update]
version = "0.41.0"
2 changes: 1 addition & 1 deletion compiler/compiler/Cargo.toml
Original file line number Diff line number Diff line change
@@ -74,7 +74,7 @@ workspace = true
version = "3.1.1"

[dev-dependencies.tempfile]
version = "3.13"
workspace = true

[features]
default = [ ]
34 changes: 33 additions & 1 deletion compiler/parser/src/parser/mod.rs
Original file line number Diff line number Diff line change
@@ -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,35 @@ pub fn parse<N: Network>(

tokens.parse_program()
}

pub fn parse_expression<N: Network>(
handler: &Handler,
node_builder: &NodeBuilder,
source: &str,
start_pos: BytePos,
) -> Result<Expression> {
let mut context = ParserContext::<N>::new(handler, node_builder, crate::tokenize(source, start_pos)?);

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,
start_pos: BytePos,
) -> Result<Statement> {
let mut context = ParserContext::<N>::new(handler, node_builder, crate::tokenize(source, start_pos)?);

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())
}
}
2 changes: 1 addition & 1 deletion compiler/span/src/source_map.rs
Original file line number Diff line number Diff line change
@@ -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())
}

50 changes: 50 additions & 0 deletions errors/src/errors/interpreter_halt.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
7 changes: 7 additions & 0 deletions errors/src/errors/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
74 changes: 74 additions & 0 deletions interpreter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[package]
name = "leo-interpreter"
version = "2.3.1"
authors = [ "The Leo Team <[email protected]>" ]
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.82.0"

[dependencies.snarkvm]
workspace = true

[dependencies.snarkvm-circuit]
version = "1.0.0"

[dependencies.snarkvm-synthesizer-program]
version = "1.0.0"

[dependencies.leo-ast]
workspace = true

[dependencies.leo-passes]
workspace = true

[dependencies.leo-errors]
workspace = true

[dependencies.leo-package]
workspace = true

[dependencies.leo-parser]
workspace = true

[dependencies.leo-span]
workspace = true

[dependencies.colored]
workspace = true

[dependencies.indexmap]
workspace = true

[dependencies.dialoguer]
version = "0.11.0"
features = [ "history" ]

[dependencies.rand]
workspace = true

[dependencies.rand_chacha]
workspace = true

[dependencies.toml]
workspace = true

[dev-dependencies.leo-test-framework]
path = "../tests/test-framework"

[dev-dependencies.serial_test]
version = "3.1.1"

[dev-dependencies.tempfile]
workspace = true
2,675 changes: 2,675 additions & 0 deletions interpreter/src/cursor.rs

Large diffs are not rendered by default.

1,028 changes: 1,028 additions & 0 deletions interpreter/src/cursor_aleo.rs

Large diffs are not rendered by default.

387 changes: 387 additions & 0 deletions interpreter/src/interpreter.rs

Large diffs are not rendered by default.

186 changes: 186 additions & 0 deletions interpreter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// 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 leo_ast::{Ast, Node as _, NodeBuilder};
use leo_errors::{InterpreterHalt, LeoError, Result};
use leo_span::{Span, source_map::FileName, symbol::with_session_globals};

use snarkvm::prelude::{Program, TestnetV0};

use colored::*;
use std::{
collections::HashMap,
fmt::{Display, Write as _},
fs,
path::{Path, PathBuf},
};

#[cfg(test)]
mod test;

mod util;
use util::*;

mod cursor;
use cursor::*;

mod interpreter;
use interpreter::*;

mod cursor_aleo;

mod value;
use value::*;

const INTRO: &str = "This is the Leo Interpreter. Try the command `#help`.";

const HELP: &str = "
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 quit the interpreter.
You can set a breakpoint with
#break program_name line_number
When executing Aleo VM code, you can print the value of a register like this:
#print 2
You may also use one letter abbreviations for these commands, such as #i.
Note that this interpreter is not line oriented as in many common debuggers;
rather it is oriented around expressions and statements.
As you step into code, individual expressions or statements will
be evaluated one by one, including arguments of function calls.
You may simply enter Leo 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;
Note that statements (like the assignment above) must end with a semicolon.
If there are futures available to be executed, they will be listed by
numerical index, and you may run them using `#future` (or `#f`); for instance
#future 0
Input history is available - use the up and down arrow keys.
";

fn parse_breakpoint(s: &str) -> Option<Breakpoint> {
let strings: Vec<&str> = s.split_whitespace().collect();
if strings.len() == 2 {
let mut program = strings[0].to_string();
if program.ends_with(".aleo") {
program.truncate(program.len() - 5);
}
if let Ok(line) = strings[1].parse::<usize>() {
return Some(Breakpoint { program, line });
}
}
None
}

/// Load all the Leo source files indicated and open the interpreter
/// to commands from the user.
pub fn interpret(
leo_filenames: &[PathBuf],
aleo_filenames: &[PathBuf],
signer: SvmAddress,
block_height: u32,
) -> Result<()> {
let mut interpreter = Interpreter::new(leo_filenames.iter(), aleo_filenames.iter(), signer, block_height)?;
println!("{}", INTRO);

let mut history = dialoguer::BasicHistory::new();
loop {
if let Some(v) = interpreter.view_current_in_context() {
println!("{}:\n{v}", "Prepared to evaluate".bold());
} else if let Some(v) = interpreter.view_current() {
println!("{}:\n{v}", "Prepared to evaluate".bold());
}
for (i, future) in interpreter.cursor.futures.iter().enumerate() {
println!("{i}: {future}");
}

let user_input: String = dialoguer::Input::with_theme(&dialoguer::theme::ColorfulTheme::default())
.with_prompt("Command?")
.history_with(&mut history)
.interact_text()
.unwrap();

let action = match user_input.trim() {
"" => continue,
"#h" | "#help" => {
println!("{}", HELP);
continue;
}
"#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("#future ").or(s.strip_prefix("#f ")) {
if let Ok(num) = rest.trim().parse::<usize>() {
if num >= interpreter.cursor.futures.len() {
println!("No such future");
continue;
}
InterpreterAction::RunFuture(num)
} else {
println!("Failed to parse future");
continue;
}
} else if let Some(rest) = s.strip_prefix("#break ").or(s.strip_prefix("#b ")) {
let Some(breakpoint) = parse_breakpoint(rest) else {
println!("Failed to parse breakpoint");
continue;
};
InterpreterAction::Breakpoint(breakpoint)
} else if let Some(rest) = s.strip_prefix("#into ").or(s.strip_prefix("#i ")) {
InterpreterAction::LeoInterpretInto(rest.trim().into())
} else if let Some(rest) = s.strip_prefix("#print ").or(s.strip_prefix("#p ")) {
let trimmed = rest.trim();
let without_r = trimmed.strip_prefix("r").unwrap_or(trimmed);
if let Ok(num) = without_r.parse::<u64>() {
InterpreterAction::PrintRegister(num)
} else {
println!("failed to parse register number {trimmed}");
continue;
}
} else {
InterpreterAction::LeoInterpretOver(s.trim().into())
}
}
};

match interpreter.action(action) {
Ok(Some(value)) => {
println!("{}: {}\n", "Result".bold(), format!("{value}").bright_cyan());
}
Ok(None) => {}
Err(LeoError::InterpreterHalt(interpreter_halt)) => println!("Halted: {interpreter_halt}"),
Err(e) => return Err(e),
}
}
}
25 changes: 25 additions & 0 deletions interpreter/src/test/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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 serial_test::serial;

mod runner;

#[test]
#[serial]
pub fn tests() {
leo_test_framework::run_tests(&runner::InterpreterRunner, "interpreter");
}
98 changes: 98 additions & 0 deletions interpreter/src/test/runner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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 crate::*;

use snarkvm::prelude::{Address, PrivateKey};

use leo_span::symbol::create_session_if_not_set_then;
use leo_test_framework::runner::{Namespace, ParseType, Runner, Test};

use std::{fs, path::PathBuf, str::FromStr as _};

pub struct LeoNamespace;

impl Namespace for LeoNamespace {
fn parse_type(&self) -> ParseType {
ParseType::Whole
}

fn run_test(&self, test: Test) -> Result<toml::Value, String> {
create_session_if_not_set_then(|_| run_leo_test(test).map(|v| toml::Value::String(format!("{v}"))))
}
}

pub struct AleoNamespace;

impl Namespace for AleoNamespace {
fn parse_type(&self) -> ParseType {
ParseType::Whole
}

fn run_test(&self, test: Test) -> Result<toml::Value, String> {
create_session_if_not_set_then(|_| run_aleo_test(test).map(|v| toml::Value::String(format!("{v}"))))
}
}

pub struct InterpreterRunner;

impl Runner for InterpreterRunner {
fn resolve_namespace(&self, name: &str) -> Option<Box<dyn Namespace>> {
match name {
"Leo" => Some(Box::new(LeoNamespace)),
"Aleo" => Some(Box::new(AleoNamespace)),
_ => None,
}
}
}

fn run_leo_test(test: Test) -> Result<Value, String> {
let tempdir = tempfile::tempdir().map_err(|e| format!("{e}"))?;
let mut filename = PathBuf::from(tempdir.path());
filename.push("main.leo");
fs::write(&filename, &test.content).map_err(|e| format!("{e}"))?;

let private_key: PrivateKey<TestnetV0> =
PrivateKey::from_str(leo_package::VALIDATOR_0_PRIVATE_KEY).expect("should be able to parse private key");
let address = Address::try_from(&private_key).expect("should be able to create address");
let empty: [&PathBuf; 0] = [];
let mut interpreter = Interpreter::new([filename].iter(), empty, address, 0).map_err(|e| format!("{e}"))?;
let v = interpreter.action(InterpreterAction::LeoInterpretOver("test.aleo/main()".into()));
println!("got {v:?}");
match v {
Err(e) => Err(format!("{e}")),
Ok(None) => Err("no value received".to_string()),
Ok(Some(v)) => Ok(v),
}
}

fn run_aleo_test(test: Test) -> Result<Value, String> {
let tempdir = tempfile::tempdir().map_err(|e| format!("{e}"))?;
let mut filename = PathBuf::from(tempdir.path());
filename.push("main.aleo");
fs::write(&filename, &test.content).map_err(|e| format!("{e}"))?;

let private_key: PrivateKey<TestnetV0> =
PrivateKey::from_str(leo_package::VALIDATOR_0_PRIVATE_KEY).expect("should be able to parse private key");
let address = Address::try_from(&private_key).expect("should be able to create address");
let empty: [&PathBuf; 0] = [];
let mut interpreter = Interpreter::new(empty, [filename].iter(), address, 0).map_err(|e| format!("{e}"))?;
match interpreter.action(InterpreterAction::LeoInterpretOver("test.aleo/main()".into())) {
Err(e) => Err(format!("{e}")),
Ok(None) => Err("no value received".to_string()),
Ok(Some(v)) => Ok(v),
}
}
75 changes: 75 additions & 0 deletions interpreter/src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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 leo_errors::{InterpreterHalt, Result};
use leo_span::{Span, Symbol};

use snarkvm::prelude::{Identifier, TestnetV0};

#[macro_export]
macro_rules! tc_fail {
() => {
panic!("type checker failure")
};
}

#[macro_export]
macro_rules! halt_no_span {
($($x:tt)*) => {
return Err(InterpreterHalt::new(format!($($x)*)).into())
}
}

#[macro_export]
macro_rules! halt {
($span: expr) => {
return Err(InterpreterHalt::new_spanned(String::new(), $span).into())

};

($span: expr, $($x:tt)*) => {
return Err(InterpreterHalt::new_spanned(format!($($x)*), $span).into())
};
}

pub trait ExpectTc {
type T;
fn expect_tc(self, span: Span) -> Result<Self::T>;
}

impl<T> ExpectTc for Option<T> {
type T = T;

fn expect_tc(self, span: Span) -> Result<Self::T> {
match self {
Some(t) => Ok(t),
None => Err(InterpreterHalt::new_spanned("type failure".into(), span).into()),
}
}
}

impl<T, U: std::fmt::Debug> ExpectTc for Result<T, U> {
type T = T;

fn expect_tc(self, span: Span) -> Result<Self::T> {
self.map_err(|_e| InterpreterHalt::new_spanned("type failure".into(), span).into())
}
}

pub fn snarkvm_identifier_to_symbol(id: &Identifier<TestnetV0>) -> Symbol {
let s = id.to_string();
Symbol::intern(&s)
}
476 changes: 476 additions & 0 deletions interpreter/src/value.rs

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions leo/cli/cli.rs
Original file line number Diff line number Diff line change
@@ -82,6 +82,11 @@ enum Commands {
#[clap(flatten)]
command: LeoBuild,
},
#[clap(about = "Debug the current package via the interpreter")]
Debug {
#[clap(flatten)]
command: LeoDebug,
},
#[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::Debug { 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),
137 changes: 137 additions & 0 deletions leo/cli/commands/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// 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::path::PathBuf;

use snarkvm::prelude::{Network, ProgramID, TestnetV0};

#[cfg(not(feature = "only_testnet"))]
use snarkvm::prelude::{CanaryV0, MainnetV0};

use leo_errors::UtilError;
use leo_retriever::{Manifest, NetworkName, Retriever};
use leo_span::Symbol;

use super::*;

/// Debugs an Aleo program through the interpreter.
#[derive(Parser, Debug)]
pub struct LeoDebug {
#[arg(long, help = "Use these source files instead of finding source files through the project structure.", num_args = 1..)]
pub(crate) paths: Vec<String>,

#[arg(long, help = "The block height, accessible via block.height.", default_value = "0")]
pub(crate) block_height: u32,

#[clap(flatten)]
pub(crate) compiler_options: BuildOptions,
}

impl Command for LeoDebug {
type Input = <LeoBuild as Command>::Output;
type Output = ();

fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Leo")
}

fn prelude(&self, context: Context) -> Result<Self::Input> {
if self.paths.is_empty() {
(LeoBuild { options: self.compiler_options.clone() }).execute(context)
} else {
Ok(())
}
}

fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
// Parse the network.
let network = NetworkName::try_from(context.get_network(&self.compiler_options.network)?)?;
match network {
NetworkName::TestnetV0 => handle_debug::<TestnetV0>(&self, context),
NetworkName::MainnetV0 => {
#[cfg(feature = "only_testnet")]
panic!("Mainnet chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
return handle_debug::<MainnetV0>(&self, context);
}
NetworkName::CanaryV0 => {
#[cfg(feature = "only_testnet")]
panic!("Canary chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
return handle_debug::<CanaryV0>(&self, context);
}
}
}
}

fn handle_debug<N: Network>(command: &LeoDebug, context: Context) -> Result<()> {
if command.paths.is_empty() {
// Get the package path.
let package_path = context.dir()?;
let home_path = context.home()?;

// Get the program id.
let manifest = Manifest::read_from_dir(&package_path)?;
let program_id = ProgramID::<N>::from_str(manifest.program())?;

// Get the private key.
let private_key = context.get_private_key(&None)?;
let address = Address::try_from(&private_key)?;

// Retrieve all local dependencies in post order
let main_sym = Symbol::intern(&program_id.name().to_string());
let mut retriever = Retriever::<N>::new(
main_sym,
&package_path,
&home_path,
context.get_endpoint(&command.compiler_options.endpoint)?.to_string(),
)
.map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?;
let mut local_dependencies =
retriever.retrieve().map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?;

// Push the main program at the end of the list.
local_dependencies.push(main_sym);

let paths: Vec<PathBuf> = local_dependencies
.into_iter()
.map(|dependency| {
let base_path = retriever.get_context(&dependency).full_path();
base_path.join("src/main.leo")
})
.collect();

leo_interpreter::interpret(&paths, &[], address, command.block_height)
} else {
let private_key: PrivateKey<TestnetV0> = PrivateKey::from_str(leo_package::VALIDATOR_0_PRIVATE_KEY)?;
let address = Address::try_from(&private_key)?;

let leo_paths: Vec<PathBuf> = command
.paths
.iter()
.filter(|path_str| path_str.ends_with(".leo"))
.map(|path_str| path_str.into())
.collect();
let aleo_paths: Vec<PathBuf> = command
.paths
.iter()
.filter(|path_str| !path_str.ends_with(".leo"))
.map(|path_str| path_str.into())
.collect();

leo_interpreter::interpret(&leo_paths, &aleo_paths, address, command.block_height)
}
}
3 changes: 3 additions & 0 deletions leo/cli/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -26,6 +26,9 @@ pub use build::LeoBuild;
pub mod clean;
pub use clean::LeoClean;

pub mod debug;
pub use debug::LeoDebug;

pub mod deploy;
pub use deploy::Deploy;

2 changes: 2 additions & 0 deletions leo/package/src/lib.rs
Original file line number Diff line number Diff line change
@@ -31,6 +31,8 @@ use std::{fs, fs::ReadDir, path::PathBuf};

pub static LEO_FILE_EXTENSION: &str = ".leo";

pub static VALIDATOR_0_PRIVATE_KEY: &str = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH";

pub(crate) fn parse_file_paths(directory: ReadDir, file_paths: &mut Vec<PathBuf>) -> Result<()> {
for file_entry in directory {
let file_entry = file_entry.map_err(PackageError::failed_to_get_leo_file_entry)?;
7 changes: 2 additions & 5 deletions leo/package/src/package.rs
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.

use crate::{
VALIDATOR_0_PRIVATE_KEY,
root::{Env, Gitignore},
source::{MainFile, SourceDirectory},
};
@@ -148,11 +149,7 @@ impl Package {

// Create the .env file.
// Include the private key of validator 0 for ease of use with local devnets, as it will automatically be seeded with funds.
Env::<N>::new(
Some(PrivateKey::<N>::from_str("APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH")?),
endpoint,
)?
.write_to(&path)?;
Env::<N>::new(Some(PrivateKey::<N>::from_str(VALIDATOR_0_PRIVATE_KEY)?), endpoint)?.write_to(&path)?;

// Create a manifest.
let manifest = Manifest::default(package_name);
3 changes: 3 additions & 0 deletions tests/expectations/interpreter/arithmetic.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace = "Leo"
expectation = "Pass"
outputs = ["1712u32"]
3 changes: 3 additions & 0 deletions tests/expectations/interpreter/hash.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace = "Leo"
expectation = "Pass"
outputs = ["7649508962193807282860816486231709561414143880166292770947785297592281622433field"]
2 changes: 2 additions & 0 deletions tests/test-framework/src/error.rs
Original file line number Diff line number Diff line change
@@ -48,6 +48,8 @@ fn toml_to_string(x: &Value) -> String {
s.push('\n');
}
s
} else if let Some(s) = x.as_str() {
s.to_string()
} else {
toml::to_string(x).expect("serialization failed")
}
15 changes: 15 additions & 0 deletions tests/tests/interpreter/arithmetic.leo
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
namespace = "Leo"
expectation = "Pass"
*/

program test.aleo {
function f(x: u32, y: u32) -> u32 {
return x + (17u32 * y);

}

transition main() -> u32 {
return f(12u32, 100u32);
}
}
15 changes: 15 additions & 0 deletions tests/tests/interpreter/hash.leo
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
namespace = "Leo"
expectation = "Pass"
*/

program test.aleo {
transition main() -> field {
let a: field = 1234567890field;
return BHP256::hash_to_field(a)
* SHA3_256::hash_to_field(a)
* Poseidon2::hash_to_field(a)
* Poseidon4::hash_to_field(a)
* Poseidon8::hash_to_field(a);
}
}

0 comments on commit dd68615

Please sign in to comment.