use leo_ast::{NodeBuilder, Stub};
use leo_disassembler::disassemble_from_str;
use leo_errors::{BufferEmitter, Handler, LeoError};
use leo_span::{Symbol, create_session_if_not_set_then};
use snarkvm::{
prelude::{Process, TestnetV0},
synthesizer::program::ProgramCore,
};
use indexmap::IndexMap;
use itertools::Itertools as _;
use serial_test::serial;
use std::{rc::Rc, str::FromStr};
#[derive(PartialEq)]
enum StubType {
FromLeo,
FromAleo,
}
fn run_test(stub_type: StubType, test: &str, handler: &Handler, node_builder: &Rc<NodeBuilder>) -> Result<String, ()> {
let mut process = Process::<TestnetV0>::load().unwrap();
let mut import_stubs = IndexMap::new();
let mut bytecodes = Vec::<String>::new();
let sources: Vec<&str> = test.split(super::test_utils::PROGRAM_DELIMITER).collect();
let mut add_aleo_program = |code: &str| -> Result<(), ()> {
let program = handler.extend_if_error(ProgramCore::from_str(code).map_err(LeoError::Anyhow))?;
handler.extend_if_error(process.add_program(&program).map_err(LeoError::Anyhow))?;
Ok(())
};
let (last, rest) = sources.split_last().expect("non-empty sources");
for source in rest {
if let Some(aleo_source) = super::test_utils::extract_aleo_stub_header(source) {
let program = handler
.extend_if_error(disassemble_from_str::<TestnetV0>("", aleo_source).map_err(|err| err.into()))?;
let name = program.stub_id.as_symbol();
import_stubs.insert(name, program.into());
add_aleo_program(aleo_source)?;
continue;
}
if let Some((library_name, library_source)) = super::test_utils::extract_library_header(source) {
let library = handler.extend_if_error(super::test_utils::parse_library(
&library_name,
library_source,
handler,
node_builder,
))?;
import_stubs.insert(Symbol::intern(&library_name), library.into());
if handler.err_count() != 0 {
return Err(());
}
continue;
}
match stub_type {
StubType::FromLeo => {
let (program, program_name) = handler.extend_if_error(super::test_utils::parse_program(
source,
handler,
node_builder,
import_stubs.clone(),
))?;
import_stubs.insert(Symbol::intern(&program_name), program.into());
if handler.err_count() != 0 {
return Err(());
}
}
StubType::FromAleo => {
let program_name_for_parents =
handler.extend_if_error(super::test_utils::extract_program_name(source, handler))?;
let program_symbol = Symbol::intern(&program_name_for_parents);
for stub in import_stubs.values_mut() {
if stub.is_library() {
stub.add_parent(program_symbol);
}
}
let (programs, program_name) = handler.extend_if_error(super::test_utils::whole_compile(
source,
handler,
node_builder,
import_stubs.clone(),
))?;
add_aleo_program(&programs.primary.bytecode)?;
let program = handler.extend_if_error(
disassemble_from_str::<TestnetV0>(&program_name, &programs.primary.bytecode)
.map_err(|err| err.into()),
)?;
import_stubs.insert(Symbol::intern(&program_name), program.into());
if handler.err_count() != 0 {
return Err(());
}
bytecodes.push(programs.primary.bytecode);
}
}
}
let final_program_name = handler.extend_if_error(super::test_utils::extract_program_name(last, handler))?;
let final_symbol = Symbol::intern(&final_program_name);
{
let symbols: Vec<Symbol> = import_stubs.keys().copied().collect();
for (i, symbol) in symbols.iter().enumerate() {
if let Some(Stub::FromLibrary { parents, .. }) = import_stubs.get_mut(symbol) {
parents.extend(symbols[i + 1..].iter().copied());
parents.insert(final_symbol);
}
}
}
let (compiled, _program_name) =
handler.extend_if_error(super::test_utils::whole_compile(last, handler, node_builder, import_stubs.clone()))?;
if handler.err_count() != 0 {
return Err(());
}
if stub_type == StubType::FromLeo {
for import in &compiled.imports {
add_aleo_program(&import.bytecode)?;
bytecodes.push(import.bytecode.clone());
}
}
let primary_bytecode = compiled.primary.bytecode.clone();
add_aleo_program(&primary_bytecode)?;
bytecodes.push(primary_bytecode);
Ok(bytecodes.iter().format(&format!("{}\n", super::test_utils::PROGRAM_DELIMITER)).to_string())
}
fn runner(source: &str) -> String {
let from_leo_output = run_with_stub(StubType::FromLeo, source);
let from_aleo_output = run_with_stub(StubType::FromAleo, source);
if from_leo_output != from_aleo_output {
format!(
"{from_leo_output}\n\n\
---\n\
Note: Treating dependencies as Aleo produces different results:\n\n\
{from_aleo_output}"
)
} else {
from_leo_output
}
}
fn run_with_stub(stub: StubType, source: &str) -> String {
let buf = BufferEmitter::new();
let handler = Handler::new(buf.clone());
let node_builder = Rc::new(NodeBuilder::default());
create_session_if_not_set_then(|_| {
match run_test(stub, source, &handler, &node_builder) {
Ok(output) => {
format!("{}{}", buf.extract_warnings(), output)
}
Err(()) => {
format!("{}{}", buf.extract_errs(), buf.extract_warnings())
}
}
})
}
#[test]
#[serial]
fn test_compiler() {
leo_test_framework::run_tests("compiler", runner);
}