Skip to main content

luaur_repl_cli/functions/
repl_main.rs

1//! Faithful port of the C++ `int replMain(int argc, char** argv)` from
2//! `CLI/src/Repl.cpp`. Parses the CLI options, installs the assertion handler,
3//! then either starts the interactive REPL (no file arguments) or runs each
4//! source file on a fresh `lua_State`, optionally enabling profiling / coverage
5//! / counters / native codegen, returning `failed ? 1 : 0`.
6
7use alloc::string::String;
8use alloc::vec::Vec;
9use core::ffi::{c_char, CStr};
10use core::sync::atomic::{AtomicBool, AtomicI32, Ordering};
11
12use luaur_cli_lib::functions::get_source_files::get_source_files;
13use luaur_cli_lib::functions::set_luau_flags_flags_alt_b::set_luau_flags_c_char;
14use luaur_code_gen::functions::is_supported::is_supported;
15use luaur_common::functions::assert_handler::assert_handler;
16
17use luaur_vm::functions::lua_close::lua_close;
18use luaur_vm::functions::lua_l_newstate::lua_l_newstate;
19use luaur_vm::records::lua_state::lua_State;
20
21use crate::functions::assertion_handler::assertion_handler;
22use crate::functions::copts::GlobalOptions;
23use crate::functions::counters_dump::counters_dump;
24use crate::functions::counters_init::counters_init;
25use crate::functions::coverage_dump::coverage_dump;
26use crate::functions::coverage_init::coverage_init;
27use crate::functions::display_help::display_help;
28use crate::functions::profiler_dump::profiler_dump;
29use crate::functions::profiler_start::profiler_start;
30use crate::functions::profiler_stop::profiler_stop;
31use crate::functions::run_file::run_file;
32use crate::functions::run_repl::run_repl;
33use crate::functions::setup_state::setup_state;
34
35// CLI-level statics from Repl.cpp: `static bool codegen`, `static bool
36// codegenCold`, `static int program_argc`, `char** program_argv`.
37static REPL_CODEGEN: AtomicBool = AtomicBool::new(false);
38static REPL_CODEGEN_COLD: AtomicBool = AtomicBool::new(false);
39static PROGRAM_ARGC: AtomicI32 = AtomicI32::new(0);
40static mut PROGRAM_ARGV: *mut *mut c_char = core::ptr::null_mut();
41
42/// `static bool codegen` accessor — used by setupState, the requirer and runFile.
43pub fn repl_codegen_enabled() -> bool {
44    REPL_CODEGEN.load(Ordering::Relaxed)
45}
46
47/// `static bool codegenCold` accessor — used by runFile.
48pub fn repl_codegen_cold() -> bool {
49    REPL_CODEGEN_COLD.load(Ordering::Relaxed)
50}
51
52/// `static int program_argc` accessor — used by runFile (setupArguments).
53pub fn program_argc() -> i32 {
54    PROGRAM_ARGC.load(Ordering::Relaxed)
55}
56
57/// `char** program_argv` accessor — used by runFile (setupArguments).
58pub fn program_argv() -> *mut *mut c_char {
59    unsafe { PROGRAM_ARGV }
60}
61
62// `struct GlobalOptions { int optimizationLevel = 1; int debugLevel = 1; }
63// globalOptions;` — the definition backing the `extern` declaration in copts.rs.
64#[no_mangle]
65pub(crate) static mut globalOptions: GlobalOptions = GlobalOptions {
66    optimizationLevel: 1,
67    debugLevel: 1,
68};
69
70#[allow(non_snake_case)]
71pub fn repl_main(argc: i32, argv: *mut *mut c_char) -> i32 {
72    // Luau::assertHandler() = assertionHandler;
73    *assert_handler() = Some(assertion_handler_adapter);
74
75    // (Windows) SetConsoleOutputCP(CP_UTF8) — not applicable on this build.
76
77    let mut profile: i32 = 0;
78    let mut coverage = false;
79    let mut interactive = false;
80    let mut codegen_perf = false;
81    let mut counters = false;
82    let mut program_args = argc;
83
84    // Reset the CLI statics to the C++ defaults for this invocation.
85    REPL_CODEGEN.store(false, Ordering::Relaxed);
86    REPL_CODEGEN_COLD.store(false, Ordering::Relaxed);
87    unsafe {
88        globalOptions = GlobalOptions {
89            optimizationLevel: 1,
90            debugLevel: 1,
91        };
92    }
93
94    let arg = |i: i32| -> String {
95        unsafe {
96            let p = *argv.add(i as usize);
97            CStr::from_ptr(p).to_string_lossy().into_owned()
98        }
99    };
100    let argv0 = arg(0);
101
102    let mut i = 1i32;
103    while i < argc {
104        let a = arg(i);
105
106        if a == "-h" || a == "--help" {
107            display_help(&argv0);
108            return 0;
109        } else if a == "-i" || a == "--interactive" {
110            interactive = true;
111        } else if a.starts_with("-O") {
112            // atoi(argv[i] + 2): parse leading digits, defaulting to 0.
113            let level = atoi_like(&a[2..]);
114            if level < 0 || level > 2 {
115                eprintln!("Error: Optimization level must be between 0 and 2 inclusive.");
116                return 1;
117            }
118            unsafe {
119                globalOptions.optimizationLevel = level;
120            }
121        } else if a.starts_with("-g") {
122            let level = atoi_like(&a[2..]);
123            if level < 0 || level > 2 {
124                eprintln!("Error: Debug level must be between 0 and 2 inclusive.");
125                return 1;
126            }
127            unsafe {
128                globalOptions.debugLevel = level;
129            }
130        } else if a == "--profile" {
131            profile = 10000; // default to 10 KHz
132        } else if let Some(rest) = a.strip_prefix("--profile=") {
133            profile = atoi_like(rest);
134        } else if a == "--codegen" {
135            REPL_CODEGEN.store(true, Ordering::Relaxed);
136        } else if a == "--codegen-cold" {
137            REPL_CODEGEN.store(true, Ordering::Relaxed);
138            REPL_CODEGEN_COLD.store(true, Ordering::Relaxed);
139        } else if a == "--codegen-perf" {
140            REPL_CODEGEN.store(true, Ordering::Relaxed);
141            codegen_perf = true;
142        } else if a == "--coverage" {
143            coverage = true;
144        } else if a == "--counters" {
145            counters = true;
146        } else if a == "--timetrace" {
147            luaur_common::FFlag::DebugLuauTimeTracing.set(true);
148        } else if a.starts_with("--fflags=") {
149            // setLuauFlags(argv[i] + 9)
150            unsafe {
151                let p = *argv.add(i as usize);
152                set_luau_flags_c_char(p.add(9));
153            }
154        } else if a == "--program-args" || a == "-a" {
155            program_args = i + 1;
156            break;
157        } else if a.starts_with('-') {
158            eprintln!("Error: Unrecognized option '{}'.\n", a);
159            display_help(&argv0);
160            return 1;
161        }
162
163        i += 1;
164    }
165
166    PROGRAM_ARGC.store(argc - program_args, Ordering::Relaxed);
167    unsafe {
168        PROGRAM_ARGV = argv.add(program_args as usize);
169    }
170
171    // #if !defined(LUAU_ENABLE_TIME_TRACE): time tracing is compiled out.
172    if luaur_common::FFlag::DebugLuauTimeTracing.get() {
173        eprintln!(
174            "To run with --timetrace, Luau has to be built with LUAU_ENABLE_TIME_TRACE enabled"
175        );
176        return 1;
177    }
178
179    if codegen_perf {
180        // The --codegen-perf perf-map path is Linux-only in C++; on other
181        // platforms it errors out. The Rust codegen port does not expose
182        // CodeGen::setPerfLog, so we take the unsupported-platform branch.
183        eprintln!("--codegen-perf option is only supported on Linux");
184        return 1;
185    }
186
187    if repl_codegen_enabled() && !is_supported() {
188        eprintln!("Warning: Native code generation is not supported in current configuration");
189    }
190
191    let files: Vec<String> = get_source_files(argc, argv);
192
193    if files.is_empty() {
194        unsafe {
195            run_repl();
196        }
197        0
198    } else {
199        unsafe {
200            let l: *mut lua_State = lua_l_newstate();
201
202            setup_state(l);
203
204            if profile != 0 {
205                profiler_start(l, profile);
206            }
207
208            if coverage {
209                coverage_init(l);
210            }
211
212            if counters {
213                counters_init(l as *mut core::ffi::c_void);
214            }
215
216            let mut failed = 0i32;
217
218            let n = files.len();
219            for (idx, file) in files.iter().enumerate() {
220                let is_last_file = idx == n - 1;
221                let ran = run_file(file, l, interactive && is_last_file);
222                failed += (!ran) as i32;
223            }
224
225            if profile != 0 {
226                profiler_stop();
227                profiler_dump(c"profile.out".as_ptr());
228            }
229
230            if coverage {
231                coverage_dump("coverage.out");
232            }
233
234            if counters {
235                counters_dump("callgrind.out");
236            }
237
238            lua_close(l);
239
240            if failed != 0 {
241                1
242            } else {
243                0
244            }
245        }
246    }
247}
248
249// Adapter matching the AssertHandler fn-pointer ABI expected by Common.
250unsafe extern "C" fn assertion_handler_adapter(
251    expr: *const c_char,
252    file: *const c_char,
253    line: i32,
254    function: *const c_char,
255) -> i32 {
256    assertion_handler(expr, file, line, function)
257}
258
259// Mirrors C's atoi(s): parse the leading optional sign + digits, ignoring any
260// trailing characters; non-numeric input yields 0.
261fn atoi_like(s: &str) -> i32 {
262    let bytes = s.as_bytes();
263    let mut idx = 0;
264    let mut sign = 1i32;
265
266    if idx < bytes.len() && (bytes[idx] == b'+' || bytes[idx] == b'-') {
267        if bytes[idx] == b'-' {
268            sign = -1;
269        }
270        idx += 1;
271    }
272
273    let mut value: i32 = 0;
274    while idx < bytes.len() && bytes[idx].is_ascii_digit() {
275        value = value
276            .wrapping_mul(10)
277            .wrapping_add((bytes[idx] - b'0') as i32);
278        idx += 1;
279    }
280
281    sign * value
282}