Fork of daniellemaywood.uk/gleam — Wasm codegen work
5.3 kB
184 lines
1use crate::{
2 build::Module,
3 config::PackageConfig,
4 erlang,
5 io::{FileSystemWriter, Utf8Writer},
6 javascript,
7 line_numbers::LineNumbers,
8 Result,
9};
10use itertools::Itertools;
11use std::{fmt::Debug, path::Path};
12
13/// A code generator that creates a .erl Erlang module and record header files
14/// for each Gleam module in the package.
15#[derive(Debug)]
16pub struct Erlang<'a> {
17 output_directory: &'a Path,
18}
19
20impl<'a> Erlang<'a> {
21 pub fn new(output_directory: &'a Path) -> Self {
22 Self { output_directory }
23 }
24
25 pub fn render<Writer: FileSystemWriter>(
26 &self,
27 writer: Writer,
28 modules: &[Module],
29 ) -> Result<()> {
30 for module in modules {
31 let erl_name = module.name.replace("/", "@");
32 self.erlang_module(&writer, module, &erl_name)?;
33 self.erlang_record_headers(&writer, module, &erl_name)?;
34 }
35 Ok(())
36 }
37
38 fn erlang_module<Writer: FileSystemWriter>(
39 &self,
40 writer: &Writer,
41 module: &Module,
42 erl_name: &str,
43 ) -> Result<()> {
44 let name = format!("{}.erl", erl_name);
45 let path = self.output_directory.join(&name);
46 let mut file = writer.writer(&path)?;
47 let line_numbers = LineNumbers::new(&module.code);
48 let res = erlang::module(&module.ast, &line_numbers, &mut file);
49 tracing::debug!(name = ?name, "Generated Erlang module");
50 res
51 }
52
53 fn erlang_record_headers<Writer: FileSystemWriter>(
54 &self,
55 writer: &Writer,
56 module: &Module,
57 erl_name: &str,
58 ) -> Result<()> {
59 for (name, text) in erlang::records(&module.ast) {
60 let name = format!("{}_{}.hrl", erl_name, name);
61 tracing::debug!(name = ?name, "Generated Erlang header");
62 writer
63 .writer(&self.output_directory.join(name))?
64 .write(text.as_bytes())?;
65 }
66 Ok(())
67 }
68}
69
70/// A code generator that creates a .app Erlang application file for the package
71#[derive(Debug)]
72pub struct ErlangApp<'a> {
73 output_directory: &'a Path,
74}
75
76impl<'a> ErlangApp<'a> {
77 pub fn new(output_directory: &'a Path) -> Self {
78 Self { output_directory }
79 }
80
81 pub fn render<Writer: FileSystemWriter>(
82 &self,
83 writer: Writer,
84 config: &PackageConfig,
85 modules: &[Module],
86 ) -> Result<()> {
87 fn tuple(key: &str, value: &str) -> String {
88 format!(" {{{}, {}}},\n", key, value)
89 }
90
91 let path = self.output_directory.join(format!("{}.app", &config.name));
92
93 let start_module = config
94 .erlang
95 .application_start_module
96 .as_ref()
97 .map(|module| tuple("mod", &format!("'{}'", module.replace("/", "@"))))
98 .unwrap_or_default();
99
100 let modules = modules
101 .iter()
102 .map(|m| m.name.replace("/", "@"))
103 .sorted()
104 .join(",\n ");
105
106 // TODO: When precompiling for production (i.e. as a precompiled hex
107 // package) we will need to exclude the dev deps.
108 let applications = config
109 .dependencies
110 .keys()
111 .chain(config.dev_dependencies.keys())
112 .chain(config.erlang.extra_applications.iter())
113 .sorted()
114 .join(",\n ");
115
116 let text = format!(
117 r#"{{application, {package}, [
118{start_module} {{vsn, "{version}"}},
119 {{applications, [{applications}]}},
120 {{description, "{description}"}},
121 {{modules, [{modules}]}},
122 {{registered, []}}
123]}}.
124"#,
125 applications = applications,
126 description = config.description,
127 modules = modules,
128 package = config.name,
129 start_module = start_module,
130 version = config.version,
131 );
132
133 writer.writer(&path)?.write(text.as_bytes())
134 }
135}
136
137#[derive(Debug)]
138pub struct JavaScript<'a> {
139 output_directory: &'a Path,
140}
141
142impl<'a> JavaScript<'a> {
143 pub fn new(output_directory: &'a Path) -> Self {
144 Self { output_directory }
145 }
146
147 pub fn render(&self, writer: &impl FileSystemWriter, modules: &[Module]) -> Result<()> {
148 for module in modules {
149 let js_name = module.name.clone();
150 self.js_module(writer, module, &js_name)?
151 }
152 self.write_prelude(writer)?;
153 Ok(())
154 }
155
156 fn write_prelude(&self, writer: &impl FileSystemWriter) -> Result<()> {
157 tracing::debug!("Generated js prelude");
158 writer
159 .writer(&self.output_directory.join("gleam.mjs"))?
160 .str_write(javascript::PRELUDE)?;
161 Ok(())
162 }
163
164 fn js_module(
165 &self,
166 writer: &impl FileSystemWriter,
167 module: &Module,
168 js_name: &str,
169 ) -> Result<()> {
170 let name = format!("{}.mjs", js_name);
171 let path = self.output_directory.join(&name);
172 let mut file = writer.writer(&path)?;
173 let line_numbers = LineNumbers::new(&module.code);
174 let res = javascript::module(
175 &module.ast,
176 &line_numbers,
177 &module.input_path,
178 &module.code,
179 &mut file,
180 );
181 tracing::debug!(name = ?js_name, "Generated js module");
182 res
183 }
184}