dalbit_core/
transpile.rs

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 = if bundle {
91        //     toml::from_str("bundle = { require_mode = 'path' }").unwrap()
92        // } else {
93        //     Configuration::empty()
94        // };
95        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    // needed additional modifiers: inject_global_value
166    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}