use leo_ast::Program;
pub use leo_ast::{Ast, InputAst};
use leo_errors::{emitter::Handler, CompilerError, Result};
pub use leo_passes::SymbolTable;
use leo_passes::*;
use leo_span::{source_map::FileName, symbol::with_session_globals};
use sha2::{Digest, Sha256};
use std::{fs, path::PathBuf};
use crate::CompilerOptions;
#[derive(Clone)]
pub struct Compiler<'a> {
handler: &'a Handler,
main_file_path: PathBuf,
output_directory: PathBuf,
pub program_name: String,
pub network: String,
pub ast: Ast,
pub input_ast: Option<InputAst>,
compiler_options: CompilerOptions,
}
impl<'a> Compiler<'a> {
pub fn new(
program_name: String,
network: String,
handler: &'a Handler,
main_file_path: PathBuf,
output_directory: PathBuf,
compiler_options: Option<CompilerOptions>,
) -> Self {
Self {
handler,
main_file_path,
output_directory,
program_name,
network,
ast: Ast::new(Program::default()),
input_ast: None,
compiler_options: compiler_options.unwrap_or_default(),
}
}
pub fn checksum(&self) -> Result<String> {
let unparsed_file = fs::read_to_string(&self.main_file_path)
.map_err(|e| CompilerError::file_read_error(self.main_file_path.clone(), e))?;
let mut hasher = Sha256::new();
hasher.update(unparsed_file.as_bytes());
let hash = hasher.finalize();
Ok(format!("{hash:x}"))
}
pub fn parse_program_from_string(&mut self, program_string: &str, name: FileName) -> Result<()> {
let prg_sf = with_session_globals(|s| s.source_map.new_source(program_string, name));
self.ast = leo_parser::parse_ast(self.handler, &prg_sf.src, prg_sf.start_pos)?;
let program_scope = self.ast.ast.program_scopes.values().next().unwrap();
let program_scope_name = format!("{}", program_scope.program_id.name);
if program_scope_name != self.program_name {
return Err(CompilerError::program_scope_name_does_not_match(
program_scope_name,
self.program_name.clone(),
program_scope.program_id.name.span,
)
.into());
}
if self.compiler_options.output.initial_ast {
self.write_ast_to_json("initial_ast.json")?;
}
Ok(())
}
pub fn parse_program(&mut self) -> Result<()> {
let program_string = fs::read_to_string(&self.main_file_path)
.map_err(|e| CompilerError::file_read_error(&self.main_file_path, e))?;
self.parse_program_from_string(&program_string, FileName::Real(self.main_file_path.clone()))
}
pub fn parse_input(&mut self, input_file_path: PathBuf) -> Result<()> {
if input_file_path.exists() {
let input_sf = with_session_globals(|s| s.source_map.load_file(&input_file_path))
.map_err(|e| CompilerError::file_read_error(&input_file_path, e))?;
let input_ast = leo_parser::parse_input(self.handler, &input_sf.src, input_sf.start_pos)?;
if self.compiler_options.output.initial_ast {
if self.compiler_options.output.spans_enabled {
input_ast.to_json_file(
self.output_directory.clone(),
&format!("{}.initial_input_ast.json", self.program_name),
)?;
} else {
input_ast.to_json_file_without_keys(
self.output_directory.clone(),
&format!("{}.initial_input_ast.json", self.program_name),
&["span"],
)?;
}
}
self.input_ast = Some(input_ast);
}
Ok(())
}
pub fn symbol_table_pass(&self) -> Result<SymbolTable> {
SymbolTableCreator::do_pass((&self.ast, self.handler))
}
pub fn type_checker_pass(&'a self, symbol_table: SymbolTable) -> Result<(SymbolTable, StructGraph, CallGraph)> {
TypeChecker::do_pass((&self.ast, self.handler, symbol_table))
}
pub fn loop_unrolling_pass(&mut self, symbol_table: SymbolTable) -> Result<SymbolTable> {
let (ast, symbol_table) = Unroller::do_pass((std::mem::take(&mut self.ast), self.handler, symbol_table))?;
self.ast = ast;
if self.compiler_options.output.unrolled_ast {
self.write_ast_to_json("unrolled_ast.json")?;
}
Ok(symbol_table)
}
pub fn static_single_assignment_pass(&mut self, symbol_table: &SymbolTable) -> Result<Assigner> {
let (ast, assigner) = StaticSingleAssigner::do_pass((std::mem::take(&mut self.ast), symbol_table))?;
self.ast = ast;
if self.compiler_options.output.ssa_ast {
self.write_ast_to_json("ssa_ast.json")?;
}
Ok(assigner)
}
pub fn flattening_pass(&mut self, symbol_table: &SymbolTable, assigner: Assigner) -> Result<Assigner> {
let (ast, assigner) = Flattener::do_pass((std::mem::take(&mut self.ast), symbol_table, assigner))?;
self.ast = ast;
if self.compiler_options.output.flattened_ast {
self.write_ast_to_json("flattened_ast.json")?;
}
Ok(assigner)
}
pub fn function_inlining_pass(&mut self, call_graph: &CallGraph, assigner: Assigner) -> Result<Assigner> {
let (ast, assigner) = FunctionInliner::do_pass((std::mem::take(&mut self.ast), call_graph, assigner))?;
self.ast = ast;
if self.compiler_options.output.inlined_ast {
self.write_ast_to_json("inlined_ast.json")?;
}
Ok(assigner)
}
pub fn dead_code_elimination_pass(&mut self) -> Result<()> {
if self.compiler_options.build.dce_enabled {
self.ast = DeadCodeEliminator::do_pass(std::mem::take(&mut self.ast))?;
}
if self.compiler_options.output.dce_ast {
self.write_ast_to_json("dce_ast.json")?;
}
Ok(())
}
pub fn code_generation_pass(
&mut self,
symbol_table: &SymbolTable,
struct_graph: &StructGraph,
call_graph: &CallGraph,
) -> Result<String> {
CodeGenerator::do_pass((&self.ast, symbol_table, struct_graph, call_graph))
}
pub fn compiler_stages(&mut self) -> Result<(SymbolTable, StructGraph, CallGraph)> {
let st = self.symbol_table_pass()?;
let (st, struct_graph, call_graph) = self.type_checker_pass(st)?;
let st = self.loop_unrolling_pass(st)?;
let assigner = self.static_single_assignment_pass(&st)?;
let assigner = self.flattening_pass(&st, assigner)?;
let _ = self.function_inlining_pass(&call_graph, assigner)?;
self.dead_code_elimination_pass()?;
Ok((st, struct_graph, call_graph))
}
pub fn compile(&mut self) -> Result<(SymbolTable, String)> {
self.parse_program()?;
let (symbol_table, struct_graph, call_graph) = self.compiler_stages()?;
let bytecode = self.code_generation_pass(&symbol_table, &struct_graph, &call_graph)?;
Ok((symbol_table, bytecode))
}
fn write_ast_to_json(&self, file_suffix: &str) -> Result<()> {
if self.compiler_options.output.spans_enabled {
self.ast.to_json_file(self.output_directory.clone(), &format!("{}.{file_suffix}", self.program_name))?;
} else {
self.ast.to_json_file_without_keys(
self.output_directory.clone(),
&format!("{}.{file_suffix}", self.program_name),
&["span"],
)?;
}
Ok(())
}
}