1use crate::{AstSnapshots, CompilerOptions};
22
23pub use leo_ast::Ast;
24use leo_ast::{NetworkName, Stub};
25use leo_errors::{CompilerError, Handler, Result};
26use leo_passes::*;
27use leo_span::{Symbol, source_map::FileName, with_session_globals};
28
29use std::{
30 ffi::OsStr,
31 fs,
32 path::{Path, PathBuf},
33};
34
35use indexmap::{IndexMap, IndexSet};
36use walkdir::WalkDir;
37
38pub struct Compiler {
40 output_directory: PathBuf,
42 pub program_name: Option<String>,
44 compiler_options: CompilerOptions,
46 state: CompilerState,
48 import_stubs: IndexMap<Symbol, Stub>,
50 pub statements_before_dce: u32,
52 pub statements_after_dce: u32,
54}
55
56impl Compiler {
57 pub fn parse(&mut self, source: &str, filename: FileName, modules: &[(&str, FileName)]) -> Result<()> {
58 let source_file = with_session_globals(|s| s.source_map.new_source(source, filename.clone()));
60
61 let modules = modules
63 .iter()
64 .map(|(source, filename)| with_session_globals(|s| s.source_map.new_source(source, filename.clone())))
65 .collect::<Vec<_>>();
66
67 self.state.ast = leo_parser::parse_ast(
69 self.state.handler.clone(),
70 &self.state.node_builder,
71 &source_file,
72 &modules,
73 self.state.network,
74 )?;
75
76 let program_scope = self.state.ast.ast.program_scopes.values().next().unwrap();
79 if self.program_name.is_none() {
80 self.program_name = Some(program_scope.program_id.name.to_string());
81 } else if self.program_name != Some(program_scope.program_id.name.to_string()) {
82 return Err(CompilerError::program_name_should_match_file_name(
83 program_scope.program_id.name,
84 if self.state.is_test {
86 format!(
87 "`{}` (the test file name)",
88 filename.to_string().split("/").last().expect("Could not get file name")
89 )
90 } else {
91 format!("`{}` (specified in `program.json`)", self.program_name.as_ref().unwrap())
92 },
93 program_scope.program_id.name.span,
94 )
95 .into());
96 }
97
98 if self.compiler_options.initial_ast {
99 self.write_ast_to_json("initial.json")?;
100 self.write_ast("initial.ast")?;
101 }
102
103 Ok(())
104 }
105
106 #[allow(clippy::too_many_arguments)]
108 pub fn new(
109 expected_program_name: Option<String>,
110 is_test: bool,
111 handler: Handler,
112 output_directory: PathBuf,
113 compiler_options: Option<CompilerOptions>,
114 import_stubs: IndexMap<Symbol, Stub>,
115 network: NetworkName,
116 ) -> Self {
117 Self {
118 state: CompilerState { handler, is_test, network, ..Default::default() },
119 output_directory,
120 program_name: expected_program_name,
121 compiler_options: compiler_options.unwrap_or_default(),
122 import_stubs,
123 statements_before_dce: 0,
124 statements_after_dce: 0,
125 }
126 }
127
128 fn do_pass<P: Pass>(&mut self, input: P::Input) -> Result<P::Output> {
129 let output = P::do_pass(input, &mut self.state)?;
130
131 let write = match &self.compiler_options.ast_snapshots {
132 AstSnapshots::All => true,
133 AstSnapshots::Some(passes) => passes.contains(P::NAME),
134 };
135
136 if write {
137 self.write_ast_to_json(&format!("{}.json", P::NAME))?;
138 self.write_ast(&format!("{}.ast", P::NAME))?;
139 }
140
141 Ok(output)
142 }
143
144 pub fn intermediate_passes(&mut self) -> Result<()> {
146 let type_checking_config = TypeCheckingInput::new(self.state.network);
147
148 self.do_pass::<NameValidation>(())?;
149
150 self.do_pass::<PathResolution>(())?;
151
152 self.do_pass::<SymbolTableCreation>(())?;
153
154 self.do_pass::<TypeChecking>(type_checking_config.clone())?;
155
156 self.do_pass::<ProcessingAsync>(type_checking_config.clone())?;
157
158 self.do_pass::<StaticAnalyzing>(())?;
159
160 self.do_pass::<ConstPropUnrollAndMorphing>(type_checking_config.clone())?;
161
162 self.do_pass::<StorageLowering>(type_checking_config.clone())?;
163
164 self.do_pass::<OptionLowering>(type_checking_config)?;
165
166 self.do_pass::<ProcessingScript>(())?;
167
168 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: true })?;
169
170 self.do_pass::<Destructuring>(())?;
171
172 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
173
174 self.do_pass::<WriteTransforming>(())?;
175
176 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
177
178 self.do_pass::<Flattening>(())?;
179
180 self.do_pass::<FunctionInlining>(())?;
181
182 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
184
185 self.do_pass::<SsaConstPropagation>(())?;
186
187 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
188
189 self.do_pass::<CommonSubexpressionEliminating>(())?;
190
191 let output = self.do_pass::<DeadCodeEliminating>(())?;
192 self.statements_before_dce = output.statements_before;
193 self.statements_after_dce = output.statements_after;
194
195 Ok(())
196 }
197
198 pub fn compile(&mut self, source: &str, filename: FileName, modules: &Vec<(&str, FileName)>) -> Result<String> {
213 self.parse(source, filename, modules)?;
215 self.add_import_stubs()?;
217 self.intermediate_passes()?;
219 let bytecode = CodeGenerating::do_pass((), &mut self.state)?;
221 Ok(bytecode.to_string())
222 }
223
224 pub fn compile_from_directory(
245 &mut self,
246 entry_file_path: impl AsRef<Path>,
247 source_directory: impl AsRef<Path>,
248 ) -> Result<String> {
249 let source = fs::read_to_string(&entry_file_path)
251 .map_err(|e| CompilerError::file_read_error(entry_file_path.as_ref().display().to_string(), e))?;
252
253 let files = WalkDir::new(source_directory)
255 .into_iter()
256 .filter_map(Result::ok)
257 .filter(|e| {
258 e.file_type().is_file()
259 && e.path() != entry_file_path.as_ref()
260 && e.path().extension() == Some(OsStr::new("leo"))
261 })
262 .collect::<Vec<_>>();
263
264 let mut module_sources = Vec::new(); let mut modules = Vec::new(); for file in &files {
269 let source = fs::read_to_string(file.path())
270 .map_err(|e| CompilerError::file_read_error(file.path().display().to_string(), e))?;
271 module_sources.push(source); }
273
274 for (i, file) in files.iter().enumerate() {
276 let source = &module_sources[i]; modules.push((&source[..], FileName::Real(file.path().into())));
278 }
279
280 self.compile(&source, FileName::Real(entry_file_path.as_ref().into()), &modules)
282 }
283
284 fn write_ast_to_json(&self, file_suffix: &str) -> Result<()> {
286 if self.compiler_options.ast_spans_enabled {
288 self.state.ast.to_json_file(
289 self.output_directory.clone(),
290 &format!("{}.{file_suffix}", self.program_name.as_ref().unwrap()),
291 )?;
292 } else {
293 self.state.ast.to_json_file_without_keys(
294 self.output_directory.clone(),
295 &format!("{}.{file_suffix}", self.program_name.as_ref().unwrap()),
296 &["_span", "span"],
297 )?;
298 }
299 Ok(())
300 }
301
302 fn write_ast(&self, file_suffix: &str) -> Result<()> {
304 let filename = format!("{}.{file_suffix}", self.program_name.as_ref().unwrap());
305 let full_filename = self.output_directory.join(&filename);
306 let contents = self.state.ast.ast.to_string();
307 fs::write(&full_filename, contents).map_err(|e| CompilerError::failed_ast_file(full_filename.display(), e))?;
308 Ok(())
309 }
310
311 pub fn add_import_stubs(&mut self) -> Result<()> {
314 let mut explored = IndexSet::<Symbol>::new();
315 let mut to_explore: Vec<Symbol> = self.state.ast.ast.imports.keys().cloned().collect();
316
317 while let Some(import) = to_explore.pop() {
318 explored.insert(import);
319 if let Some(stub) = self.import_stubs.get(&import) {
320 for new_import_id in stub.imports.iter() {
321 if !explored.contains(&new_import_id.name.name) {
322 to_explore.push(new_import_id.name.name);
323 }
324 }
325 } else {
326 return Err(CompilerError::imported_program_not_found(
327 self.program_name.as_ref().unwrap(),
328 import,
329 self.state.ast.ast.imports[&import].1,
330 )
331 .into());
332 }
333 }
334
335 self.state.ast.ast.stubs = self
338 .import_stubs
339 .iter()
340 .filter(|(symbol, _stub)| explored.contains(*symbol))
341 .map(|(symbol, stub)| (*symbol, stub.clone()))
342 .collect();
343 Ok(())
344 }
345}