dalbit_core/
transpile.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
use std::{path::PathBuf, str::FromStr};

use anyhow::{anyhow, Result};
use darklua_core::{
    rules::{self, bundle::BundleRequireMode},
    BundleConfiguration, Configuration, GeneratorParameters, Options, Resources,
};
use indexmap::IndexMap;
use tokio::fs;

use crate::{injector::Injector, manifest::Manifest, modifiers::Modifier, utils};

pub const DALBIT_GLOBAL_IDENTIFIER_PREFIX: &str = "DALBIT_";

pub const DEFAULT_LUAU_TO_LUA_MODIFIERS: [&str; 8] = [
    "remove_interpolated_string",
    "remove_compound_assignment",
    "remove_types",
    "remove_if_expression",
    "remove_continue",
    "remove_redeclared_keys",
    "remove_generalized_iteration",
    "remove_number_literals",
];

pub const DEFAULT_MINIFYING_MODIFIERS: [&str; 11] = [
    "remove_spaces",
    "remove_nil_declaration",
    "remove_function_call_parens",
    "remove_comments",
    "convert_index_to_field",
    "compute_expression",
    "filter_after_early_return",
    "remove_unused_variable",
    "remove_unused_while",
    "remove_unused_if_branch",
    "remove_empty_do",
];

async fn private_process(
    manifest: &Manifest,
    input: &PathBuf,
    output: &PathBuf,
    additional_modifiers: Option<&mut Vec<Modifier>>,
    bundle: bool,
) -> Result<Vec<PathBuf>> {
    let resources = Resources::from_file_system();

    let mut modifiers = Vec::new();
    if let Some(additional_modifiers) = additional_modifiers {
        modifiers.append(additional_modifiers);
    }
    {
        let mut transpiling_modifiers = IndexMap::new();
        for name in DEFAULT_LUAU_TO_LUA_MODIFIERS {
            transpiling_modifiers.insert(name, true);
        }
        for (name, enabled) in manifest.modifiers() {
            let name = name.as_str();
            log::debug!("inserted modifier name: {}", name);
            transpiling_modifiers.insert(name, *enabled);
        }
        for (name, enabled) in transpiling_modifiers {
            let modifier = Modifier::from_str(name)?;
            if enabled {
                modifiers.push(modifier);
            }
        }
    }
    if manifest.minify {
        for name in DEFAULT_MINIFYING_MODIFIERS {
            modifiers.push(Modifier::from_str(name)?);
        }
    }

    let (rules, mut fullmoon_visitors) = modifiers.into_iter().fold(
        (Vec::new(), Vec::new()),
        |(mut rules, mut fullmoon_visitors), modifier| {
            match modifier {
                Modifier::DarkluaRule(darklua_rule) => rules.push(darklua_rule),
                Modifier::FullMoonVisitor(fullmoon_visitor) => {
                    fullmoon_visitors.push(fullmoon_visitor)
                }
            }
            (rules, fullmoon_visitors)
        },
    );

    let mut options = Options::new(input).with_configuration({
        // let mut config: Configuration = if bundle {
        //     toml::from_str("bundle = { require_mode = 'path' }").unwrap()
        // } else {
        //     Configuration::empty()
        // };
        let mut config = Configuration::empty();
        if bundle {
            config = config
                .with_bundle_configuration(BundleConfiguration::new(BundleRequireMode::default()));
        }
        config = config.with_generator(GeneratorParameters::RetainLines);

        rules
            .into_iter()
            .fold(config, |config, rule| config.with_rule(rule))
    });
    options = options.with_output(&output);
    let result = darklua_core::process(&resources, options);

    let success_count = result.success_count();
    if result.has_errored() {
        let error_count = result.error_count();
        eprintln!(
            "{}{} error{} happened:",
            if success_count > 0 { "but " } else { "" },
            error_count,
            if error_count > 1 { "s" } else { "" }
        );

        result.errors().for_each(|error| eprintln!("-> {}", error));

        return Err(anyhow!("darklua process was not successful"));
    }

    let mut created_files: Vec<PathBuf> = result.into_created_files().collect();
    let extension = manifest.file_extension();
    if fullmoon_visitors.is_empty() {
        if let Some(extension) = extension {
            for path in &mut created_files {
                let old_path = path.clone();
                path.set_extension(extension);
                fs::rename(old_path, path).await?;
            }
        }
    } else {
        for path in &mut created_files {
            let mut ast = utils::parse_file(path, manifest.target_version()).await?;

            for visitor in &mut fullmoon_visitors {
                ast = visitor.visit_ast_boxed(ast);
            }

            if let Some(extension) = extension {
                let old_path = path.clone();
                path.set_extension(extension);
                let new_path = path.to_owned();
                if new_path != old_path && old_path.exists() {
                    fs::remove_file(old_path).await?;
                }
            }

            fs::write(path, ast.to_string()).await?;
        }
    }

    Ok(created_files)
}

pub async fn process(manifest: Manifest) -> Result<()> {
    let output_files =
        private_process(&manifest, manifest.input(), manifest.output(), None, false).await?;
    let polyfill = manifest.polyfill();
    let polyfill_cache = polyfill.cache().await?;
    let polyfill_config = polyfill_cache.config();

    // needed additional modifiers: inject_global_value
    let mut additional_modifiers: Vec<Modifier> = Vec::new();
    for (key, value) in polyfill_config {
        let value = if let Some(val) = polyfill.config().get(key) {
            val
        } else {
            value
        };
        let mut identifier = DALBIT_GLOBAL_IDENTIFIER_PREFIX.to_string();
        identifier.push_str(key);
        let inject_global_value = rules::InjectGlobalValue::boolean(identifier, *value);
        additional_modifiers.push(Modifier::DarkluaRule(Box::new(inject_global_value)));
    }

    if let Some(first_output) = output_files.first() {
        log::debug!("first output found!");
        let extension = if let Some(extension) = manifest.file_extension() {
            extension.to_owned()
        } else {
            first_output
                .extension()
                .ok_or_else(|| anyhow!("Failed to get extension from output file."))?
                .to_string_lossy()
                .into_owned()
        };

        if let Some(module_path) = first_output.parent().map(|parent| {
            parent
                .join(polyfill.injection_path())
                .with_extension(extension)
        }) {
            let _ = private_process(
                &manifest,
                polyfill_cache.globals_path(),
                &module_path,
                Some(&mut additional_modifiers),
                true,
            )
            .await?;

            let mut exports = polyfill_cache.globals_exports().to_owned();
            for (key, value) in polyfill.globals() {
                if exports.contains(key) {
                    if !value {
                        exports.remove(key);
                    }
                } else {
                    return Err(anyhow!("Invalid global `{}`", key));
                }
            }

            log::info!("[injector] exports to be injected: {:?}", exports);

            let injector = Injector::new(
                module_path,
                exports,
                manifest.target_version().to_lua_version(),
                polyfill_cache.removes().to_owned(),
            );

            for source_path in &output_files {
                injector.inject(source_path).await?
            }
        }
    }
    Ok(())
}