use std::cell::RefCell;
use std::io::{ErrorKind, Read, Seek, SeekFrom, Write};
use std::os::unix::process::ExitStatusExt;
use std::rc::Rc;
use super::Module;
use crate::error::Result;
use crate::value::{
from_str_radix_wrapping, parse_f64_hex, FileBufMode, FileDesc, FileHandle, FuncBuiltin,
Numeric, Table, UserData, ValueType,
};
use crate::{Error, LuaError, Value, VM};
pub(super) fn create(vm: &mut VM, file: FileDesc) -> Result<Rc<RefCell<FileHandle>>> {
_create(file, vm.alloc_table(), vm.alloc_table())
}
pub(super) fn _create(
file: FileDesc,
meta: Rc<RefCell<Table>>,
index: Rc<RefCell<Table>>,
) -> Result<Rc<RefCell<FileHandle>>> {
Module {
name: "",
tbl: meta.clone(),
}
.func("__close", __close)?
.func("__gc", __close)?
.cons(
"__index",
Value::Table(
Module {
name: "",
tbl: index,
}
.func("close", close)?
.func("flush", flush)?
.func("lines", lines)?
.func("read", read)?
.func("seek", seek)?
.func("setvbuf", setvbuf)?
.func("write", write)?
.tbl
.clone(),
),
)?
.cons("__name", Value::str("FILE*"))?
.func("__tostring", __tostring)?;
Ok(Rc::new(RefCell::new(FileHandle::new(file, Some(meta)))))
}
fn close(vm: &mut VM) -> Result<Value> {
let file = vm
.arg(0)
.map_err(|_| Error::from_lua(LuaError::ArgumentNoValue))?
.to_file()?;
Ok(_close_helper(file))
}
fn __close(vm: &mut VM) -> Result<Value> {
Ok(
if let Value::UserData(UserData::File(file)) = vm
.arg(0)
.map_err(|_| Error::from_lua(LuaError::ArgumentNoValue))?
{
_close_helper(file)
} else {
Value::Bool(true)
},
)
}
pub(super) fn _close_helper(file: Rc<RefCell<FileHandle>>) -> Value {
let mut file = file.borrow_mut();
let file = file.desc_mut();
match file {
FileDesc::File(Some(_), mode) => {
*file = FileDesc::File(None, *mode);
Value::Bool(true)
}
FileDesc::Child(Some(child), mode) => {
let res = match child.wait() {
Ok(exit) => {
let success = if exit.success() {
Value::Bool(true)
} else {
Value::Nil
};
let (why, code) = match exit.code() {
Some(code) => ("exit", code as i64),
None => ("signal", exit.signal().unwrap_or_default() as i64),
};
Value::Mult(vec![success, Value::str(why), Value::int(code)])
}
Err(e) => Value::Mult(vec![Value::Bool(false), e.into()]),
};
*file = FileDesc::Child(None, *mode);
res
}
FileDesc::File(None, _) | FileDesc::Child(None, _) => {
Value::Mult(vec![Value::Nil, Value::str("file already closed")])
}
FileDesc::Buffer(..) | FileDesc::StdIn | FileDesc::StdOut | FileDesc::StdErr => {
Value::Mult(vec![Value::Nil, Value::str("cannot close standard file")])
}
}
}
fn flush(vm: &mut VM) -> Result<Value> {
let file = vm
.arg(0)
.map_err(|_| Error::from_lua(LuaError::ArgumentNoValue))?
.to_file()?;
Ok(_flush_helper(file))
}
pub(super) fn _flush_helper(file: Rc<RefCell<FileHandle>>) -> Value {
let mut file = file.borrow_mut();
match file.flush() {
Ok(_) => Value::Bool(true),
Err(e) => e.into(),
}
}
fn lines(vm: &mut VM) -> Result<Value> {
let file = vm.arg_file(0)?;
let args = vm.arg_split(1);
if args.len() > 250 {
return err!(LuaError::ArgumentTooMany);
}
Ok(Value::Func(vm.alloc_builtin(FuncBuiltin {
module: "",
name: "__lines",
func: Rc::new(move |_| {
_read_helper(file.clone(), args.clone()).and_then(|res| match &res {
Value::Mult(vec) => {
if vec.first() == Some(&Value::Nil) {
if let Some(Value::String(buf)) = vec.get(1) {
err!(LuaError::CustomString(
String::from_utf8_lossy(&buf).into_owned()
))
} else {
Ok(res)
}
} else {
Ok(res)
}
}
_ => Ok(res),
})
}),
})))
}
fn read(vm: &mut VM) -> Result<Value> {
_read_helper(vm.arg_file(0)?, vm.arg_split(1))
}
pub(super) fn _read_helper(file: Rc<RefCell<FileHandle>>, mut args: Vec<Value>) -> Result<Value> {
let mut file = file.borrow_mut();
if args.is_empty() {
args.push(Value::str("l"));
} else if args.len() > 250 {
return err!(LuaError::ArgumentTooMany);
}
let mut out = Vec::new();
for arg in args {
let value = match arg {
Value::Number(n) => {
let n = n.coerce_int()? as usize;
let mut buf = vec![0; n.max(1)];
let read = loop {
match file.read(&mut buf) {
Ok(read) => break read,
Err(ref e) if matches!(e.kind(), ErrorKind::Interrupted) => {}
Err(e) => return Ok(e.into()),
}
};
if read == 0 {
break;
} else if n == 0 {
file.seek(SeekFrom::Current(-(read as i64)))?;
buf.clear();
}
buf.truncate(read);
Value::str_bytes(buf)
}
Value::String(s) => match if s.get(0) == Some(&b'*') {
&s[1] } else {
&s[0]
} {
b'n' => {
let num = match __read_numeric(&mut file) {
Ok(Some(num)) => num,
Ok(None) => break,
Err(e) => {
if let ErrorKind::UnexpectedEof = e.kind() {
break;
} else {
return Ok(e.into());
}
}
};
Value::Number(num)
}
b'a' => {
let mut buf = Vec::new();
if let Err(e) = file.read_to_end(&mut buf) {
return Ok(e.into());
}
Value::str_bytes(buf)
}
c if matches!(c, b'l' | b'L') => {
let line = match __read_line(&mut file, c.is_ascii_uppercase()) {
Ok(line) => line,
Err(e) => {
if let ErrorKind::UnexpectedEof = e.kind() {
break;
} else {
return Ok(e.into());
}
}
};
Value::str_bytes(line)
}
_ => return err!(LuaError::FileRead),
},
v => return err!(LuaError::ExpectedType(ValueType::String, v.value_type())),
};
out.push(value);
}
Ok(Value::Mult(out))
}
fn __read_numeric(file: &mut FileHandle) -> std::io::Result<Option<Numeric>> {
let mut hex = false;
let mut int = true;
let mut exp = false;
let mut digits = vec![];
let mut buf = vec![0];
loop {
file.read_exact(&mut buf)?;
if !buf[0].is_ascii_whitespace() {
break;
}
}
loop {
if digits.len() > 200 {
return Ok(None);
}
match &buf[0] {
b'+' | b'-'
if digits.is_empty()
|| matches!(digits.last(), Some(b'e' | b'E' | b'p' | b'P')) => {}
b'a'..=b'f' | b'A'..=b'F' if hex && !exp => {}
b'0'..=b'9' => {}
b'.' if int => {
int = false;
}
b'x' | b'X' if !hex && digits.iter().all(|c| matches!(c, b'+' | b'-' | b'0')) => {
hex = true;
}
b'e' | b'E' if !exp && !hex && digits.iter().any(u8::is_ascii_digit) => {
int = false;
exp = true;
}
b'p' | b'P' if !exp && hex && digits.iter().any(u8::is_ascii_hexdigit) => {
int = false;
exp = true;
}
_ => break,
}
digits.push(buf[0]);
file.read_exact(&mut buf)?;
}
file.seek(SeekFrom::Current(-1))?;
let digits: String = digits.into_iter().map(char::from).collect();
Ok(if digits.is_empty() {
None
} else if int {
if hex {
match from_str_radix_wrapping(digits.as_bytes(), 16) {
Some(int) => Some(Numeric::Integer(int)),
None => parse_f64_hex(digits.as_bytes()).map(Numeric::Float),
}
} else {
match digits.parse::<i64>() {
Ok(int) => Some(Numeric::Integer(int as i64)),
Err(..) => digits.parse().ok().map(Numeric::Float),
}
}
} else {
if hex {
parse_f64_hex(digits.as_bytes()).map(Numeric::Float)
} else {
digits.parse().ok().map(Numeric::Float)
}
})
}
fn __read_line(file: &mut FileHandle, keep: bool) -> std::io::Result<Vec<u8>> {
let mut buf = vec![0];
let mut line = Vec::new();
loop {
match file.read_exact(&mut buf) {
Ok(_) => {
if buf[0] == b'\n' {
if keep {
line.push(b'\n');
}
break;
}
}
Err(e) if !line.is_empty() && e.kind() == ErrorKind::UnexpectedEof => break,
Err(e) => return Err(e),
}
line.push(buf[0]);
}
Ok(line)
}
fn seek(vm: &mut VM) -> Result<Value> {
let file = vm.arg_file(0)?;
let mut file = file.borrow_mut();
let whence = match vm.arg_opt(1) {
Some(val) => String::from_utf8_lossy(val.to_string()?).into_owned(),
None => "cur".to_string(),
};
let offset = match vm.arg_opt(2) {
Some(val) => val.to_int_coerce()?,
None => 0,
};
let seek = match whence.as_str() {
"set" => {
if offset < 0 {
return Ok(Value::Mult(vec![
Value::Nil,
Value::str("illegal seek"),
Value::int(1),
]));
} else {
SeekFrom::Start(offset as u64)
}
}
"cur" => SeekFrom::Current(offset),
"end" => SeekFrom::End(offset),
_ => return err!(LuaError::FileSeek(whence)),
};
Ok(match file.seek(seek) {
Ok(pos) => Value::int(pos as i64),
Err(e) => e.into(),
})
}
fn setvbuf(vm: &mut VM) -> Result<Value> {
let file = vm.arg_file(0)?;
let mode = vm.arg_string_coerce(1)?;
let size = match vm.arg_opt(2) {
Some(val) => Some(val.to_int_coerce()? as usize),
None => None,
};
let mode = match String::from_utf8_lossy(&mode).as_ref() {
"no" => FileBufMode::None,
"full" => FileBufMode::Full(size),
"line" => FileBufMode::Line(size),
str => return err!(LuaError::FileBufOpt(str.to_string())),
};
let mut file = file.borrow_mut();
Ok(Value::Bool(file.set_buffering(mode)))
}
fn __tostring(vm: &mut VM) -> Result<Value> {
vm.arg(0)?.write_as_value()
}
fn write(vm: &mut VM) -> Result<Value> {
let strs = (1..vm.arg_len())
.map(|idx| vm.arg_string_coerce(idx))
.collect::<Result<Vec<_>>>()?;
Ok(_write_helper(vm.arg_file(0)?, strs))
}
pub(super) fn _write_helper(file: Rc<RefCell<FileHandle>>, args: Vec<Vec<u8>>) -> Value {
{
let mut file = file.borrow_mut();
for arg in args {
if let Err(e) = file.write_all(&arg) {
return e.into();
}
}
}
Value::UserData(UserData::File(file))
}