Fork of daniellemaywood.uk/gleam — Wasm codegen work
9.3 kB
350 lines
1#![allow(warnings)]
2
3mod elixir_libraries;
4mod module_loader;
5mod native_file_copier;
6pub mod package_compiler;
7mod package_loader;
8mod project_compiler;
9mod telemetry;
10
11#[cfg(test)]
12mod tests;
13
14pub use self::package_compiler::PackageCompiler;
15pub use self::package_loader::StaleTracker;
16pub use self::project_compiler::{Built, Options, ProjectCompiler};
17pub use self::telemetry::{NullTelemetry, Telemetry};
18
19use crate::ast::{
20 CustomType, DefinitionLocation, TypedArg, TypedDefinition, TypedExpr, TypedFunction,
21 TypedPattern, TypedStatement,
22};
23use crate::{
24 ast::{Definition, SrcSpan, TypedModule},
25 config::{self, PackageConfig},
26 erlang,
27 error::{Error, FileIoAction, FileKind},
28 io::OutputFile,
29 parse::extra::{Comment, ModuleExtra},
30 type_,
31};
32use camino::Utf8PathBuf;
33use ecow::EcoString;
34use itertools::Itertools;
35use serde::{Deserialize, Serialize};
36use std::time::SystemTime;
37use std::{collections::HashMap, ffi::OsString, fs::DirEntry, iter::Peekable, process};
38use strum::{Display, EnumIter, EnumString, EnumVariantNames, VariantNames};
39
40#[derive(
41 Debug,
42 Serialize,
43 Deserialize,
44 Display,
45 EnumString,
46 EnumVariantNames,
47 EnumIter,
48 Clone,
49 Copy,
50 PartialEq,
51 Eq,
52)]
53#[strum(serialize_all = "lowercase")]
54pub enum Target {
55 #[strum(serialize = "erlang", serialize = "erl")]
56 #[serde(rename = "erlang", alias = "erl")]
57 Erlang,
58 #[strum(serialize = "javascript", serialize = "js")]
59 #[serde(rename = "javascript", alias = "js")]
60 JavaScript,
61}
62
63impl Target {
64 pub fn variant_strings() -> Vec<EcoString> {
65 Self::VARIANTS.iter().map(|s| (*s).into()).collect()
66 }
67
68 /// Returns `true` if the target is [`JavaScript`].
69 ///
70 /// [`JavaScript`]: Target::JavaScript
71 #[must_use]
72 pub fn is_javascript(&self) -> bool {
73 matches!(self, Self::JavaScript)
74 }
75
76 /// Returns `true` if the target is [`Erlang`].
77 ///
78 /// [`Erlang`]: Target::Erlang
79 #[must_use]
80 pub fn is_erlang(&self) -> bool {
81 matches!(self, Self::Erlang)
82 }
83}
84
85#[derive(Debug, PartialEq, Eq, Clone, Copy)]
86pub enum Codegen {
87 All,
88 DepsOnly,
89 None,
90}
91
92impl Codegen {
93 fn should_codegen(&self, is_root_package: bool) -> bool {
94 match self {
95 Codegen::All => true,
96 Codegen::DepsOnly => !is_root_package,
97 Codegen::None => false,
98 }
99 }
100}
101
102#[derive(
103 Debug, Serialize, Deserialize, Display, EnumString, EnumVariantNames, Clone, Copy, PartialEq, Eq,
104)]
105pub enum Runtime {
106 #[strum(serialize = "nodejs", serialize = "node")]
107 #[serde(rename = "nodejs", alias = "node")]
108 NodeJs,
109 #[strum(serialize = "deno")]
110 #[serde(rename = "deno")]
111 Deno,
112 #[strum(serialize = "bun")]
113 #[serde(rename = "bun")]
114 Bun,
115}
116
117impl Default for Runtime {
118 fn default() -> Self {
119 Self::NodeJs
120 }
121}
122
123#[derive(Debug)]
124pub enum TargetCodegenConfiguration {
125 JavaScript {
126 emit_typescript_definitions: bool,
127 prelude_location: Utf8PathBuf,
128 },
129 Erlang {
130 app_file: Option<ErlangAppCodegenConfiguration>,
131 },
132}
133
134impl TargetCodegenConfiguration {
135 pub fn target(&self) -> Target {
136 match self {
137 Self::JavaScript { .. } => Target::JavaScript,
138 Self::Erlang { .. } => Target::Erlang,
139 }
140 }
141}
142
143#[derive(Debug)]
144pub struct ErlangAppCodegenConfiguration {
145 pub include_dev_deps: bool,
146 /// Some packages have a different OTP application name than their package
147 /// name, as rebar3 (and Mix?) support this. The .app file must use the OTP
148 /// name, not the package name.
149 pub package_name_overrides: HashMap<EcoString, EcoString>,
150}
151
152#[derive(
153 Debug,
154 Serialize,
155 Deserialize,
156 Display,
157 EnumString,
158 EnumVariantNames,
159 EnumIter,
160 Clone,
161 Copy,
162 PartialEq,
163)]
164#[strum(serialize_all = "lowercase")]
165pub enum Mode {
166 Dev,
167 Prod,
168 Lsp,
169}
170
171impl Mode {
172 /// Returns `true` if the mode includes test code.
173 ///
174 pub fn includes_tests(&self) -> bool {
175 match self {
176 Self::Dev | Self::Lsp => true,
177 Self::Prod => false,
178 }
179 }
180}
181
182#[test]
183fn mode_includes_tests() {
184 assert!(Mode::Dev.includes_tests());
185 assert!(Mode::Lsp.includes_tests());
186 assert!(!Mode::Prod.includes_tests());
187}
188
189#[derive(Debug)]
190pub struct Package {
191 pub config: PackageConfig,
192 pub modules: Vec<Module>,
193}
194
195impl Package {
196 pub fn attach_doc_and_module_comments(&mut self) {
197 for mut module in &mut self.modules {
198 module.attach_doc_and_module_comments();
199 }
200 }
201
202 pub fn into_modules_hashmap(self) -> HashMap<String, Module> {
203 self.modules
204 .into_iter()
205 .map(|m| (m.name.to_string(), m))
206 .collect()
207 }
208}
209
210#[derive(Debug)]
211pub struct Module {
212 pub name: EcoString,
213 pub code: EcoString,
214 pub mtime: SystemTime,
215 pub input_path: Utf8PathBuf,
216 pub origin: Origin,
217 pub ast: TypedModule,
218 pub extra: ModuleExtra,
219 pub dependencies: Vec<(EcoString, SrcSpan)>,
220}
221
222impl Module {
223 pub fn compiled_erlang_path(&self) -> Utf8PathBuf {
224 let mut path = self.name.replace("/", "@");
225 path.push_str(".erl");
226 Utf8PathBuf::from(path.as_ref())
227 }
228
229 pub fn is_test(&self) -> bool {
230 self.origin == Origin::Test
231 }
232
233 pub fn find_node(&self, byte_index: u32) -> Option<Located<'_>> {
234 self.ast.find_node(byte_index)
235 }
236
237 pub fn attach_doc_and_module_comments(&mut self) {
238 // Module Comments
239 self.ast.documentation = self
240 .extra
241 .module_comments
242 .iter()
243 .map(|span| Comment::from((span, self.code.as_str())).content.into())
244 .collect();
245
246 // Order statements to avoid missociating doc comments after the order
247 // has changed during compilation.
248 let mut statements: Vec<_> = self.ast.definitions.iter_mut().collect();
249 statements.sort_by(|a, b| a.location().start.cmp(&b.location().start));
250
251 // Doc Comments
252 let mut doc_comments = self.extra.doc_comments.iter().peekable();
253 for statement in &mut statements {
254 let docs: Vec<&str> =
255 comments_before(&mut doc_comments, statement.location().start, &self.code);
256 if !docs.is_empty() {
257 let doc = docs.join("\n").into();
258 statement.put_doc(doc);
259 }
260
261 if let Definition::CustomType(CustomType { constructors, .. }) = statement {
262 for constructor in constructors {
263 let docs: Vec<&str> =
264 comments_before(&mut doc_comments, constructor.location.start, &self.code);
265 if !docs.is_empty() {
266 let doc = docs.join("\n").into();
267 constructor.put_doc(doc);
268 }
269
270 for argument in constructor.arguments.iter_mut() {
271 let docs: Vec<&str> =
272 comments_before(&mut doc_comments, argument.location.start, &self.code);
273 if !docs.is_empty() {
274 let doc = docs.join("\n").into();
275 argument.put_doc(doc);
276 }
277 }
278 }
279 }
280 }
281 }
282
283 pub(crate) fn dependencies_list(&self) -> Vec<EcoString> {
284 self.dependencies
285 .iter()
286 .map(|(name, _)| name.clone())
287 .collect()
288 }
289}
290
291#[derive(Debug, Clone, PartialEq)]
292pub enum Located<'a> {
293 Pattern(&'a TypedPattern),
294 Statement(&'a TypedStatement),
295 Expression(&'a TypedExpr),
296 ModuleStatement(&'a TypedDefinition),
297 FunctionBody(&'a TypedFunction),
298 Arg(&'a TypedArg),
299}
300
301impl<'a> Located<'a> {
302 pub fn definition_location(&self) -> Option<DefinitionLocation<'_>> {
303 match self {
304 Self::Pattern(pattern) => pattern.definition_location(),
305 Self::Statement(statement) => statement.definition_location(),
306 Self::FunctionBody(statement) => None,
307 Self::Expression(expression) => expression.definition_location(),
308 Self::ModuleStatement(statement) => Some(DefinitionLocation {
309 module: None,
310 span: statement.location(),
311 }),
312 Self::Arg(_) => None,
313 }
314 }
315}
316
317#[derive(Debug, Clone, Copy, PartialEq, Eq)]
318pub enum Origin {
319 Src,
320 Test,
321}
322
323impl Origin {
324 /// Returns `true` if the origin is [`Src`].
325 ///
326 /// [`Src`]: Origin::Src
327 #[must_use]
328 pub fn is_src(&self) -> bool {
329 matches!(self, Self::Src)
330 }
331}
332
333fn comments_before<'a>(
334 comment_spans: &mut Peekable<impl Iterator<Item = &'a SrcSpan>>,
335 byte: u32,
336 src: &'a str,
337) -> Vec<&'a str> {
338 let mut comments = vec![];
339 while let Some(SrcSpan { start, .. }) = comment_spans.peek() {
340 if start <= &byte {
341 let comment = comment_spans
342 .next()
343 .expect("Comment before accessing next span");
344 comments.push(Comment::from((comment, src)).content)
345 } else {
346 break;
347 }
348 }
349 comments
350}