1use crate::CompilerOptions;
22
23pub use leo_ast::Ast;
24use leo_ast::{NodeBuilder, Program, Stub};
25use leo_errors::{CompilerError, Result, emitter::Handler};
26pub use leo_passes::SymbolTable;
27use leo_passes::*;
28use leo_span::{Symbol, source_map::FileName, symbol::with_session_globals};
29
30use snarkvm::prelude::Network;
31
32use indexmap::{IndexMap, IndexSet};
33use sha2::{Digest, Sha256};
34use std::{fs, path::PathBuf};
35
36#[derive(Clone)]
38pub struct Compiler<'a, N: Network> {
39 handler: &'a Handler,
41 main_file_path: PathBuf,
43 output_directory: PathBuf,
45 pub program_name: String,
47 pub network: String,
49 pub ast: Ast,
51 compiler_options: CompilerOptions,
53 node_builder: NodeBuilder,
55 assigner: Assigner,
57 type_table: TypeTable,
59 import_stubs: IndexMap<Symbol, Stub>,
61 phantom: std::marker::PhantomData<N>,
63}
64
65impl<'a, N: Network> Compiler<'a, N> {
66 pub fn new(
68 program_name: String,
69 network: String,
70 handler: &'a Handler,
71 main_file_path: PathBuf,
72 output_directory: PathBuf,
73 compiler_options: Option<CompilerOptions>,
74 import_stubs: IndexMap<Symbol, Stub>,
75 ) -> Self {
76 let node_builder = NodeBuilder::default();
77 let assigner = Assigner::default();
78 let type_table = TypeTable::default();
79 Self {
80 handler,
81 main_file_path,
82 output_directory,
83 program_name,
84 network,
85 ast: Ast::new(Program::default()),
86 compiler_options: compiler_options.unwrap_or_default(),
87 node_builder,
88 assigner,
89 import_stubs,
90 type_table,
91 phantom: Default::default(),
92 }
93 }
94
95 pub fn checksum(&self) -> Result<String> {
97 let unparsed_file = fs::read_to_string(&self.main_file_path)
99 .map_err(|e| CompilerError::file_read_error(self.main_file_path.clone(), e))?;
100
101 let mut hasher = Sha256::new();
103 hasher.update(unparsed_file.as_bytes());
104 let hash = hasher.finalize();
105
106 Ok(format!("{hash:x}"))
107 }
108
109 pub fn parse_program_from_string(&mut self, program_string: &str, name: FileName) -> Result<()> {
111 let prg_sf = with_session_globals(|s| s.source_map.new_source(program_string, name));
113
114 self.ast = leo_parser::parse_ast::<N>(self.handler, &self.node_builder, &prg_sf.src, prg_sf.start_pos)?;
116
117 let program_scope = self.ast.ast.program_scopes.values().next().unwrap();
121 let program_scope_name = format!("{}", program_scope.program_id.name);
122 if program_scope_name != self.program_name {
123 return Err(CompilerError::program_scope_name_does_not_match(
124 program_scope_name,
125 self.program_name.clone(),
126 program_scope.program_id.name.span,
127 )
128 .into());
129 }
130
131 if self.compiler_options.output.initial_ast {
132 self.write_ast_to_json("initial_ast.json")?;
133 }
134
135 Ok(())
136 }
137
138 pub fn parse_program(&mut self) -> Result<()> {
140 let program_string = fs::read_to_string(&self.main_file_path)
142 .map_err(|e| CompilerError::file_read_error(&self.main_file_path, e))?;
143
144 self.parse_program_from_string(&program_string, FileName::Real(self.main_file_path.clone()))
145 }
146
147 pub fn symbol_table_pass(&self) -> Result<SymbolTable> {
149 let symbol_table = SymbolTableCreator::do_pass((&self.ast, self.handler))?;
150 if self.compiler_options.output.initial_symbol_table {
151 self.write_symbol_table_to_json("initial_symbol_table.json", &symbol_table)?;
152 }
153 Ok(symbol_table)
154 }
155
156 pub fn type_checker_pass(&'a self, symbol_table: SymbolTable) -> Result<(SymbolTable, StructGraph, CallGraph)> {
158 let (symbol_table, struct_graph, call_graph) =
159 TypeChecker::<N>::do_pass((&self.ast, self.handler, symbol_table, &self.type_table))?;
160 if self.compiler_options.output.type_checked_symbol_table {
161 self.write_symbol_table_to_json("type_checked_symbol_table.json", &symbol_table)?;
162 }
163 Ok((symbol_table, struct_graph, call_graph))
164 }
165
166 pub fn static_analysis_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> {
168 StaticAnalyzer::<N>::do_pass((
169 &self.ast,
170 self.handler,
171 symbol_table,
172 &self.type_table,
173 self.compiler_options.build.conditional_block_max_depth,
174 self.compiler_options.build.disable_conditional_branch_type_checking,
175 ))
176 }
177
178 pub fn loop_unrolling_pass(&mut self, symbol_table: SymbolTable) -> Result<SymbolTable> {
180 let (ast, symbol_table) = Unroller::do_pass((
181 std::mem::take(&mut self.ast),
182 self.handler,
183 &self.node_builder,
184 symbol_table,
185 &self.type_table,
186 ))?;
187 self.ast = ast;
188
189 if self.compiler_options.output.unrolled_ast {
190 self.write_ast_to_json("unrolled_ast.json")?;
191 }
192
193 if self.compiler_options.output.unrolled_symbol_table {
194 self.write_symbol_table_to_json("unrolled_symbol_table.json", &symbol_table)?;
195 }
196
197 Ok(symbol_table)
198 }
199
200 pub fn static_single_assignment_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> {
202 self.ast = StaticSingleAssigner::do_pass((
203 std::mem::take(&mut self.ast),
204 &self.node_builder,
205 &self.assigner,
206 symbol_table,
207 &self.type_table,
208 ))?;
209
210 if self.compiler_options.output.ssa_ast {
211 self.write_ast_to_json("ssa_ast.json")?;
212 }
213
214 Ok(())
215 }
216
217 pub fn flattening_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> {
219 self.ast = Flattener::do_pass((
220 std::mem::take(&mut self.ast),
221 symbol_table,
222 &self.type_table,
223 &self.node_builder,
224 &self.assigner,
225 ))?;
226
227 if self.compiler_options.output.flattened_ast {
228 self.write_ast_to_json("flattened_ast.json")?;
229 }
230
231 Ok(())
232 }
233
234 pub fn destructuring_pass(&mut self) -> Result<()> {
236 self.ast = Destructurer::do_pass((
237 std::mem::take(&mut self.ast),
238 &self.type_table,
239 &self.node_builder,
240 &self.assigner,
241 ))?;
242
243 if self.compiler_options.output.destructured_ast {
244 self.write_ast_to_json("destructured_ast.json")?;
245 }
246
247 Ok(())
248 }
249
250 pub fn function_inlining_pass(&mut self, call_graph: &CallGraph) -> Result<()> {
252 let ast = FunctionInliner::do_pass((
253 std::mem::take(&mut self.ast),
254 &self.node_builder,
255 call_graph,
256 &self.assigner,
257 &self.type_table,
258 ))?;
259 self.ast = ast;
260
261 if self.compiler_options.output.inlined_ast {
262 self.write_ast_to_json("inlined_ast.json")?;
263 }
264
265 Ok(())
266 }
267
268 pub fn dead_code_elimination_pass(&mut self) -> Result<()> {
270 if self.compiler_options.build.dce_enabled {
271 self.ast = DeadCodeEliminator::do_pass((std::mem::take(&mut self.ast), &self.node_builder))?;
272 }
273
274 if self.compiler_options.output.dce_ast {
275 self.write_ast_to_json("dce_ast.json")?;
276 }
277
278 Ok(())
279 }
280
281 pub fn code_generation_pass(
283 &mut self,
284 symbol_table: &SymbolTable,
285 struct_graph: &StructGraph,
286 call_graph: &CallGraph,
287 ) -> Result<String> {
288 CodeGenerator::do_pass((&self.ast, symbol_table, &self.type_table, struct_graph, call_graph, &self.ast.ast))
289 }
290
291 pub fn compiler_stages(&mut self) -> Result<(SymbolTable, StructGraph, CallGraph)> {
293 let st = self.symbol_table_pass()?;
294
295 let (st, struct_graph, call_graph) = self.type_checker_pass(st)?;
296
297 self.static_analysis_pass(&st)?;
298
299 let st = self.loop_unrolling_pass(st)?;
301
302 self.static_single_assignment_pass(&st)?;
303
304 self.flattening_pass(&st)?;
305
306 self.destructuring_pass()?;
307
308 self.function_inlining_pass(&call_graph)?;
309
310 self.dead_code_elimination_pass()?;
311
312 Ok((st, struct_graph, call_graph))
313 }
314
315 pub fn compile(&mut self) -> Result<String> {
317 self.parse_program()?;
319 self.add_import_stubs()?;
321 let (symbol_table, struct_graph, call_graph) = self.compiler_stages()?;
323 let bytecode = self.code_generation_pass(&symbol_table, &struct_graph, &call_graph)?;
325 Ok(bytecode)
326 }
327
328 fn write_ast_to_json(&self, file_suffix: &str) -> Result<()> {
330 if self.compiler_options.output.ast_spans_enabled {
332 self.ast.to_json_file(self.output_directory.clone(), &format!("{}.{file_suffix}", self.program_name))?;
333 } else {
334 self.ast.to_json_file_without_keys(
335 self.output_directory.clone(),
336 &format!("{}.{file_suffix}", self.program_name),
337 &["_span", "span"],
338 )?;
339 }
340 Ok(())
341 }
342
343 fn write_symbol_table_to_json(&self, file_suffix: &str, symbol_table: &SymbolTable) -> Result<()> {
345 if self.compiler_options.output.symbol_table_spans_enabled {
347 symbol_table
348 .to_json_file(self.output_directory.clone(), &format!("{}.{file_suffix}", self.program_name))?;
349 } else {
350 symbol_table.to_json_file_without_keys(
351 self.output_directory.clone(),
352 &format!("{}.{file_suffix}", self.program_name),
353 &["_span", "span"],
354 )?;
355 }
356 Ok(())
357 }
358
359 pub fn add_import_stubs(&mut self) -> Result<()> {
361 let (mut unexplored, mut explored): (IndexSet<Symbol>, IndexSet<Symbol>) =
363 (self.ast.ast.imports.keys().cloned().collect(), IndexSet::new());
364 while !unexplored.is_empty() {
365 let mut current_dependencies: IndexSet<Symbol> = IndexSet::new();
366 for program_name in unexplored.iter() {
367 if let Some(stub) = self.import_stubs.get(program_name) {
368 explored.insert(*program_name);
370 for dependency in stub.imports.iter() {
371 if explored.insert(dependency.name.name) {
373 current_dependencies.insert(dependency.name.name);
374 }
375 }
376 } else {
377 return Err(CompilerError::imported_program_not_found(
378 self.program_name.clone(),
379 *program_name,
380 self.ast.ast.imports[program_name].1,
381 )
382 .into());
383 }
384 }
385
386 unexplored = current_dependencies;
388 }
389
390 self.ast.ast.stubs =
392 self.import_stubs.clone().into_iter().filter(|(program_name, _)| explored.contains(program_name)).collect();
393 Ok(())
394 }
395}