use std::env;
use std::io::Read;
use std::process;
use rilua::compiler;
use rilua::vm::listing;
use rilua::vm::proto::{Proto, ProtoRef};
const VERSION: &str = "Lua 5.1.1 Copyright (C) 1994-2006 Lua.org, PUC-Rio";
const PROGNAME: &str = "riluac";
fn fatal(message: &str) -> ! {
eprintln!("{PROGNAME}: {message}");
process::exit(1);
}
fn usage(message: &str) -> ! {
if !message.is_empty() {
eprintln!("{PROGNAME}: {message}");
}
eprintln!(
"usage: {PROGNAME} [options] [filenames].\n\
Available options are:\n \
- process stdin\n \
-l list (use -l -l for full listing)\n \
-o name output to file 'name' (default \"luac.out\")\n \
-p parse only\n \
-s strip debug information\n \
-v show version information\n \
-- stop handling options"
);
process::exit(1);
}
struct Args {
listing: u32,
parse_only: bool,
strip: bool,
output_file: String,
files: Vec<String>,
}
fn do_args() -> Args {
let argv: Vec<String> = env::args().collect();
let argc = argv.len();
let mut listing: u32 = 0;
let mut parse_only = false;
let mut strip = false;
let mut output_file = "luac.out".to_string();
let mut files = Vec::new();
let mut version_seen = false;
let mut i = 1;
while i < argc {
let arg = &argv[i];
if !arg.starts_with('-') {
break;
}
match arg.as_str() {
"--" => {
i += 1;
if i < argc && argv[i] == "-v" {
version_seen = true;
}
break;
}
"-" => {
files.push(String::new());
i += 1;
break;
}
"-l" => listing += 1,
"-o" => {
i += 1;
if i >= argc {
usage("'-o' needs argument");
}
output_file.clone_from(&argv[i]);
}
"-p" => parse_only = true,
"-s" => strip = true,
"-v" => version_seen = true,
other => {
usage(&format!("unrecognized option '{other}'"));
}
}
i += 1;
}
while i < argc {
files.push(argv[i].clone());
i += 1;
}
if version_seen {
println!("{VERSION}");
}
if files.is_empty() && listing == 0 && !parse_only {
if version_seen {
process::exit(0);
}
usage("no input files given");
}
Args {
listing,
parse_only,
strip,
output_file,
files,
}
}
fn compile_source(filename: &str) -> ProtoRef {
let (source, name) = if filename.is_empty() {
let mut buf = Vec::new();
if let Err(e) = std::io::stdin().read_to_end(&mut buf) {
fatal(&format!("cannot read stdin: {e}"));
}
(buf, "=stdin".to_string())
} else {
let buf = match std::fs::read(filename) {
Ok(b) => b,
Err(e) => fatal(&format!("cannot open {filename}: {e}")),
};
(buf, format!("@{filename}"))
};
if source.starts_with(rilua::vm::dump::LUA_SIGNATURE) {
match rilua::vm::undump::undump(&source, &name) {
Ok(proto) => proto,
Err(e) => fatal(&format!("{e}")),
}
} else {
match compiler::compile(&source, &name) {
Ok(proto) => proto,
Err(e) => fatal(&format!("{e}")),
}
}
}
fn combine(protos: Vec<ProtoRef>) -> ProtoRef {
use rilua::vm::instructions::{Instruction, OpCode};
if protos.len() == 1 {
return protos.into_iter().next().unwrap_or_else(|| unreachable!());
}
let mut main = Proto::new(&format!("=({PROGNAME})"));
main.is_vararg = rilua::vm::proto::VARARG_ISVARARG;
main.max_stack_size = 1;
for (i, _) in protos.iter().enumerate() {
main.code
.push(Instruction::a_bx(OpCode::Closure, 0, i as u32).raw());
main.line_info.push(0);
main.code
.push(Instruction::abc(OpCode::Call, 0, 1, 1).raw());
main.line_info.push(0);
}
main.code
.push(Instruction::abc(OpCode::Return, 0, 1, 0).raw());
main.line_info.push(0);
main.protos = protos;
ProtoRef::new(main)
}
fn main() {
let args = do_args();
let files = if args.files.is_empty() {
vec!["luac.out".to_string()]
} else {
args.files
};
let protos: Vec<ProtoRef> = files.iter().map(|f| compile_source(f)).collect();
let proto = combine(protos);
if args.listing > 0 {
let full = args.listing > 1;
let output = listing::list_function(&proto, full);
print!("{output}");
}
if args.parse_only {
return;
}
let bytes = rilua::vm::dump::dump(&proto, None, args.strip);
if let Err(e) = std::fs::write(&args.output_file, &bytes) {
fatal(&format!("cannot write {}: {e}", args.output_file));
}
}