#![deny(warnings)]
#![deny(absolute_paths_not_starting_with_crate)]
#![deny(anonymous_parameters)]
#![deny(deprecated_in_future)]
#![deny(elided_lifetimes_in_paths)]
#![deny(explicit_outlives_requirements)]
#![deny(indirect_structural_match)]
#![deny(keyword_idents)]
#![deny(macro_use_extern_crate)]
#![deny(meta_variable_misuse)]
#![deny(missing_copy_implementations)]
#![deny(missing_debug_implementations)]
#![deny(missing_docs)]
#![deny(missing_doc_code_examples)]
#![deny(non_ascii_idents)]
#![deny(private_doc_tests)]
#![deny(single_use_lifetimes)]
#![deny(trivial_casts)]
#![deny(trivial_numeric_casts)]
#![deny(unreachable_pub)]
#![deny(unused_extern_crates)]
#![deny(unused_import_braces)]
#![deny(unused_labels)]
#![deny(unused_lifetimes)]
#![deny(unused_qualifications)]
#![deny(unused_results)]
#![deny(variant_size_differences)]
use lua_rs::ffi;
const LUA_PROMPT: &str = "> ";
const LUA_PROMPT2: &str = ">> ";
const INITVAR_NAME: &str = "=LUA_INIT";
const INITVAR_VERSION_NAME: &str = "=LUA_INIT_5_3";
#[cfg(unix)]
fn stdin_is_tty() -> bool {
unsafe { libc::isatty(0) != 0 }
}
#[cfg(not(unix))]
fn stdin_is_tty() -> bool {
true
}
fn readline(prompt: &str) -> Option<String> {
use std::io::{self, Write};
print!("{}", prompt);
io::stdout().flush().unwrap();
let mut input = String::new();
match io::stdin().read_line(&mut input) {
Ok(_) => Some(input),
Err(_) => None,
}
}
fn saveline(_: &str) {}
#[allow(non_upper_case_globals)]
static mut global_l: *mut ffi::lua_State = 0 as *mut _;
#[cfg(unix)]
extern "C" fn stop(l: *mut ffi::lua_State, _: *mut ffi::lua_Debug) {
unsafe { ffi::lua_sethook(l, None, 0, 0) };
unsafe { ffi::luaL_error(l, b"interrupted!\0".as_ptr() as _) };
}
#[cfg(unix)]
extern "C" fn laction(i: i32) {
let _ = unsafe { libc::signal(i, libc::SIG_DFL) };
unsafe {
ffi::lua_sethook(
global_l,
Some(stop),
ffi::LUA_MASKCALL | ffi::LUA_MASKRET | ffi::LUA_MASKCOUNT,
1,
)
};
}
fn print_usage(badoption: &str) {
let progname = std::env::args().next().unwrap();
eprint!("{}: ", progname);
let ch = badoption.chars().nth(1).unwrap();
if ch == 'e' || ch == 'l' {
eprintln!("'{}' needs argument", badoption);
} else {
eprintln!("unrecognized option '{}'", badoption);
}
eprintln!(
r#"usage: {} [options] [script [args]]
Available options are:
-e stat execute string 'stat'
-i enter interactive mode after executing 'script'
-l name require library 'name' into global 'name'
-v show version information
-E ignore environment variables
-- stop handling options
- stop handling options and execute stdin"#,
progname
);
}
fn message(msg: &str, include_name: bool) {
if include_name {
eprint!("{}: ", std::env::args().next().unwrap());
}
eprintln!("{}", msg);
}
fn report(l: *mut ffi::lua_State, status: i32, include_name: bool) -> i32 {
if status != ffi::LUA_OK {
let mut len: usize = 0;
let msg = unsafe { ffi::lua_tolstring(l, -1, &mut len) };
let msg_slice = unsafe { std::slice::from_raw_parts(msg as _, len) };
message(std::str::from_utf8(msg_slice).unwrap(), include_name);
unsafe { ffi::lua_pop(l, 1) };
}
status
}
fn msghandler_(l: *mut ffi::lua_State) -> i32 {
let mut msg = unsafe { ffi::lua_tostring(l, 1) };
if msg.is_null() {
let meta = unsafe { ffi::luaL_callmeta(l, 1, b"__tostring\0".as_ptr() as _) };
if meta != 0 && unsafe { ffi::lua_type(l, -1) } == ffi::LUA_TSTRING {
return 1;
} else {
msg = unsafe {
ffi::lua_pushfstring(
l,
b"(error object is a %s value)\0".as_ptr() as _,
ffi::luaL_typename(l, 1),
)
};
}
}
unsafe { ffi::luaL_traceback(l, l, msg, 1) };
1
}
unsafe extern "C" fn msghandler(l: *mut ffi::lua_State) -> i32 {
msghandler_(l)
}
fn docall(l: *mut ffi::lua_State, narg: i32, nres: i32) -> i32 {
let base = unsafe { ffi::lua_gettop(l) } - narg;
unsafe { ffi::lua_pushcfunction(l, Some(msghandler)) };
unsafe { ffi::lua_insert(l, base) };
unsafe { global_l = l };
#[cfg(unix)]
let _ = unsafe { libc::signal(libc::SIGINT, laction as _) };
let status = unsafe { ffi::lua_pcall(l, narg, nres, base) };
#[cfg(unix)]
let _ = unsafe { libc::signal(libc::SIGINT, libc::SIG_DFL) };
unsafe { ffi::lua_remove(l, base) };
status
}
fn print_version() {
println!("Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio");
}
fn createargtable(l: *mut ffi::lua_State, options: &ProgramOptions<'_>) {
let (pre, post) = if options.script_args.is_empty() {
(options.script_args, options.pre_script_args)
} else {
(options.pre_script_args, options.script_args)
};
unsafe { ffi::lua_createtable(l, post.len() as _, (pre.len() + 1) as _) };
for (i, arg) in pre.iter().enumerate() {
let _ = unsafe { ffi::lua_pushlstring(l, arg.as_ptr() as _, arg.as_bytes().len()) };
unsafe { ffi::lua_rawseti(l, -2, i as i64 - pre.len() as i64) };
}
for (i, arg) in post.iter().enumerate() {
let _ = unsafe { ffi::lua_pushlstring(l, arg.as_ptr() as _, arg.as_bytes().len()) };
unsafe { ffi::lua_rawseti(l, -2, i as _) };
}
unsafe { ffi::lua_setglobal(l, b"arg\0".as_ptr() as _) };
}
fn dochunk(l: *mut ffi::lua_State, status: i32) -> i32 {
let stat = if status == ffi::LUA_OK {
docall(l, 0, 0)
} else {
status
};
report(l, stat, true)
}
fn dofile(l: *mut ffi::lua_State, name: Option<&str>) -> i32 {
let status = match name {
Some(n) => {
let s = std::ffi::CString::new(n).unwrap();
unsafe { ffi::luaL_loadfile(l, s.as_ptr()) }
}
None => unsafe { ffi::luaL_loadfile(l, std::ptr::null()) },
};
dochunk(l, status)
}
fn dostring(l: *mut ffi::lua_State, s: &str, name: &str) -> i32 {
let name = std::ffi::CString::new(name).unwrap();
dochunk(l, unsafe {
ffi::luaL_loadbuffer(l, s.as_ptr() as _, s.as_bytes().len(), name.as_ptr())
})
}
fn dolibrary(l: *mut ffi::lua_State, name: &str) -> i32 {
let _ = unsafe { ffi::lua_getglobal(l, b"require\0".as_ptr() as _) };
let name = std::ffi::CString::new(name).unwrap();
let _ = unsafe { ffi::lua_pushlstring(l, name.as_ptr(), name.as_bytes().len()) };
let status = docall(l, 1, 1);
if status == ffi::LUA_OK {
unsafe { ffi::lua_setglobal(l, name.as_ptr()) };
}
report(l, status, true)
}
fn get_prompt(l: *mut ffi::lua_State, firstline: bool) -> String {
let s = if firstline {
b"_PROMPT\0".as_ref()
} else {
b"_PROMPT2\0".as_ref()
};
let _ = unsafe { ffi::lua_getglobal(l, s.as_ptr() as _) };
let p = unsafe { ffi::lua_tostring(l, -1) };
if p.is_null() {
if firstline { LUA_PROMPT } else { LUA_PROMPT2 }.to_string()
} else {
unsafe { std::ffi::CStr::from_ptr(p) }
.to_str()
.unwrap()
.to_string()
}
}
const EOFMARK: &str = "<eof>";
fn incomplete(l: *mut ffi::lua_State, status: i32) -> bool {
if status == ffi::LUA_ERRSYNTAX {
let msg = unsafe { ffi::lua_tostring(l, -1) };
let s = unsafe { std::ffi::CStr::from_ptr(msg) };
if s.to_str().unwrap().ends_with(EOFMARK) {
unsafe { ffi::lua_pop(l, 1) };
return true;
}
}
false
}
fn pushline(l: *mut ffi::lua_State, firstline: bool) -> bool {
let prompt = get_prompt(l, firstline);
let mut line = match readline(&prompt) {
Some(l) => l,
None => return false,
};
if line.is_empty() {
return false;
}
unsafe { ffi::lua_pop(l, 1) };
if line.ends_with('\n') {
let _ = line.pop();
}
let _ = unsafe { ffi::lua_pushlstring(l, line.as_ptr() as _, line.as_bytes().len()) };
true
}
fn addreturn(l: *mut ffi::lua_State) -> i32 {
let line = unsafe { ffi::lua_tostring(l, -1) };
let retline = unsafe { ffi::lua_pushfstring(l, b"return %s;\0".as_ptr() as _, line) };
let status = unsafe {
ffi::luaL_loadbuffer(l, retline, libc::strlen(retline), b"=stdin\0".as_ptr() as _)
};
if status == ffi::LUA_OK {
unsafe { ffi::lua_remove(l, -2) };
let s = unsafe { std::ffi::CStr::from_ptr(line) };
if !s.to_bytes().is_empty() {
saveline(s.to_str().unwrap());
}
} else {
unsafe { ffi::lua_pop(l, 2) };
}
status
}
fn multiline(l: *mut ffi::lua_State) -> i32 {
loop {
let mut len: usize = 0;
let line = unsafe { ffi::lua_tolstring(l, 1, &mut len) };
let status = unsafe { ffi::luaL_loadbuffer(l, line, len, b"=stdin\0".as_ptr() as _) };
if !incomplete(l, status) || !pushline(l, false) {
let s = unsafe { std::ffi::CStr::from_ptr(line) };
saveline(s.to_str().unwrap());
return status;
}
let _ = unsafe { ffi::lua_pushliteral(l, b"\n") };
unsafe { ffi::lua_insert(l, -2) };
unsafe { ffi::lua_concat(l, 3) };
}
}
fn loadline(l: *mut ffi::lua_State) -> i32 {
unsafe { ffi::lua_settop(l, 0) };
if !pushline(l, true) {
return -1;
}
let mut status = addreturn(l);
if status != ffi::LUA_OK {
status = multiline(l);
}
unsafe { ffi::lua_remove(l, 1) };
assert!(unsafe { ffi::lua_gettop(l) } == 1);
status
}
fn print(l: *mut ffi::lua_State) {
let n = unsafe { ffi::lua_gettop(l) };
if n > 0 {
unsafe {
ffi::luaL_checkstack(
l,
ffi::LUA_MINSTACK,
b"too many results to print\0".as_ptr() as _,
)
};
let _ = unsafe { ffi::lua_getglobal(l, b"print\0".as_ptr() as _) };
unsafe { ffi::lua_insert(l, 1) };
if unsafe { ffi::lua_pcall(l, n, 0, 0) } != ffi::LUA_OK {
let msg = unsafe {
ffi::lua_pushfstring(
l,
b"error calling 'print' (%s)\0".as_ptr() as _,
ffi::lua_tostring(l, -1),
)
};
message(
unsafe { std::ffi::CStr::from_ptr(msg) }.to_str().unwrap(),
false,
);
}
}
}
fn dorepl(l: *mut ffi::lua_State) {
loop {
let mut status = loadline(l);
if status == -1 {
break;
}
if status == ffi::LUA_OK {
status = docall(l, 0, ffi::LUA_MULTRET);
}
if status == ffi::LUA_OK {
print(l);
} else {
let _ = report(l, status, false);
}
}
unsafe { ffi::lua_settop(l, 0) };
println!();
}
fn pushargs(l: *mut ffi::lua_State) -> i32 {
let ty = unsafe { ffi::lua_getglobal(l, b"arg\0".as_ptr() as _) };
if ty != ffi::LUA_TTABLE {
unsafe { ffi::luaL_error(l, b"'arg' is not a table\0".as_ptr() as _) };
}
let n = unsafe { ffi::luaL_len(l, -1) } as i32;
unsafe { ffi::luaL_checkstack(l, n + 3, b"too many arguments to script\0".as_ptr() as _) };
for i in 1..=n {
let _ = unsafe { ffi::lua_rawgeti(l, -i, i64::from(i)) };
}
unsafe { ffi::lua_remove(l, -(n + 1)) };
n
}
fn handle_script(l: *mut ffi::lua_State, args: &[&str], stop_options: bool) -> i32 {
let s = std::ffi::CString::new(args[0]).unwrap();
let fname = if args[0] == "-" && !stop_options {
std::ptr::null()
} else {
s.as_ptr()
};
let mut status = unsafe { ffi::luaL_loadfile(l, fname) };
if status == ffi::LUA_OK {
let n = pushargs(l);
status = docall(l, n, ffi::LUA_MULTRET);
}
report(l, status, true)
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
#[allow(single_use_lifetimes)]
enum RunnableArg<'a> {
Library(&'a str),
Execute(&'a str),
}
#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Debug)]
#[allow(single_use_lifetimes)]
struct ProgramOptions<'a> {
interactive: bool,
version: bool,
ignore_env: bool,
execute: bool,
stop_options: bool,
runnable_args: Vec<RunnableArg<'a>>,
pre_script_args: &'a [&'a str],
script_args: &'a [&'a str],
}
fn collectargs<'a>(args: &'a [&str]) -> Result<ProgramOptions<'a>, &'a str> {
fn add_script_args<'b>(
mut options: ProgramOptions<'b>,
args: &'b [&str],
script: usize,
) -> Result<ProgramOptions<'b>, &'b str> {
let (pre_script_args, script_args) = args.split_at(script);
options.pre_script_args = pre_script_args;
options.script_args = script_args;
Ok(options)
}
let mut options: ProgramOptions<'_> = Default::default();
let mut first = 0usize;
let mut skip = 0;
for (i, arg) in args.iter().enumerate().skip(1) {
first = i;
if skip != 0 {
if arg.starts_with('-') {
return Err(args[first]);
}
options.runnable_args.push(if skip == 1 {
RunnableArg::Library(arg)
} else {
RunnableArg::Execute(arg)
});
skip = 0;
continue;
}
if !arg.starts_with('-') {
return add_script_args(options, args, first);
}
if arg.len() == 1 {
return add_script_args(options, args, first);
}
match arg.chars().nth(1).unwrap() {
'-' => {
if arg.len() != 2 {
return Err(args[first]);
}
options.stop_options = true;
return add_script_args(options, args, first + 1);
}
'E' => {
if arg.len() != 2 {
return Err(args[first]);
}
options.ignore_env = true;
}
'i' => {
if arg.len() != 2 {
return Err(args[first]);
}
options.interactive = true;
options.version = true;
}
'v' => {
if arg.len() != 2 {
return Err(args[first]);
}
options.version = true;
}
'e' => {
options.execute = true;
if arg.len() == 2 {
skip = 2;
} else {
let (_, a) = arg.split_at(2);
options.runnable_args.push(RunnableArg::Execute(a));
}
}
'l' => {
if arg.len() == 2 {
skip = 1;
} else {
let (_, a) = arg.split_at(2);
options.runnable_args.push(RunnableArg::Library(a));
}
}
_ => return Err(args[first]),
}
}
if skip != 0 {
return Err(args[first]);
}
add_script_args(options, args, first + 1)
}
fn runargs(l: *mut ffi::lua_State, runnable_args: &[RunnableArg<'_>]) -> bool {
for arg in runnable_args {
let status = match *arg {
RunnableArg::Library(s) => dolibrary(l, s),
RunnableArg::Execute(s) => dostring(l, s, "=(command line)"),
};
if status != ffi::LUA_OK {
return false;
}
}
true
}
fn handle_luainit(l: *mut ffi::lua_State) -> i32 {
fn get_var(name: &str) -> Option<(String, &str)> {
let mut chars = name.chars();
let _ = chars.next();
match std::env::var(chars.as_str()) {
Ok(val) => Some((val, name)),
Err(_) => None,
}
}
let (init, name) = match get_var(INITVAR_VERSION_NAME).or_else(|| get_var(INITVAR_NAME)) {
Some((i, n)) => (i, n),
None => return ffi::LUA_OK,
};
if init.starts_with('@') {
let mut chars = init.chars();
let _ = chars.next();
dofile(l, Some(chars.as_str()))
} else {
dostring(l, &init, &name)
}
}
#[cfg(debug_assertions)]
fn openlibs(l: *mut ffi::lua_State) {
unsafe { ffi::luaL_openlibs(l) };
extern "C" {
fn luaB_opentests(l: *mut ffi::lua_State) -> i32;
}
unsafe { ffi::luaL_requiref(l, b"T\0".as_ptr() as _, Some(luaB_opentests), 1) };
}
#[cfg(not(debug_assertions))]
fn openlibs(l: *mut ffi::lua_State) {
unsafe { ffi::luaL_openlibs(l) };
}
fn pmain_(l: *mut ffi::lua_State) -> i32 {
let args: Vec<_> = std::env::args().collect();
let arg_strs: Vec<_> = args.iter().map(AsRef::as_ref).collect();
let options = match collectargs(&arg_strs) {
Ok(o) => o,
Err(bad_arg) => {
print_usage(bad_arg);
return 0;
}
};
if options.version {
print_version();
}
if options.ignore_env {
unsafe { ffi::lua_pushboolean(l, 1) };
unsafe { ffi::lua_setfield(l, ffi::LUA_REGISTRYINDEX, "LUA_NOENV\0".as_ptr() as _) };
}
openlibs(l);
createargtable(l, &options);
if !options.ignore_env && handle_luainit(l) != ffi::LUA_OK {
return 0;
}
if !runargs(l, &options.runnable_args) {
return 0;
}
if !options.script_args.is_empty()
&& handle_script(l, options.script_args, options.stop_options) != ffi::LUA_OK
{
return 0;
}
if options.interactive {
dorepl(l);
}
else if options.script_args.is_empty() && !(options.execute || options.version) {
if stdin_is_tty() {
print_version();
dorepl(l);
} else {
let _ = dofile(l, None);
}
}
unsafe { ffi::lua_pushboolean(l, 1) };
1
}
unsafe extern "C" fn pmain(l: *mut ffi::lua_State) -> i32 {
pmain_(l)
}
#[cfg(debug_assertions)]
fn newstate() -> *mut ffi::lua_State {
#[repr(C)]
struct Memcontrol {
numblocks: u64,
total: u64,
maxmem: u64,
memlimit: u64,
objcount: [u64; ffi::LUA_NUMTAGS as usize],
}
extern "C" {
static mut l_memcontrol: Memcontrol;
}
extern "C" {
fn debug_realloc(
ud: *mut std::ffi::c_void,
ptr: *mut std::ffi::c_void,
osize: usize,
osize: usize,
) -> *mut std::ffi::c_void;
}
#[allow(trivial_casts)]
unsafe {
ffi::lua_newstate(
Some(debug_realloc),
&mut l_memcontrol as *mut Memcontrol as *mut _,
)
}
}
#[cfg(not(debug_assertions))]
fn newstate() -> *mut ffi::lua_State {
unsafe { ffi::luaL_newstate() }
}
fn main() {
let l = newstate();
if l.is_null() {
message("cannot create state: not enough memory", true);
std::process::exit(1);
}
unsafe { ffi::lua_pushcfunction(l, Some(pmain)) };
let status = unsafe { ffi::lua_pcall(l, 0, 1, 0) };
let result = unsafe { ffi::lua_toboolean(l, -1) };
let _ = report(l, status, true);
unsafe { ffi::lua_close(l) };
std::process::exit(if result != 0 && status == ffi::LUA_OK {
0
} else {
1
});
}
#[cfg(test)]
mod tests {
use super::collectargs;
use super::ProgramOptions;
use super::RunnableArg::{Execute, Library};
#[test]
fn test_collectargs() {
assert_eq!(
collectargs(vec!["lua"].as_slice()),
Ok(ProgramOptions {
pre_script_args: &["lua"],
..Default::default()
})
);
assert_eq!(
collectargs(vec!["lua", "name"].as_slice()),
Ok(ProgramOptions {
pre_script_args: &["lua"],
script_args: &["name"],
..Default::default()
})
);
assert_eq!(
collectargs(vec!["lua", "-"].as_slice()),
Ok(ProgramOptions {
pre_script_args: &["lua"],
script_args: &["-"],
..Default::default()
})
);
assert_eq!(
collectargs(vec!["lua", "--"].as_slice()),
Ok(ProgramOptions {
stop_options: true,
pre_script_args: &["lua", "--"],
..Default::default()
})
);
assert_eq!(
collectargs(vec!["lua", "-i"].as_slice()),
Ok(ProgramOptions {
interactive: true,
version: true,
pre_script_args: &["lua", "-i"],
..Default::default()
})
);
assert_eq!(
collectargs(vec!["lua", "-v"].as_slice()),
Ok(ProgramOptions {
version: true,
pre_script_args: &["lua", "-v"],
..Default::default()
})
);
assert_eq!(
collectargs(vec!["lua", "-v", "name"].as_slice()),
Ok(ProgramOptions {
version: true,
pre_script_args: &["lua", "-v"],
script_args: &["name"],
..Default::default()
})
);
assert_eq!(collectargs(vec!["lua", "-e"].as_slice()), Err("-e"));
assert_eq!(
collectargs(vec!["lua", "-escript"].as_slice()),
Ok(ProgramOptions {
execute: true,
runnable_args: vec![Execute("script")],
pre_script_args: &["lua", "-escript"],
..Default::default()
})
);
assert_eq!(
collectargs(vec!["lua", "-e", "script"].as_slice()),
Ok(ProgramOptions {
execute: true,
runnable_args: vec![Execute("script")],
pre_script_args: &["lua", "-e", "script"],
..Default::default()
})
);
assert_eq!(
collectargs(vec!["lua", "-E"].as_slice()),
Ok(ProgramOptions {
ignore_env: true,
pre_script_args: &["lua", "-E"],
..Default::default()
})
);
assert_eq!(collectargs(vec!["lua", "-l"].as_slice()), Err("-l"));
assert_eq!(
collectargs(vec!["lua", "-llib"].as_slice()),
Ok(ProgramOptions {
runnable_args: vec![Library("lib")],
pre_script_args: &["lua", "-llib"],
..Default::default()
})
);
assert_eq!(
collectargs(vec!["lua", "-l", "lib"].as_slice()),
Ok(ProgramOptions {
runnable_args: vec![Library("lib")],
pre_script_args: &["lua", "-l", "lib"],
..Default::default()
})
);
assert_eq!(collectargs(vec!["lua", "-x"].as_slice()), Err("-x"));
}
}