use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use unluac::decompile::{
DebugColorMode, DecompileDialect, DecompileOptions, DecompileStage, decompile,
render_timing_report,
};
const SOURCE: &str = "tests/lua_cases/luajit/09_ull_table_rotation.lua";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CompilerProtocol {
LuacStyle,
LuaJitBytecodeTool,
LuauBinaryStdout,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let repo_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let options = DecompileOptions::default();
let dialect = options.dialect;
let debug_detail = options.debug.detail;
let source = repo_root.join(SOURCE);
let compiler = bundled_compiler_path(&repo_root, dialect);
let chunk = compile_source(&compiler, &source, dialect)?;
let bytes = fs::read(&chunk)?;
let result = decompile(&bytes, options)?;
println!("== Debug Input ==");
println!("dialect: {}", dialect.as_str());
println!("source: {}", source.display());
println!("compiler: {}", compiler.display());
println!("chunk: {}", chunk.display());
println!();
if result.debug_output.is_empty() && result.timing_report.is_none() {
if let Some(generated) = result.state.generated.as_ref() {
print!("{}", generated.source);
return Ok(());
}
println!(
"pipeline stopped after {}",
result
.state
.completed_stage
.unwrap_or(DecompileStage::Parse)
);
} else {
for (index, output) in result.debug_output.iter().enumerate() {
if index > 0 {
println!();
}
print!("{}", output.content);
}
if let Some(report) = result.timing_report.as_ref() {
if !result.debug_output.is_empty() {
println!();
}
print!(
"{}",
render_timing_report(report, debug_detail, DebugColorMode::Auto)
);
}
}
Ok(())
}
fn compile_source(
compiler: &Path,
source: &Path,
dialect: DecompileDialect,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
if !compiler.exists() {
return Err(format!(
"missing bundled compiler for {}: {}",
dialect.as_str(),
compiler.display()
)
.into());
}
let output_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("unluac-debug")
.join("examples")
.join(dialect.as_str());
fs::create_dir_all(&output_dir)?;
let file_stem = source
.file_stem()
.and_then(OsStr::to_str)
.unwrap_or("debug");
let output = output_dir.join(format!("{file_stem}.{}", compiled_chunk_extension(dialect)));
match compiler_protocol(dialect) {
CompilerProtocol::LuacStyle => {
let status = Command::new(compiler)
.arg("-s")
.arg("-o")
.arg(&output)
.arg(source)
.status()?;
if !status.success() {
return Err(format!("compiler exited with status {status}").into());
}
}
CompilerProtocol::LuaJitBytecodeTool => {
let status = Command::new(compiler)
.arg("-s")
.arg(source)
.arg(&output)
.status()?;
if !status.success() {
return Err(format!("compiler exited with status {status}").into());
}
}
CompilerProtocol::LuauBinaryStdout => {
let command_output = Command::new(compiler)
.arg("--binary")
.arg("-g0")
.arg(source)
.output()?;
if !command_output.status.success() {
return Err(
format!("compiler exited with status {}", command_output.status).into(),
);
}
fs::write(&output, &command_output.stdout)?;
}
}
Ok(output)
}
fn bundled_compiler_path(repo_root: &Path, dialect: DecompileDialect) -> PathBuf {
repo_root
.join("lua")
.join("build")
.join(dialect.as_str())
.join(bundled_compiler_name(dialect))
}
fn bundled_compiler_name(dialect: DecompileDialect) -> &'static str {
match dialect {
DecompileDialect::Lua51
| DecompileDialect::Lua52
| DecompileDialect::Lua53
| DecompileDialect::Lua54
| DecompileDialect::Lua55 => "luac",
DecompileDialect::Luajit => "luac",
DecompileDialect::Luau => "luau-compile",
}
}
fn compiler_protocol(dialect: DecompileDialect) -> CompilerProtocol {
match dialect {
DecompileDialect::Lua51
| DecompileDialect::Lua52
| DecompileDialect::Lua53
| DecompileDialect::Lua54
| DecompileDialect::Lua55 => CompilerProtocol::LuacStyle,
DecompileDialect::Luajit => CompilerProtocol::LuaJitBytecodeTool,
DecompileDialect::Luau => CompilerProtocol::LuauBinaryStdout,
}
}
fn compiled_chunk_extension(dialect: DecompileDialect) -> &'static str {
match dialect {
DecompileDialect::Lua51
| DecompileDialect::Lua52
| DecompileDialect::Lua53
| DecompileDialect::Lua54
| DecompileDialect::Lua55 => "out",
DecompileDialect::Luajit => "luajit",
DecompileDialect::Luau => "luau",
}
}