use crate::CodeGenerator;
use leo_ast::{functions, CallType, Function, Identifier, Mapping, Mode, Program, ProgramScope, Struct, Type};
use indexmap::IndexMap;
use itertools::Itertools;
use leo_span::sym;
use std::fmt::Write as _;
impl<'a> CodeGenerator<'a> {
pub(crate) fn visit_program(&mut self, input: &'a Program) -> String {
let mut program_string = String::new();
if !input.imports.is_empty() {
program_string.push_str(
&input
.imports
.iter()
.map(|(identifier, (imported_program, _))| self.visit_import(identifier, imported_program))
.join("\n"),
);
program_string.push('\n');
}
let program_scope: &ProgramScope = input.program_scopes.values().next().unwrap();
writeln!(program_string, "program {};", program_scope.program_id)
.expect("Failed to write program id to string.");
program_string.push('\n');
program_string.push_str(
&program_scope
.structs
.values()
.map(|struct_| self.visit_struct_or_record(struct_))
.join("\n"),
);
program_string.push('\n');
program_string.push_str(
&program_scope
.mappings
.values()
.map(|mapping| self.visit_mapping(mapping))
.join("\n"),
);
let mut closures = String::new();
let mut functions = String::new();
program_scope.functions.values().for_each(|function| {
self.is_transition_function = matches!(function.call_type, CallType::Transition);
let function_string = self.visit_function(function);
if self.is_transition_function {
functions.push_str(&function_string);
functions.push('\n');
} else {
closures.push_str(&function_string);
closures.push('\n');
}
self.is_transition_function = false;
});
program_string.push_str(&closures);
program_string.push('\n');
program_string.push_str(&functions);
program_string
}
fn visit_import(&mut self, import_name: &'a Identifier, import_program: &'a Program) -> String {
let _import_program_string = self.visit_program(import_program);
format!("import {import_name}.aleo;")
}
fn visit_struct_or_record(&mut self, struct_: &'a Struct) -> String {
if struct_.is_record {
self.visit_record(struct_)
} else {
self.visit_struct(struct_)
}
}
fn visit_struct(&mut self, struct_: &'a Struct) -> String {
self.composite_mapping
.insert(&struct_.identifier.name, (false, String::from("private")));
let mut output_string = format!("struct {}:\n", struct_.identifier);
for var in struct_.members.iter() {
writeln!(output_string, " {} as {};", var.identifier, var.type_,).expect("failed to write to string");
}
output_string
}
fn visit_record(&mut self, record: &'a Struct) -> String {
let mut output_string = String::from("record");
self.composite_mapping
.insert(&record.identifier.name, (true, output_string.clone()));
writeln!(output_string, " {}:", record.identifier).expect("failed to write to string");
for var in record.members.iter() {
writeln!(
output_string,
" {} as {}.private;", var.identifier, var.type_,
)
.expect("failed to write to string");
}
output_string
}
fn visit_function(&mut self, function: &'a Function) -> String {
self.next_register = 0;
self.variable_mapping = IndexMap::new();
self.variable_mapping.insert(&sym::SelfLower, "self".to_string());
self.current_function = Some(function);
let mut function_string = match self.is_transition_function {
true => format!("function {}:\n", function.identifier),
false => format!("closure {}:\n", function.identifier),
};
for input in function.input.iter() {
let register_string = format!("r{}", self.next_register);
self.next_register += 1;
let type_string = match input {
functions::Input::Internal(input) => {
self.variable_mapping
.insert(&input.identifier.name, register_string.clone());
let visibility = match (self.is_transition_function, input.mode) {
(true, Mode::None) => Mode::Private,
_ => input.mode,
};
self.visit_type_with_visibility(&input.type_, visibility)
}
functions::Input::External(input) => {
self.variable_mapping
.insert(&input.identifier.name, register_string.clone());
format!("{}.aleo/{}.record", input.program_name, input.record)
}
};
writeln!(function_string, " input {register_string} as {type_string};",)
.expect("failed to write to string");
}
let block_string = self.visit_block(&function.block);
function_string.push_str(&block_string);
if let Some(finalize) = &function.finalize {
self.next_register = 0;
self.in_finalize = true;
self.variable_mapping = IndexMap::new();
self.variable_mapping.insert(&sym::SelfLower, "self".to_string());
function_string.push_str(&format!("\nfinalize {}:\n", finalize.identifier));
for input in finalize.input.iter() {
let register_string = format!("r{}", self.next_register);
self.next_register += 1;
let type_string = match input {
functions::Input::Internal(input) => {
self.variable_mapping
.insert(&input.identifier.name, register_string.clone());
let visibility = match (self.is_transition_function, input.mode) {
(true, Mode::None) => Mode::Public,
_ => input.mode,
};
self.visit_type_with_visibility(&input.type_, visibility)
}
functions::Input::External(input) => {
self.variable_mapping
.insert(&input.program_name.name, register_string.clone());
format!("{}.aleo/{}.record", input.program_name, input.record)
}
};
writeln!(function_string, " input {register_string} as {type_string};",)
.expect("failed to write to string");
}
function_string.push_str(&self.visit_block(&finalize.block));
self.in_finalize = false;
}
function_string
}
fn visit_mapping(&mut self, mapping: &'a Mapping) -> String {
let mut mapping_string = format!("mapping {}:\n", mapping.identifier);
let create_type = |type_: &Type| {
match type_ {
Type::Mapping(_) | Type::Tuple(_) => unreachable!("Mappings cannot contain mappings or tuples."),
Type::Identifier(identifier) => {
let (is_record, _) = self.composite_mapping.get(&identifier.name).unwrap();
match is_record {
true => format!("{identifier}.record"),
false => format!("{identifier}.public"),
}
}
type_ => format!("{type_}.public"),
}
};
mapping_string.push_str(&format!("\tkey left as {};\n", create_type(&mapping.key_type)));
mapping_string.push_str(&format!("\tvalue right as {};\n", create_type(&mapping.value_type)));
mapping_string
}
}