1use std::{ffi::OsStr, path::PathBuf, str::FromStr};
2
3use anyhow::{anyhow, Result};
4use async_walkdir::WalkDir;
5use darklua_core::{
6 rules::{self, bundle::BundleRequireMode},
7 BundleConfiguration, Configuration, GeneratorParameters, Options, Resources,
8};
9use futures_lite::stream::StreamExt;
10use indexmap::IndexMap;
11use tokio::fs;
12
13use crate::{injector::Injector, manifest::Manifest, modifiers::Modifier, utils};
14
15pub const DALBIT_GLOBAL_IDENTIFIER_PREFIX: &str = "DALBIT_";
16
17pub const DEFAULT_LUAU_TO_LUA_MODIFIERS: [&str; 8] = [
18 "remove_interpolated_string",
19 "remove_compound_assignment",
20 "remove_types",
21 "remove_if_expression",
22 "remove_continue",
23 "remove_redeclared_keys",
24 "remove_generalized_iteration",
25 "remove_number_literals",
26];
27
28pub const DEFAULT_MINIFYING_MODIFIERS: [&str; 11] = [
29 "remove_spaces",
30 "remove_nil_declaration",
31 "remove_function_call_parens",
32 "remove_comments",
33 "convert_index_to_field",
34 "compute_expression",
35 "filter_after_early_return",
36 "remove_unused_variable",
37 "remove_unused_while",
38 "remove_unused_if_branch",
39 "remove_empty_do",
40];
41
42async fn private_process(
43 manifest: &Manifest,
44 input: &PathBuf,
45 output: &PathBuf,
46 additional_modifiers: Option<&mut Vec<Modifier>>,
47 bundle: bool,
48) -> Result<Vec<PathBuf>> {
49 let resources = Resources::from_file_system();
50
51 let mut modifiers = Vec::new();
52 if let Some(additional_modifiers) = additional_modifiers {
53 modifiers.append(additional_modifiers);
54 }
55 {
56 let mut transpiling_modifiers = IndexMap::new();
57 for name in DEFAULT_LUAU_TO_LUA_MODIFIERS {
58 transpiling_modifiers.insert(name, true);
59 }
60 for (name, enabled) in manifest.modifiers() {
61 let name = name.as_str();
62 log::debug!("inserted modifier name: {}", name);
63 transpiling_modifiers.insert(name, *enabled);
64 }
65 for (name, enabled) in transpiling_modifiers {
66 let modifier = Modifier::from_str(name)?;
67 if enabled {
68 modifiers.push(modifier);
69 }
70 }
71 }
72 if manifest.minify {
73 for name in DEFAULT_MINIFYING_MODIFIERS {
74 modifiers.push(Modifier::from_str(name)?);
75 }
76 }
77
78 let (rules, mut fullmoon_visitors) = modifiers.into_iter().fold(
79 (Vec::new(), Vec::new()),
80 |(mut rules, mut fullmoon_visitors), modifier| {
81 match modifier {
82 Modifier::DarkluaRule(darklua_rule) => rules.push(darklua_rule),
83 Modifier::FullMoonVisitor(fullmoon_visitor) => {
84 fullmoon_visitors.push(fullmoon_visitor)
85 }
86 }
87 (rules, fullmoon_visitors)
88 },
89 );
90
91 let mut options = Options::new(input).with_configuration({
92 let mut config = Configuration::empty();
98 if bundle {
99 config = config
100 .with_bundle_configuration(BundleConfiguration::new(BundleRequireMode::default()));
101 }
102 config = config.with_generator(GeneratorParameters::RetainLines);
103
104 rules
105 .into_iter()
106 .fold(config, |config, rule| config.with_rule(rule))
107 });
108 options = options.with_output(&output);
109 let result = darklua_core::process(&resources, options).map_err(|e| anyhow!(e))?;
110
111 let success_count = result.success_count();
112 let errors = result.collect_errors();
113 let error_count = errors.len();
114 if error_count > 0 {
115 eprintln!(
116 "{}{} error{} happened:",
117 if success_count > 0 { "but " } else { "" },
118 error_count,
119 if error_count > 1 { "s" } else { "" }
120 );
121
122 errors
123 .into_iter()
124 .for_each(|error| eprintln!("-> {}", error));
125
126 return Err(anyhow!("darklua process was not successful"));
127 }
128
129 let mut created_files: Vec<PathBuf> = if output.is_dir() {
130 let mut created_files = Vec::new();
131 let mut entries = WalkDir::new(output);
132 while let Some(entry) = entries.next().await {
133 let path = entry?.path();
134 if !matches!(
135 path.extension().and_then(OsStr::to_str),
136 Some("lua") | Some("luau")
137 ) {
138 continue;
139 }
140 created_files.push(path);
141 }
142 created_files
143 } else {
144 vec![output.clone()]
145 };
146 println!("created_files: {:?}", created_files);
147
148 let extension = manifest.file_extension();
149 if fullmoon_visitors.is_empty() {
150 if let Some(extension) = extension {
151 for path in &mut created_files {
152 let old_path = path.clone();
153 path.set_extension(extension);
154 fs::rename(old_path, path).await?;
155 }
156 }
157 } else {
158 for path in &mut created_files {
159 let mut ast = utils::parse_file(path, manifest.target_version()).await?;
160
161 for visitor in &mut fullmoon_visitors {
162 ast = visitor.visit_ast_boxed(ast);
163 }
164
165 if let Some(extension) = extension {
166 let old_path = path.clone();
167 path.set_extension(extension);
168 let new_path = path.to_owned();
169 if new_path != old_path && old_path.exists() {
170 fs::remove_file(old_path).await?;
171 }
172 }
173
174 fs::write(path, ast.to_string()).await?;
175 }
176 }
177
178 Ok(created_files)
179}
180
181pub async fn process(manifest: Manifest) -> Result<()> {
182 let output_files = private_process(
183 &manifest,
184 &manifest.input(),
185 &manifest.output(),
186 None,
187 false,
188 )
189 .await?;
190 let Some(polyfill) = manifest.polyfill() else {
191 return Ok(());
192 };
193 let polyfill_cache = polyfill.cache().await?;
194 let polyfill_config = polyfill_cache.config();
195
196 let mut additional_modifiers: Vec<Modifier> = Vec::new();
198 for (key, value) in polyfill_config {
199 let value = if let Some(val) = polyfill.config().get(key) {
200 val
201 } else {
202 value
203 };
204 let mut identifier = DALBIT_GLOBAL_IDENTIFIER_PREFIX.to_string();
205 identifier.push_str(key);
206 let inject_global_value = rules::InjectGlobalValue::boolean(identifier, *value);
207 additional_modifiers.push(Modifier::DarkluaRule(Box::new(inject_global_value)));
208 }
209
210 if let Some(first_output) = output_files.first() {
211 log::debug!("first output found!");
212 let extension = if let Some(extension) = manifest.file_extension() {
213 extension.to_owned()
214 } else {
215 first_output
216 .extension()
217 .ok_or_else(|| anyhow!("Failed to get extension from output file."))?
218 .to_string_lossy()
219 .into_owned()
220 };
221
222 if let Some(module_path) = first_output.parent().map(|parent| {
223 parent
224 .join(polyfill.injection_path())
225 .with_extension(extension)
226 }) {
227 let _ = private_process(
228 &manifest,
229 polyfill_cache.globals_path(),
230 &module_path,
231 Some(&mut additional_modifiers),
232 true,
233 )
234 .await?;
235
236 let mut exports = polyfill_cache.globals_exports().to_owned();
237 for (key, value) in polyfill.globals() {
238 if exports.contains(key) {
239 if !value {
240 exports.remove(key);
241 }
242 } else {
243 return Err(anyhow!("Invalid global `{}`", key));
244 }
245 }
246
247 log::info!("[injector] exports to be injected: {:?}", exports);
248
249 let injector = Injector::new(
250 module_path,
251 exports,
252 manifest.target_version().to_lua_version(),
253 polyfill_cache.removes().to_owned(),
254 );
255
256 for source_path in &output_files {
257 injector.inject(source_path).await?
258 }
259 }
260 }
261 Ok(())
262}