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