Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / codegen.rs
8.2 kB 269 lines
1use crate::{ 2 Result, 3 build::{ 4 ErlangAppCodegenConfiguration, Module, module_erlang_name, package_compiler::StdlibPackage, 5 }, 6 config::PackageConfig, 7 erlang, 8 io::FileSystemWriter, 9 javascript::{self, ModuleConfig}, 10 line_numbers::LineNumbers, 11}; 12use ecow::EcoString; 13use erlang::escape_atom_string; 14use itertools::Itertools; 15use std::fmt::Debug; 16 17use camino::Utf8Path; 18 19/// A code generator that creates a .erl Erlang module and record header files 20/// for each Gleam module in the package. 21#[derive(Debug)] 22pub struct Erlang<'a> { 23 build_directory: &'a Utf8Path, 24 include_directory: &'a Utf8Path, 25} 26 27impl<'a> Erlang<'a> { 28 pub fn new(build_directory: &'a Utf8Path, include_directory: &'a Utf8Path) -> Self { 29 Self { 30 build_directory, 31 include_directory, 32 } 33 } 34 35 pub fn render<Writer: FileSystemWriter>( 36 &self, 37 writer: Writer, 38 modules: &[Module], 39 root: &Utf8Path, 40 ) -> Result<()> { 41 for module in modules { 42 let erl_name = module.erlang_name(); 43 self.erlang_module(&writer, module, &erl_name, root)?; 44 self.erlang_record_headers(&writer, module, &erl_name)?; 45 } 46 Ok(()) 47 } 48 49 fn erlang_module<Writer: FileSystemWriter>( 50 &self, 51 writer: &Writer, 52 module: &Module, 53 erl_name: &str, 54 root: &Utf8Path, 55 ) -> Result<()> { 56 let name = format!("{erl_name}.erl"); 57 let path = self.build_directory.join(&name); 58 let line_numbers = LineNumbers::new(&module.code); 59 let output = erlang::module(&module.ast, &line_numbers, root); 60 tracing::debug!(name = ?name, "Generated Erlang module"); 61 writer.write(&path, &output?) 62 } 63 64 fn erlang_record_headers<Writer: FileSystemWriter>( 65 &self, 66 writer: &Writer, 67 module: &Module, 68 erl_name: &str, 69 ) -> Result<()> { 70 for (name, text) in erlang::records(&module.ast) { 71 let name = format!("{erl_name}_{name}.hrl"); 72 tracing::debug!(name = ?name, "Generated Erlang header"); 73 writer.write(&self.include_directory.join(name), &text)?; 74 } 75 Ok(()) 76 } 77} 78 79/// A code generator that creates a .app Erlang application file for the package 80#[derive(Debug)] 81pub struct ErlangApp<'a> { 82 output_directory: &'a Utf8Path, 83 config: &'a ErlangAppCodegenConfiguration, 84} 85 86impl<'a> ErlangApp<'a> { 87 pub fn new(output_directory: &'a Utf8Path, config: &'a ErlangAppCodegenConfiguration) -> Self { 88 Self { 89 output_directory, 90 config, 91 } 92 } 93 94 pub fn render<Writer: FileSystemWriter>( 95 &self, 96 writer: Writer, 97 config: &PackageConfig, 98 modules: &[Module], 99 native_modules: Vec<EcoString>, 100 ) -> Result<()> { 101 fn tuple(key: &str, value: &str) -> String { 102 format!(" {{{key}, {value}}},\n") 103 } 104 105 let path = self.output_directory.join(format!("{}.app", &config.name)); 106 107 let start_module = config 108 .erlang 109 .application_start_module 110 .as_ref() 111 .map(|module| tuple("mod", &format!("{{'{}', []}}", module_erlang_name(module)))) 112 .unwrap_or_default(); 113 114 let modules = modules 115 .iter() 116 .map(|m| m.erlang_name()) 117 .chain(native_modules) 118 .unique() 119 .sorted() 120 .map(escape_atom_string) 121 .join(",\n "); 122 123 // TODO: When precompiling for production (i.e. as a precompiled hex 124 // package) we will need to exclude the dev deps. 125 let applications = config 126 .dependencies 127 .keys() 128 .chain( 129 config 130 .dev_dependencies 131 .keys() 132 .take_while(|_| self.config.include_dev_deps), 133 ) 134 // TODO: test this! 135 .map(|name| self.config.package_name_overrides.get(name).unwrap_or(name)) 136 .chain(config.erlang.extra_applications.iter()) 137 .sorted() 138 .join(",\n "); 139 140 let text = format!( 141 r#"{{application, {package}, [ 142{start_module} {{vsn, "{version}"}}, 143 {{applications, [{applications}]}}, 144 {{description, "{description}"}}, 145 {{modules, [{modules}]}}, 146 {{registered, []}} 147]}}. 148"#, 149 applications = applications, 150 description = config.description, 151 modules = modules, 152 package = config.name, 153 start_module = start_module, 154 version = config.version, 155 ); 156 157 writer.write(&path, &text) 158 } 159} 160 161#[derive(Debug, Clone, Copy, PartialEq, Eq)] 162pub enum TypeScriptDeclarations { 163 None, 164 Emit, 165} 166 167#[derive(Debug)] 168pub struct JavaScript<'a> { 169 output_directory: &'a Utf8Path, 170 prelude_location: &'a Utf8Path, 171 project_root: &'a Utf8Path, 172 typescript: TypeScriptDeclarations, 173} 174 175impl<'a> JavaScript<'a> { 176 pub fn new( 177 output_directory: &'a Utf8Path, 178 typescript: TypeScriptDeclarations, 179 prelude_location: &'a Utf8Path, 180 project_root: &'a Utf8Path, 181 ) -> Self { 182 Self { 183 prelude_location, 184 output_directory, 185 project_root, 186 typescript, 187 } 188 } 189 190 pub fn render( 191 &self, 192 writer: &impl FileSystemWriter, 193 modules: &[Module], 194 stdlib_package: StdlibPackage, 195 ) -> Result<()> { 196 for module in modules { 197 let js_name = module.name.clone(); 198 if self.typescript == TypeScriptDeclarations::Emit { 199 self.ts_declaration(writer, module, &js_name)?; 200 } 201 self.js_module(writer, module, &js_name, stdlib_package)? 202 } 203 self.write_prelude(writer)?; 204 Ok(()) 205 } 206 207 fn write_prelude(&self, writer: &impl FileSystemWriter) -> Result<()> { 208 let rexport = format!("export * from \"{}\";\n", self.prelude_location); 209 let prelude_path = &self.output_directory.join("gleam.mjs"); 210 211 // This check skips unnecessary `gleam.mjs` writes which confuse 212 // watchers and HMR build tools 213 if !writer.exists(prelude_path) { 214 writer.write(prelude_path, &rexport)?; 215 } 216 217 if self.typescript == TypeScriptDeclarations::Emit { 218 let rexport = format!( 219 "export * from \"{}\";\nexport type * from \"{}\";\n", 220 self.prelude_location, 221 self.prelude_location.as_str().replace(".mjs", ".d.mts") 222 ); 223 let prelude_declaration_path = &self.output_directory.join("gleam.d.mts"); 224 225 // Type declaration may trigger badly configured watchers 226 if !writer.exists(prelude_declaration_path) { 227 writer.write(prelude_declaration_path, &rexport)?; 228 } 229 } 230 231 Ok(()) 232 } 233 234 fn ts_declaration( 235 &self, 236 writer: &impl FileSystemWriter, 237 module: &Module, 238 js_name: &str, 239 ) -> Result<()> { 240 let name = format!("{js_name}.d.mts"); 241 let path = self.output_directory.join(name); 242 let output = javascript::ts_declaration(&module.ast); 243 tracing::debug!(name = ?js_name, "Generated TS declaration"); 244 writer.write(&path, &output) 245 } 246 247 fn js_module( 248 &self, 249 writer: &impl FileSystemWriter, 250 module: &Module, 251 js_name: &str, 252 stdlib_package: StdlibPackage, 253 ) -> Result<()> { 254 let name = format!("{js_name}.mjs"); 255 let path = self.output_directory.join(name); 256 let line_numbers = LineNumbers::new(&module.code); 257 let output = javascript::module(ModuleConfig { 258 module: &module.ast, 259 line_numbers: &line_numbers, 260 path: &module.input_path, 261 project_root: self.project_root, 262 src: &module.code, 263 typescript: self.typescript, 264 stdlib_package, 265 }); 266 tracing::debug!(name = ?js_name, "Generated js module"); 267 writer.write(&path, &output) 268 } 269}