use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Read, Seek, Write};
use crate::lua_value::userdata_trait::UserDataTrait;
use crate::lua_value::{LuaValue, LuaValueKind};
use crate::lua_vm::{LuaResult, LuaState};
#[cfg(not(target_arch = "wasm32"))]
use super::popen::{PopenFile, PopenReader, PopenWriter};
use super::popen::{ProcessExit, spawn_popen, validate_popen_mode};
pub enum ReadNumberResult {
Integer(i64),
Float(f64),
}
fn parse_lua_integer(s: &str) -> Option<i64> {
let s = s.trim();
if s.starts_with("0x") || s.starts_with("0X") {
let hex = &s[2..];
u64::from_str_radix(hex, 16).ok().map(|n| n as i64)
} else if s.starts_with("-0x") || s.starts_with("-0X") {
let hex = &s[3..];
u64::from_str_radix(hex, 16)
.ok()
.map(|n| (n as i64).wrapping_neg())
} else {
s.parse::<i64>().ok()
}
}
fn parse_lua_float(s: &str) -> Option<f64> {
let s = s.trim();
if let Ok(n) = s.parse::<f64>() {
return Some(n);
}
let (neg, rest) = if let Some(rest) = s.strip_prefix('-') {
(true, rest)
} else if let Some(rest) = s.strip_prefix('+') {
(false, rest)
} else {
(false, s)
};
if rest.starts_with("0x") || rest.starts_with("0X") {
let hex = &rest[2..];
let (mantissa_str, exp_str) = if let Some(p) = hex.find(['p', 'P']) {
(&hex[..p], Some(&hex[p + 1..]))
} else {
(hex, None)
};
let (int_str, frac_str) = if let Some(dot) = mantissa_str.find('.') {
(&mantissa_str[..dot], Some(&mantissa_str[dot + 1..]))
} else {
(mantissa_str, None)
};
let int_val = u64::from_str_radix(int_str, 16).ok()? as f64;
let frac_val = if let Some(frac) = frac_str {
if frac.is_empty() {
0.0
} else {
let frac_num = u64::from_str_radix(frac, 16).ok()? as f64;
frac_num / 16f64.powi(frac.len() as i32)
}
} else {
0.0
};
let mut result = int_val + frac_val;
if let Some(exp) = exp_str {
let exp_val: i32 = exp.parse().ok()?;
result *= 2f64.powi(exp_val);
}
if neg {
result = -result;
}
return Some(result);
}
None
}
pub struct LuaFile {
inner: FileInner,
}
pub enum LuaFileCloseResult {
Closed,
Process(ProcessExit),
}
enum FileInner {
Read(BufReader<File>),
Write(BufWriter<File>),
ReadWrite(BufReader<File>),
#[cfg(not(target_arch = "wasm32"))]
PopenRead(PopenReader),
#[cfg(not(target_arch = "wasm32"))]
PopenWrite(PopenWriter),
Stdin,
Stdout,
Stderr,
Closed,
}
impl LuaFile {
pub fn stdin() -> Self {
LuaFile {
inner: FileInner::Stdin,
}
}
pub fn stdout() -> Self {
LuaFile {
inner: FileInner::Stdout,
}
}
pub fn stderr() -> Self {
LuaFile {
inner: FileInner::Stderr,
}
}
pub fn open_read(path: &str) -> io::Result<Self> {
let file = File::open(path)?;
Ok(LuaFile {
inner: FileInner::Read(BufReader::new(file)),
})
}
pub fn open_write(path: &str) -> io::Result<Self> {
let file = File::create(path)?;
Ok(LuaFile {
inner: FileInner::Write(BufWriter::new(file)),
})
}
pub fn open_append(path: &str) -> io::Result<Self> {
let file = std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(path)?;
Ok(LuaFile {
inner: FileInner::Write(BufWriter::new(file)),
})
}
pub fn open_readwrite(path: &str) -> io::Result<Self> {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(path)?;
Ok(LuaFile {
inner: FileInner::ReadWrite(BufReader::new(file)),
})
}
pub fn open_append_read(path: &str) -> io::Result<Self> {
let file = std::fs::OpenOptions::new()
.read(true)
.append(true)
.create(true)
.open(path)?;
Ok(LuaFile {
inner: FileInner::ReadWrite(BufReader::new(file)),
})
}
pub fn open_write_read(path: &str) -> io::Result<Self> {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)?;
Ok(LuaFile {
inner: FileInner::ReadWrite(BufReader::new(file)),
})
}
pub fn from_file(file: File) -> Self {
LuaFile {
inner: FileInner::Write(BufWriter::new(file)),
}
}
pub fn from_file_rw(file: File) -> Self {
LuaFile {
inner: FileInner::ReadWrite(BufReader::new(file)),
}
}
pub fn popen(command: &str, mode: &str) -> io::Result<Self> {
if !validate_popen_mode(mode) {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid mode"));
}
#[cfg(target_arch = "wasm32")]
{
let _ = command;
let _ = mode;
Err(io::Error::other("popen is not supported on wasm"))
}
#[cfg(not(target_arch = "wasm32"))]
{
let inner = match spawn_popen(command, mode)? {
PopenFile::Read(reader) => FileInner::PopenRead(reader),
PopenFile::Write(writer) => FileInner::PopenWrite(writer),
};
Ok(LuaFile { inner })
}
}
pub fn is_closed(&self) -> bool {
matches!(self.inner, FileInner::Closed)
}
pub fn is_std_stream(&self) -> bool {
matches!(
self.inner,
FileInner::Stdin | FileInner::Stdout | FileInner::Stderr
)
}
pub fn read_line(&mut self) -> io::Result<Option<String>> {
let mut line = String::new();
match &mut self.inner {
FileInner::Read(reader) => {
let n = reader.read_line(&mut line)?;
if n == 0 {
Ok(None)
} else {
if line.ends_with('\n') {
line.pop();
if line.ends_with('\r') {
line.pop();
}
}
Ok(Some(line))
}
}
FileInner::ReadWrite(reader) => {
let n = reader.read_line(&mut line)?;
if n == 0 {
Ok(None)
} else {
if line.ends_with('\n') {
line.pop();
if line.ends_with('\r') {
line.pop();
}
}
Ok(Some(line))
}
}
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenRead(reader) => reader.read_line(false),
FileInner::Stdin => {
let stdin = io::stdin();
let n = stdin.lock().read_line(&mut line)?;
if n == 0 {
Ok(None)
} else {
if line.ends_with('\n') {
line.pop();
if line.ends_with('\r') {
line.pop();
}
}
Ok(Some(line))
}
}
_ => Err(io::Error::other("File not opened for reading")),
}
}
pub fn read_line_with_newline(&mut self) -> io::Result<Option<String>> {
let mut line = String::new();
match &mut self.inner {
FileInner::Read(reader) => {
let n = reader.read_line(&mut line)?;
if n == 0 { Ok(None) } else { Ok(Some(line)) }
}
FileInner::ReadWrite(reader) => {
let n = reader.read_line(&mut line)?;
if n == 0 { Ok(None) } else { Ok(Some(line)) }
}
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenRead(reader) => reader.read_line(true),
FileInner::Stdin => {
let stdin = io::stdin();
let n = stdin.lock().read_line(&mut line)?;
if n == 0 { Ok(None) } else { Ok(Some(line)) }
}
_ => Err(io::Error::other("File not opened for reading")),
}
}
pub fn read_number(&mut self) -> io::Result<Option<ReadNumberResult>> {
use std::io::Read;
let mut buf = Vec::new();
let mut byte_buf = [0u8; 1];
let mut last_byte: Option<u8> = None; const L_MAXLENNUM: usize = 200;
let read_byte_inner =
|inner: &mut FileInner, byte_buf: &mut [u8; 1]| -> io::Result<Option<u8>> {
match inner {
FileInner::Read(reader) => {
let n = reader.read(&mut byte_buf[..])?;
if n == 0 {
Ok(None)
} else {
Ok(Some(byte_buf[0]))
}
}
FileInner::ReadWrite(reader) => {
let n = reader.read(&mut byte_buf[..])?;
if n == 0 {
Ok(None)
} else {
Ok(Some(byte_buf[0]))
}
}
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenRead(reader) => reader.read_byte(),
FileInner::Stdin => {
let n = io::stdin().lock().read(&mut byte_buf[..])?;
if n == 0 {
Ok(None)
} else {
Ok(Some(byte_buf[0]))
}
}
_ => Err(io::Error::other("not readable")),
}
};
let ungetc = |inner: &mut FileInner, byte: u8| match inner {
FileInner::Read(reader) => {
let _ = reader.seek(std::io::SeekFrom::Current(-1));
}
FileInner::ReadWrite(reader) => {
let _ = reader.seek(std::io::SeekFrom::Current(-1));
}
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenRead(reader) => {
reader.unread_byte(byte);
}
_ => {}
};
macro_rules! getc {
($self:expr) => {{
if let Some(b) = last_byte.take() {
Some(b)
} else {
read_byte_inner(&mut $self.inner, &mut byte_buf)?
}
}};
}
let mut c = loop {
match getc!(self) {
None => return Ok(None),
Some(b) if (b as char).is_ascii_whitespace() => continue,
Some(b) => break b,
}
};
if c == b'+' || c == b'-' {
buf.push(c);
match getc!(self) {
None => {
return Ok(None);
}
Some(b) => c = b,
}
}
let mut digit_count: usize = 0;
let hex = if c == b'0' {
buf.push(c);
digit_count += 1; match getc!(self) {
None => {
return Ok(Some(ReadNumberResult::Integer(0)));
}
Some(b) if b == b'x' || b == b'X' => {
buf.push(b);
digit_count = 0; match getc!(self) {
None => {
c = 0;
}
Some(b2) => c = b2,
}
true
}
Some(b) => {
c = b;
false
}
}
} else {
false
};
loop {
let is_digit = if hex {
(c as char).is_ascii_hexdigit()
} else {
(c as char).is_ascii_digit()
};
if !is_digit || buf.len() >= L_MAXLENNUM {
break;
}
buf.push(c);
digit_count += 1;
match getc!(self) {
None => {
c = 0;
break;
}
Some(b) => c = b,
}
}
if c == b'.' {
buf.push(c);
match getc!(self) {
None => {
c = 0;
}
Some(b) => c = b,
}
loop {
let is_digit = if hex {
(c as char).is_ascii_hexdigit()
} else {
(c as char).is_ascii_digit()
};
if !is_digit || buf.len() >= L_MAXLENNUM {
break;
}
buf.push(c);
digit_count += 1;
match getc!(self) {
None => {
c = 0;
break;
}
Some(b) => c = b,
}
}
}
if digit_count > 0 {
let exp_char = if hex { b'p' } else { b'e' };
let exp_char_upper = if hex { b'P' } else { b'E' };
if c == exp_char || c == exp_char_upper {
buf.push(c);
match getc!(self) {
None => {
c = 0;
}
Some(b) => c = b,
}
if c == b'+' || c == b'-' {
buf.push(c);
match getc!(self) {
None => {
c = 0;
}
Some(b) => c = b,
}
}
loop {
if !(c as char).is_ascii_digit() || buf.len() >= L_MAXLENNUM {
break;
}
buf.push(c);
match getc!(self) {
None => {
c = 0;
break;
}
Some(b) => c = b,
}
}
}
}
if c != 0 {
ungetc(&mut self.inner, c);
}
if buf.is_empty() || digit_count == 0 {
return Ok(None);
}
if buf.len() >= L_MAXLENNUM {
return Ok(None);
}
let s = String::from_utf8(buf).unwrap_or_default();
let trimmed = s.trim();
if let Some(n) = parse_lua_integer(trimmed) {
return Ok(Some(ReadNumberResult::Integer(n)));
}
if let Some(n) = parse_lua_float(trimmed) {
return Ok(Some(ReadNumberResult::Float(n)));
}
Ok(None)
}
pub fn read_all(&mut self) -> io::Result<Vec<u8>> {
let mut content = Vec::new();
match &mut self.inner {
FileInner::Read(reader) => {
reader.read_to_end(&mut content)?;
Ok(content)
}
FileInner::ReadWrite(reader) => {
reader.read_to_end(&mut content)?;
Ok(content)
}
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenRead(reader) => reader.read_all(),
FileInner::Stdin => {
io::stdin().lock().read_to_end(&mut content)?;
Ok(content)
}
_ => Err(io::Error::other("File not opened for reading")),
}
}
pub fn read_bytes(&mut self, n: usize) -> io::Result<Vec<u8>> {
let mut buffer = vec![0u8; n];
let mut total_read = 0;
loop {
if total_read >= n {
break;
}
let bytes_read = match &mut self.inner {
FileInner::Read(reader) => reader.read(&mut buffer[total_read..])?,
FileInner::ReadWrite(reader) => reader.read(&mut buffer[total_read..])?,
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenRead(reader) => {
let bytes = reader.read_bytes(n - total_read)?;
let len = bytes.len();
buffer[total_read..total_read + len].copy_from_slice(&bytes);
len
}
FileInner::Stdin => io::stdin().lock().read(&mut buffer[total_read..])?,
_ => {
return Err(io::Error::other("File not opened for reading"));
}
};
if bytes_read == 0 {
break; }
total_read += bytes_read;
}
buffer.truncate(total_read);
Ok(buffer)
}
pub fn is_eof(&mut self) -> io::Result<bool> {
use std::io::{Read, Seek, SeekFrom};
let mut buf = [0u8; 1];
match &mut self.inner {
FileInner::Read(reader) => {
let n = reader.read(&mut buf)?;
if n == 0 {
return Ok(true);
}
reader.seek(SeekFrom::Current(-1))?;
Ok(false)
}
FileInner::ReadWrite(reader) => {
let n = reader.read(&mut buf)?;
if n == 0 {
return Ok(true);
}
reader.seek(SeekFrom::Current(-1))?;
Ok(false)
}
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenRead(reader) => reader.is_eof(),
FileInner::Stdin => {
Ok(false)
}
_ => Ok(true),
}
}
pub fn write(&mut self, data: &str) -> io::Result<()> {
if data.is_ascii() {
return self.write_bytes(data.as_bytes());
}
let bytes: Vec<u8> = data.chars().map(|c| c as u8).collect();
self.write_bytes(&bytes)
}
pub fn write_bytes(&mut self, data: &[u8]) -> io::Result<()> {
match &mut self.inner {
FileInner::Write(writer) => {
writer.write_all(data)?;
Ok(())
}
FileInner::ReadWrite(reader) => {
reader.get_mut().write_all(data)?;
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenWrite(writer) => writer.write_bytes(data),
FileInner::Stdout => {
io::stdout().write_all(data)?;
Ok(())
}
FileInner::Stderr => {
io::stderr().write_all(data)?;
Ok(())
}
_ => Err(io::Error::other("File not opened for writing")),
}
}
pub fn flush(&mut self) -> io::Result<()> {
match &mut self.inner {
FileInner::Write(writer) => writer.flush(),
FileInner::ReadWrite(reader) => reader.get_mut().flush(),
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenWrite(writer) => writer.flush(),
FileInner::Stdout => io::stdout().flush(),
FileInner::Stderr => io::stderr().flush(),
_ => Ok(()),
}
}
pub fn close(&mut self) -> io::Result<()> {
let _ = self.close_with_result()?;
Ok(())
}
pub fn close_with_result(&mut self) -> io::Result<LuaFileCloseResult> {
let _ = self.flush();
let inner = std::mem::replace(&mut self.inner, FileInner::Closed);
match inner {
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenRead(reader) => reader.close().map(LuaFileCloseResult::Process),
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenWrite(writer) => writer.close().map(LuaFileCloseResult::Process),
_ => Ok(LuaFileCloseResult::Closed),
}
}
}
impl UserDataTrait for LuaFile {
fn type_name(&self) -> &'static str {
"FILE*"
}
fn lua_close(&mut self) {
let _ = self.close();
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
pub fn create_file_metatable(l: &mut LuaState) -> LuaResult<LuaValue> {
let mt = l.create_table(0, 1)?;
let index_table = l.create_table(0, 7)?;
let read_key = l.create_string("read")?;
l.raw_set(&index_table, read_key, LuaValue::cfunction(file_read));
let write_key = l.create_string("write")?;
l.raw_set(&index_table, write_key, LuaValue::cfunction(file_write));
let flush_key = l.create_string("flush")?;
l.raw_set(&index_table, flush_key, LuaValue::cfunction(file_flush));
let close_key = l.create_string("close")?;
l.raw_set(&index_table, close_key, LuaValue::cfunction(file_close));
let lines_key = l.create_string("lines")?;
l.raw_set(&index_table, lines_key, LuaValue::cfunction(file_lines));
let seek_key = l.create_string("seek")?;
l.raw_set(&index_table, seek_key, LuaValue::cfunction(file_seek));
let setvbuf_key = l.create_string("setvbuf")?;
l.raw_set(&index_table, setvbuf_key, LuaValue::cfunction(file_setvbuf));
let index_key = l.create_string("__index")?;
l.raw_set(&mt, index_key, index_table);
let close_mm_key = l.create_string("__close")?;
l.raw_set(&mt, close_mm_key, LuaValue::cfunction(file_gc_close));
let gc_key = l.create_string("__gc")?;
l.raw_set(&mt, gc_key, LuaValue::cfunction(file_gc_close));
let tostring_key = l.create_string("__tostring")?;
l.raw_set(&mt, tostring_key, LuaValue::cfunction(file_tostring));
let name_key = l.create_string("__name")?;
let name_val = l.create_string("FILE*")?;
l.raw_set(&mt, name_key, name_val);
Ok(mt)
}
fn file_read(l: &mut LuaState) -> LuaResult<usize> {
let file_val = l
.get_arg(1)
.ok_or_else(|| l.error("file:read requires file handle".to_string()))?;
let mut formats: Vec<LuaValue> = Vec::new();
let mut i = 2;
while let Some(v) = l.get_arg(i) {
formats.push(v);
i += 1;
}
if let Some(ud) = file_val.as_userdata_mut() {
let Ok(data) = ud.get_data_mut() else {
return Err(l.error("attempt to use an expired file handle".to_string()));
};
if let Some(lua_file) = data.downcast_mut::<LuaFile>() {
let read_result = super::read_many_formats_file(l, lua_file, &formats)?;
return super::push_read_results(l, read_result);
}
}
Err(l.error("expected file handle".to_string()))
}
fn file_write(l: &mut LuaState) -> LuaResult<usize> {
let file_val = l
.get_arg(1)
.ok_or_else(|| l.error("file:write requires file handle".to_string()))?;
if let Some(ud) = file_val.as_userdata_mut() {
let Ok(data) = ud.get_data_mut() else {
return Err(l.error("attempt to use an expired file handle".to_string()));
};
if let Some(lua_file) = data.downcast_mut::<LuaFile>() {
let mut i = 2;
while let Some(val) = l.get_arg(i) {
let write_result = match val.kind() {
LuaValueKind::String => match val.as_bytes() {
Some(bytes) => lua_file.write_bytes(bytes),
None => {
return Err(l.error("write expects strings or numbers".to_string()));
}
},
LuaValueKind::Integer => {
if let Some(n) = val.as_integer() {
lua_file.write(&n.to_string())
} else {
return Err(l.error("write expects strings or numbers".to_string()));
}
}
LuaValueKind::Float => {
if let Some(n) = val.as_float() {
lua_file.write(&n.to_string())
} else {
return Err(l.error("write expects strings or numbers".to_string()));
}
}
_ => {
return Err(l.error("write expects strings or numbers".to_string()));
}
};
if let Err(e) = write_result {
l.push_value(LuaValue::nil())?;
let msg = l.create_string(&format!("{}", e))?;
l.push_value(msg)?;
let errno = e.raw_os_error().unwrap_or(0);
l.push_value(LuaValue::integer(errno as i64))?;
return Ok(3);
}
i += 1;
}
l.push_value(file_val)?;
return Ok(1);
}
}
Err(l.error("expected file handle".to_string()))
}
fn file_flush(l: &mut LuaState) -> LuaResult<usize> {
let file_val = l
.get_arg(1)
.ok_or_else(|| l.error("file:flush requires file handle".to_string()))?;
if let Some(ud) = file_val.as_userdata_mut() {
let Ok(data) = ud.get_data_mut() else {
return Err(l.error("attempt to use an expired file handle".to_string()));
};
if let Some(lua_file) = data.downcast_mut::<LuaFile>() {
if let Err(e) = lua_file.flush() {
l.push_value(LuaValue::nil())?;
let msg = format!("{}", e);
let errno = e.raw_os_error().unwrap_or(0) as i64;
let err_str = l.create_string(&msg)?;
l.push_value(err_str)?;
l.push_value(LuaValue::integer(errno))?;
return Ok(3);
}
l.push_value(LuaValue::boolean(true))?;
return Ok(1);
}
}
Err(l.error("expected file handle".to_string()))
}
fn file_gc_close(l: &mut LuaState) -> LuaResult<usize> {
let file_val = match l.get_arg(1) {
Some(v) => v,
None => {
return Err(crate::stdlib::debug::arg_typeerror_novalue(l, 1, "FILE*"));
}
};
if let Some(ud) = file_val.as_userdata_mut() {
let Ok(data) = ud.get_data_mut() else {
return Err(l.error("attempt to use an expired file handle".to_string()));
};
if let Some(lua_file) = data.downcast_mut::<LuaFile>() {
if !lua_file.is_closed() && !lua_file.is_std_stream() {
let _ = lua_file.close_with_result();
}
return Ok(0);
}
}
Err(crate::stdlib::debug::arg_typeerror(
l, 1, "FILE*", &file_val,
))
}
fn file_close(l: &mut LuaState) -> LuaResult<usize> {
let file_val = match l.get_arg(1) {
Some(v) => v,
None => {
return Err(crate::stdlib::debug::arg_typeerror_novalue(l, 1, "FILE*"));
}
};
if let Some(ud) = file_val.as_userdata_mut() {
let Ok(data) = ud.get_data_mut() else {
return Err(l.error("attempt to use an expired file handle".to_string()));
};
if let Some(lua_file) = data.downcast_mut::<LuaFile>() {
if lua_file.is_closed() {
return Err(l.error("attempt to use a closed file".to_string()));
}
if lua_file.is_std_stream() {
l.push_value(LuaValue::nil())?;
let msg = l.create_string("cannot close standard file")?;
l.push_value(msg)?;
return Ok(2);
}
match lua_file.close_with_result() {
Ok(LuaFileCloseResult::Closed) => {
l.push_value(LuaValue::boolean(true))?;
return Ok(1);
}
Ok(LuaFileCloseResult::Process(status)) => {
if status.success {
l.push_value(LuaValue::boolean(true))?;
} else {
l.push_value(LuaValue::nil())?;
}
let kind = l.create_string(status.kind)?;
l.push_value(kind)?;
l.push_value(LuaValue::integer(status.code as i64))?;
return Ok(3);
}
Err(e) => {
l.push_value(LuaValue::nil())?;
let msg = format!("{}", e);
let errno = e.raw_os_error().unwrap_or(0) as i64;
let err_str = l.create_string(&msg)?;
l.push_value(err_str)?;
l.push_value(LuaValue::integer(errno))?;
return Ok(3);
}
}
}
}
Err(crate::stdlib::debug::arg_typeerror(
l, 1, "FILE*", &file_val,
))
}
fn file_tostring(l: &mut LuaState) -> LuaResult<usize> {
let file_val = l
.get_arg(1)
.ok_or_else(|| l.error("expected file handle".to_string()))?;
if let Some(ud) = file_val.as_userdata_mut() {
let Ok(data) = ud.get_data_mut() else {
return Err(l.error("attempt to use an expired file handle".to_string()));
};
if let Some(lua_file) = data.downcast_mut::<LuaFile>() {
let s = if lua_file.is_closed() {
"file (closed)".to_string()
} else {
format!("file ({:p})", ud as *const _)
};
let val = l.create_string(&s)?;
l.push_value(val)?;
return Ok(1);
}
}
let val = l.create_string("file (closed)")?;
l.push_value(val)?;
Ok(1)
}
fn file_lines(l: &mut LuaState) -> LuaResult<usize> {
let file_val = l
.get_arg(1)
.ok_or_else(|| l.error("file:lines requires file handle".to_string()))?;
let mut formats: Vec<LuaValue> = Vec::new();
let mut i = 2;
while let Some(v) = l.get_arg(i) {
formats.push(v);
i += 1;
}
let state_table = l.create_table(0, 5)?;
let file_key = l.create_string("file")?;
l.raw_set(&state_table, file_key, file_val);
let closed_key = l.create_string("closed")?;
l.raw_set(&state_table, closed_key, LuaValue::boolean(false));
let noclose_key = l.create_string("noclose")?;
l.raw_set(&state_table, noclose_key, LuaValue::boolean(true));
let fmts_table = l.create_table(formats.len(), 0)?;
for (idx, fmt) in formats.iter().enumerate() {
l.raw_seti(&fmts_table, (idx + 1) as i64, *fmt);
}
let fmts_key = l.create_string("fmts")?;
l.raw_set(&state_table, fmts_key, fmts_table);
let nfmts_key = l.create_string("nfmts")?;
l.raw_set(
&state_table,
nfmts_key,
LuaValue::integer(formats.len() as i64),
);
let mt = l.create_table(0, 1)?;
let call_key = l.create_string("__call")?;
l.raw_set(&mt, call_key, LuaValue::cfunction(super::io_lines_call));
if let Some(t) = state_table.as_table_mut() {
t.set_metatable(Some(mt));
}
l.push_value(state_table)?;
Ok(1)
}
fn file_seek(l: &mut LuaState) -> LuaResult<usize> {
let file_val = l
.get_arg(1)
.ok_or_else(|| l.error("file:seek requires file handle".to_string()))?;
let whence = l
.get_arg(2)
.and_then(|v| v.as_str().map(|s| s.to_string()))
.unwrap_or_else(|| "cur".to_string());
let offset = l.get_arg(3).and_then(|v| v.as_integer()).unwrap_or(0);
if let Some(ud) = file_val.as_userdata_mut() {
let Ok(data) = ud.get_data_mut() else {
return Err(l.error("attempt to use an expired file handle".to_string()));
};
if let Some(lua_file) = data.downcast_mut::<LuaFile>() {
let seek_from = match whence.as_str() {
"set" => std::io::SeekFrom::Start(offset.max(0) as u64),
"cur" => std::io::SeekFrom::Current(offset),
"end" => std::io::SeekFrom::End(offset),
_ => {
return Err(l.error(format!("invalid whence: {}", whence)));
}
};
let pos = match &mut lua_file.inner {
FileInner::Read(reader) => reader.seek(seek_from),
FileInner::Write(writer) => writer.seek(seek_from),
FileInner::ReadWrite(reader) => reader.seek(seek_from),
#[cfg(not(target_arch = "wasm32"))]
FileInner::PopenRead(_) | FileInner::PopenWrite(_) => {
l.push_value(LuaValue::nil())?;
let msg = l.create_string("cannot seek on piped stream")?;
l.push_value(msg)?;
l.push_value(LuaValue::integer(29))?;
return Ok(3);
}
FileInner::Closed => {
return Err(l.error("file is closed".to_string()));
}
FileInner::Stdin | FileInner::Stdout | FileInner::Stderr => {
l.push_value(LuaValue::nil())?;
let msg = l.create_string("cannot seek on standard stream")?;
l.push_value(msg)?;
l.push_value(LuaValue::integer(29))?; return Ok(3);
}
};
match pos {
Ok(position) => {
l.push_value(LuaValue::integer(position as i64))?;
return Ok(1);
}
Err(e) => {
l.push_value(LuaValue::nil())?;
let msg = l.create_string(&e.to_string())?;
l.push_value(msg)?;
let errno = e.raw_os_error().unwrap_or(0) as i64;
l.push_value(LuaValue::integer(errno))?;
return Ok(3);
}
}
}
}
Err(l.error("expected file handle".to_string()))
}
fn file_setvbuf(l: &mut LuaState) -> LuaResult<usize> {
let file_val = l
.get_arg(1)
.ok_or_else(|| l.error("file:setvbuf requires file handle".to_string()))?;
let _mode = l
.get_arg(2)
.and_then(|v| v.as_str().map(|s| s.to_string()))
.unwrap_or_else(|| "full".to_string());
let _size = l.get_arg(3).and_then(|v| v.as_integer());
l.push_value(file_val)?;
Ok(1)
}