1use std::io::{Read, Seek, SeekFrom, Write};
11
12use crate::numeric::{self, FloatFmt, Num};
13use crate::runtime::value::f2i_exact;
14use crate::runtime::{FileHandle, Gc, Table, Userdata, UserdataPayload, Value};
15use crate::version::LuaVersion;
16use crate::vm::argcheck::{self, Args};
17use crate::vm::builtins::{arg_error, raise_str};
18use crate::vm::error::LuaError;
19use crate::vm::exec::Vm;
20
21const EINVAL: i32 = 22;
24const ENOMEM: i32 = 12;
25const EBADF: i32 = 9;
26#[cfg(not(unix))]
27const ESPIPE: i32 = 29;
28
29const LUAL_BUFFERSIZE: i64 = 1024;
32
33const READ_CHUNK: usize = 4096;
35
36pub(crate) fn open_io(vm: &mut Vm) {
37 let v = vm.version();
38 let io = vm.heap.new_table();
39 for (name, f) in [
40 ("close", io_close as crate::runtime::value::NativeFn),
41 ("flush", io_flush),
42 ("input", io_input),
43 ("lines", io_lines),
44 ("open", io_open),
45 ("output", io_output),
46 ("popen", io_popen),
47 ("read", io_read),
48 ("tmpfile", io_tmpfile),
49 ("type", io_type),
50 ("write", io_write),
51 ] {
52 put_native(vm, io, name, f);
53 }
54 let close: crate::runtime::value::NativeFn = match v {
58 LuaVersion::Lua51 => f_close_51,
59 LuaVersion::Lua52 => io_close,
60 _ => f_close,
61 };
62 let methods: [(&str, crate::runtime::value::NativeFn); 7] = [
63 ("close", close),
64 ("flush", f_flush),
65 ("lines", f_lines),
66 ("read", f_read),
67 ("seek", f_seek),
68 ("setvbuf", f_setvbuf),
69 ("write", f_write),
70 ];
71 let mt = vm.heap.new_table();
75 let index = if v >= LuaVersion::Lua54 {
76 vm.heap.new_table()
77 } else {
78 mt
79 };
80 for (name, f) in methods {
81 put_native(vm, index, name, f);
82 }
83 put(vm, mt, "__index", Value::Table(index));
84 put_native(vm, mt, "__gc", f_gc);
85 put_native(vm, mt, "__tostring", f_tostring);
86 if v >= LuaVersion::Lua53 {
87 let n = Value::Str(vm.heap.intern(b"FILE*"));
88 put(vm, mt, "__name", n);
89 }
90 if v >= LuaVersion::Lua54 {
91 put_native(vm, mt, "__close", f_gc);
94 }
95 vm.barrier_back_table(index);
96 vm.barrier_back_table(mt);
97 vm.file_mt = Some(mt);
98
99 for (name, fh) in [
100 ("stdin", FileHandle::Stdin),
101 ("stdout", FileHandle::Stdout),
102 ("stderr", FileHandle::Stderr),
103 ] {
104 let writable = !matches!(fh, FileHandle::Stdin);
105 let h = new_file(vm, fh, writable);
106 put(vm, io, name, Value::Userdata(h));
107 match name {
108 "stdin" => {
109 vm.io_input = Some(h);
110 vm.io_stdin = Some(h);
111 }
112 "stdout" => vm.io_output = Some(h),
113 _ => {}
114 }
115 }
116 vm.set_global("io", Value::Table(io))
117 .expect("stdlib registration");
118 vm.barrier_back_table(io);
119}
120
121impl Vm {
122 pub fn read_stdin_line(&mut self, size: usize) -> std::io::Result<Option<Vec<u8>>> {
128 let mut line = Vec::new();
129 if let Some(u) = self.io_stdin {
130 while line.len() + 1 < size {
131 let Some(b) = getc(u)? else { break };
132 line.push(b);
133 if b == b'\n' {
134 break;
135 }
136 }
137 } else {
138 use std::io::BufRead;
140 let mut input = std::io::stdin().lock();
141 while line.len() + 1 < size && line.last() != Some(&b'\n') {
142 let buf = input.fill_buf()?;
143 if buf.is_empty() {
144 break;
145 }
146 let room = &buf[..buf.len().min(size - 1 - line.len())];
147 let n = room
148 .iter()
149 .position(|&b| b == b'\n')
150 .map_or(room.len(), |i| i + 1);
151 line.extend_from_slice(&room[..n]);
152 input.consume(n);
153 }
154 }
155 Ok((!line.is_empty()).then_some(line))
156 }
157}
158
159fn put(vm: &mut Vm, t: Gc<Table>, k: &str, v: Value) {
160 let k = Value::Str(vm.heap.intern(k.as_bytes()));
161 unsafe { t.as_mut() }
163 .set(&mut vm.heap, k, v)
164 .expect("valid key");
165}
166
167fn put_native(vm: &mut Vm, t: Gc<Table>, k: &str, f: crate::runtime::value::NativeFn) {
168 let fv = vm.native(f);
169 put(vm, t, k, fv);
170}
171
172fn posix_error(code: i32) -> std::io::Error {
178 #[cfg(windows)]
179 {
180 std::io::Error::other(PosixErrno(code))
181 }
182 #[cfg(not(windows))]
183 {
184 std::io::Error::from_raw_os_error(code)
185 }
186}
187
188#[cfg(windows)]
189#[derive(Debug)]
190struct PosixErrno(i32);
191
192#[cfg(windows)]
193impl std::fmt::Display for PosixErrno {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 f.write_str(match self.0 {
197 EBADF => "Bad file descriptor",
198 ENOMEM => "Not enough space",
199 EINVAL => "Invalid argument",
200 ESPIPE => "Invalid seek",
201 _ => "Unknown error",
202 })
203 }
204}
205
206#[cfg(windows)]
207impl std::error::Error for PosixErrno {}
208
209fn errno(e: &std::io::Error) -> Option<i32> {
211 #[cfg(windows)]
212 if let Some(p) = e.get_ref().and_then(|r| r.downcast_ref::<PosixErrno>()) {
213 return Some(p.0);
214 }
215 e.raw_os_error()
216}
217
218pub(crate) fn strerror(e: &std::io::Error) -> String {
220 let s = e.to_string();
221 match e.raw_os_error() {
222 Some(code) => s
223 .strip_suffix(&format!(" (os error {code})"))
224 .expect("std renders OS errors with an '(os error N)' suffix")
225 .to_string(),
226 None => s,
227 }
228}
229
230pub(crate) fn file_fail(vm: &mut Vm, fs: u32, fname: Option<&[u8]>, e: &std::io::Error) -> u32 {
232 let vals = file_fail_values(vm, fname, e);
233 vm.nat_return(fs, &vals)
234}
235
236fn file_fail_values(vm: &mut Vm, fname: Option<&[u8]>, e: &std::io::Error) -> [Value; 3] {
237 let mut msg = Vec::new();
238 if let Some(n) = fname {
239 msg.extend_from_slice(c_str(n));
240 msg.extend_from_slice(b": ");
241 }
242 msg.extend_from_slice(strerror(e).as_bytes());
243 let code = errno(e).map_or(0, i64::from);
244 let m = Value::Str(vm.heap.intern(&msg));
245 [Value::Nil, m, Value::Int(code)]
246}
247
248fn file_ok(vm: &mut Vm, fs: u32) -> u32 {
250 vm.nat_return(fs, &[Value::Bool(true)])
251}
252
253#[cfg(any(unix, windows))]
256pub(crate) fn exec_result(
257 vm: &mut Vm,
258 fs: u32,
259 status: std::io::Result<std::process::ExitStatus>,
260) -> u32 {
261 let status = match status {
262 Ok(s) => s,
263 Err(e) => return file_fail(vm, fs, None, &e),
264 };
265 let (what, code) = exit_status_breakdown(&status);
266 let ok = if what == "exit" && code == 0 {
267 Value::Bool(true)
268 } else {
269 Value::Nil
270 };
271 let w = Value::Str(vm.heap.intern(what.as_bytes()));
272 vm.nat_return(fs, &[ok, w, Value::Int(code as i64)])
273}
274
275#[cfg(any(unix, windows))]
278pub(crate) fn exit_status_breakdown(status: &std::process::ExitStatus) -> (&'static str, i32) {
279 #[cfg(unix)]
280 {
281 use std::os::unix::process::ExitStatusExt;
282 if let Some(sig) = status.signal() {
283 return ("signal", sig);
284 }
285 }
286 (
287 "exit",
288 status
289 .code()
290 .expect("a status that is not a signal carries a code"),
291 )
292}
293
294pub(crate) fn c_str(b: &[u8]) -> &[u8] {
299 b.split(|&c| c == 0)
300 .next()
301 .expect("split yields a first piece")
302}
303
304pub(crate) fn os_path(b: &[u8]) -> std::path::PathBuf {
306 #[cfg(unix)]
307 {
308 use std::os::unix::ffi::OsStrExt;
309 std::ffi::OsStr::from_bytes(c_str(b)).into()
310 }
311 #[cfg(not(unix))]
312 {
313 String::from_utf8_lossy(c_str(b)).into_owned().into()
314 }
315}
316
317fn new_file(vm: &mut Vm, fh: FileHandle, writable: bool) -> Gc<Userdata> {
324 let u = vm.heap.new_userdata(UserdataPayload::File(fh), writable);
325 unsafe { u.as_mut() }.set_metatable(vm.file_mt);
327 vm.heap.register_finalizable_userdata(u);
328 u
329}
330
331fn test_file(vm: &Vm, v: Value) -> Option<Gc<Userdata>> {
335 match v {
336 Value::Userdata(u)
337 if matches!(u.payload, UserdataPayload::File(_))
338 && u.metatable()
339 .zip(vm.file_mt)
340 .is_some_and(|(a, b)| a.ptr_eq(b)) =>
341 {
342 Some(u)
343 }
344 _ => None,
345 }
346}
347
348fn check_stream(vm: &mut Vm, a: Args, i: u32) -> Result<Gc<Userdata>, LuaError> {
350 match test_file(vm, a.get(vm, i)) {
351 Some(u) if !a.is_none(i) => Ok(u),
352 _ => Err(argcheck::type_error(vm, a, i, "FILE*")),
353 }
354}
355
356fn check_open(vm: &mut Vm, a: Args, i: u32) -> Result<Gc<Userdata>, LuaError> {
358 let u = check_stream(vm, a, i)?;
359 if u.file().is_closed() {
360 return Err(raise_str(vm, "attempt to use a closed file"));
361 }
362 Ok(u)
363}
364
365pub(crate) fn flush_all(vm: &mut Vm) {
367 for u in vm.heap.finalizable_userdata() {
368 if matches!(u.payload, UserdataPayload::File(ref fh) if !fh.is_closed()) {
369 let _ = drain_write_buf(u);
372 }
373 }
374 let _ = std::io::stdout().flush(); }
376
377enum Closed {
382 Std,
384 File(std::io::Result<()>),
386 #[cfg(any(unix, windows))]
388 Pipe(std::io::Result<std::process::ExitStatus>),
389}
390
391fn close_stream(u: Gc<Userdata>) -> Closed {
392 if u.file().is_std() {
393 return Closed::Std;
394 }
395 let flushed = drain_write_buf(u);
396 let m = unsafe { u.as_mut() };
398 *m.file_mut() = FileHandle::Closed;
401 m.read_buf = Vec::new();
402 m.read_pos = 0;
403 #[cfg(any(unix, windows))]
404 if let Some(mut child) = m.popen_child.take() {
405 return Closed::Pipe(child.wait());
406 }
407 Closed::File(flushed)
408}
409
410fn push_closed(vm: &mut Vm, fs: u32, c: Closed) -> u32 {
411 match c {
412 Closed::Std => {
413 let m = Value::Str(vm.heap.intern(b"cannot close standard file"));
414 vm.nat_return(fs, &[Value::Nil, m])
415 }
416 Closed::File(Ok(())) => file_ok(vm, fs),
417 Closed::File(Err(e)) => file_fail(vm, fs, None, &e),
418 #[cfg(any(unix, windows))]
420 Closed::Pipe(r) if vm.version() == LuaVersion::Lua51 => match r {
421 Ok(_) => file_ok(vm, fs),
422 Err(e) => file_fail(vm, fs, None, &e),
423 },
424 #[cfg(any(unix, windows))]
425 Closed::Pipe(r) => exec_result(vm, fs, r),
426 }
427}
428
429fn io_close(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
432 let u = if nargs == 0 {
433 let d = default_file(vm, Io::Output);
434 if d.file().is_closed() {
435 return Err(raise_str(vm, "attempt to use a closed file"));
436 }
437 d
438 } else {
439 check_open(vm, Args::new(fs, nargs), 0)?
440 };
441 let c = close_stream(u);
442 Ok(push_closed(vm, fs, c))
443}
444
445fn f_close_51(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
448 if nargs == 0 {
449 return Err(arg_error(vm, 1, "FILE* expected, got nil"));
450 }
451 f_close(vm, fs, nargs)
452}
453
454fn f_close(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
455 let u = check_open(vm, Args::new(fs, nargs), 0)?;
456 let c = close_stream(u);
457 Ok(push_closed(vm, fs, c))
458}
459
460fn f_gc(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
462 let u = check_stream(vm, Args::new(fs, nargs), 0)?;
463 if !u.file().is_closed() {
464 let _ = close_stream(u); }
466 Ok(vm.nat_return(fs, &[]))
467}
468
469fn f_tostring(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
470 let u = check_stream(vm, Args::new(fs, nargs), 0)?;
471 let s = if u.file().is_closed() {
472 "file (closed)".to_string()
473 } else {
474 format!("file ({:p})", u.as_ptr())
475 };
476 let v = Value::Str(vm.heap.intern(s.as_bytes()));
477 Ok(vm.nat_return(fs, &[v]))
478}
479
480fn io_type(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
481 let v = argcheck::check_any(vm, Args::new(fs, nargs), 0)?;
482 let r = match test_file(vm, v) {
483 None => Value::Nil,
484 Some(u) if u.file().is_closed() => Value::Str(vm.heap.intern(b"closed file")),
485 Some(_) => Value::Str(vm.heap.intern(b"file")),
486 };
487 Ok(vm.nat_return(fs, &[r]))
488}
489
490fn fopen_options(mode: &[u8]) -> Option<(std::fs::OpenOptions, bool)> {
495 let mut o = std::fs::OpenOptions::new();
496 let first = *mode.first()?;
497 let rest = &mode[1..];
498 let plus = rest.contains(&b'+');
499 let excl = rest.contains(&b'x');
501 let writable = match first {
502 b'r' => {
503 o.read(true).write(plus);
504 plus
505 }
506 b'w' => {
507 o.write(true).read(plus).truncate(true);
508 if excl {
509 o.create_new(true);
510 } else {
511 o.create(true);
512 }
513 true
514 }
515 b'a' => {
516 o.append(true).read(plus);
517 if excl {
518 o.create_new(true);
519 } else {
520 o.create(true);
521 }
522 true
523 }
524 _ => return None,
525 };
526 Some((o, writable))
527}
528
529fn mode_ok(v: LuaVersion, mode: &[u8]) -> bool {
532 let Some((&first, rest)) = mode.split_first() else {
533 return false;
534 };
535 if !b"rwa".contains(&first) {
536 return false;
537 }
538 let rest = rest.strip_prefix(b"+").unwrap_or(rest);
539 match v {
540 LuaVersion::Lua52 => rest.is_empty() || rest == b"b",
541 _ => rest.iter().all(|&c| c == b'b'),
542 }
543}
544
545fn open_file(name: &[u8], mode: &[u8]) -> std::io::Result<(std::fs::File, bool)> {
546 let (o, writable) = fopen_options(c_str(mode)).ok_or_else(|| posix_error(EINVAL))?;
547 Ok((o.open(os_path(name))?, writable))
548}
549
550fn io_open(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
551 let a = Args::new(fs, nargs);
552 let name = argcheck::check_string(vm, a, 0)?.as_bytes().to_vec();
553 let mode = match argcheck::opt_string(vm, a, 1)? {
554 Some(m) => m.as_bytes().to_vec(),
555 None => b"r".to_vec(),
556 };
557 if vm.version() >= LuaVersion::Lua52 && !mode_ok(vm.version(), &mode) {
558 return Err(arg_error(vm, 2, "invalid mode"));
559 }
560 match open_file(&name, &mode) {
561 Ok((f, writable)) => {
562 let u = new_file(vm, FileHandle::File(f), writable);
563 Ok(vm.nat_return(fs, &[Value::Userdata(u)]))
564 }
565 Err(e) => Ok(file_fail(vm, fs, Some(&name), &e)),
566 }
567}
568
569fn io_tmpfile(vm: &mut Vm, fs: u32, _nargs: u32) -> Result<u32, LuaError> {
570 use std::sync::atomic::{AtomicU64, Ordering};
571 static CTR: AtomicU64 = AtomicU64::new(0);
572 let n = CTR.fetch_add(1, Ordering::Relaxed);
573 let mut path = std::env::temp_dir();
574 path.push(format!("lua_tmp_{}_{n}", std::process::id()));
575 let file = match std::fs::OpenOptions::new()
576 .read(true)
577 .write(true)
578 .create_new(true)
579 .open(&path)
580 {
581 Ok(f) => f,
582 Err(e) => return Ok(file_fail(vm, fs, None, &e)),
583 };
584 if let Err(e) = std::fs::remove_file(&path) {
586 return Ok(file_fail(vm, fs, None, &e));
587 }
588 let u = new_file(vm, FileHandle::File(file), true);
589 Ok(vm.nat_return(fs, &[Value::Userdata(u)]))
590}
591
592#[cfg(any(unix, windows))]
595fn io_popen(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
596 let a = Args::new(fs, nargs);
597 let prog = argcheck::check_string(vm, a, 0)?.as_bytes().to_vec();
598 let mode = match argcheck::opt_string(vm, a, 1)? {
599 Some(m) => m.as_bytes().to_vec(),
600 None => b"r".to_vec(),
601 };
602 let read = match c_str(&mode) {
606 b"r" => true,
607 b"w" => false,
608 _ if vm.version() >= LuaVersion::Lua53 => return Err(arg_error(vm, 2, "invalid mode")),
609 _ => {
610 let e = posix_error(EINVAL);
611 return Ok(file_fail(vm, fs, Some(&prog), &e));
612 }
613 };
614 flush_all(vm);
617 let mut cmd = shell_command(&prog);
618 if read {
619 cmd.stdout(std::process::Stdio::piped());
620 } else {
621 cmd.stdin(std::process::Stdio::piped());
622 }
623 let mut child = match cmd.spawn() {
624 Ok(c) => c,
625 Err(e) => return Ok(file_fail(vm, fs, Some(&prog), &e)),
626 };
627 let file = if read {
628 pipe_file(child.stdout.take().expect("stdout was piped"))
629 } else {
630 pipe_file(child.stdin.take().expect("stdin was piped"))
631 };
632 let u = new_file(vm, FileHandle::File(file), !read);
633 unsafe { u.as_mut() }.popen_child = Some(child);
635 Ok(vm.nat_return(fs, &[Value::Userdata(u)]))
636}
637
638#[cfg(unix)]
640fn pipe_file(p: impl Into<std::os::fd::OwnedFd>) -> std::fs::File {
641 std::fs::File::from(p.into())
642}
643
644#[cfg(windows)]
645fn pipe_file(p: impl Into<std::os::windows::io::OwnedHandle>) -> std::fs::File {
646 std::fs::File::from(p.into())
647}
648
649#[cfg(not(any(unix, windows)))]
652fn io_popen(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
653 let a = Args::new(fs, nargs);
654 argcheck::check_string(vm, a, 0)?;
655 argcheck::opt_string(vm, a, 1)?;
656 Err(raise_str(vm, "'popen' not supported"))
657}
658
659#[cfg(any(unix, windows))]
661pub(crate) fn shell_command(cmd: &[u8]) -> std::process::Command {
662 #[cfg(unix)]
663 {
664 use std::os::unix::ffi::OsStrExt;
665 let mut c = std::process::Command::new("/bin/sh");
666 c.arg("-c").arg(std::ffi::OsStr::from_bytes(c_str(cmd)));
667 c
668 }
669 #[cfg(windows)]
670 {
671 let mut c = std::process::Command::new("cmd");
672 c.arg("/C")
673 .arg(String::from_utf8_lossy(c_str(cmd)).into_owned());
674 c
675 }
676}
677
678#[derive(Clone, Copy)]
681enum Io {
682 Input,
683 Output,
684}
685
686fn default_file(vm: &Vm, which: Io) -> Gc<Userdata> {
687 match which {
688 Io::Input => vm.io_input,
689 Io::Output => vm.io_output,
690 }
691 .expect("default streams are set when io opens")
692}
693
694fn get_io_file(vm: &mut Vm, which: Io) -> Result<Gc<Userdata>, LuaError> {
696 let u = default_file(vm, which);
697 if u.file().is_closed() {
698 let what = match which {
699 Io::Input => "input",
700 Io::Output => "output",
701 };
702 let adj = if vm.version() >= LuaVersion::Lua54 {
703 "default"
704 } else {
705 "standard"
706 };
707 return Err(raise_str(vm, &format!("{adj} {what} file is closed")));
708 }
709 Ok(u)
710}
711
712fn g_iofile(vm: &mut Vm, fs: u32, nargs: u32, which: Io) -> Result<u32, LuaError> {
715 let a = Args::new(fs, nargs);
716 if !a.is_none_or_nil(vm, 0) {
717 let v = a.get(vm, 0);
718 let u = match argcheck::to_str_bytes(vm, v) {
719 Some(name) => {
720 let mode: &[u8] = match which {
721 Io::Input => b"r",
722 Io::Output => b"w",
723 };
724 open_checked(vm, &name, mode)?
725 }
726 None => check_open(vm, a, 0)?,
727 };
728 match which {
729 Io::Input => vm.io_input = Some(u),
730 Io::Output => vm.io_output = Some(u),
731 }
732 }
733 let cur = default_file(vm, which);
734 Ok(vm.nat_return(fs, &[Value::Userdata(cur)]))
735}
736
737fn open_checked(vm: &mut Vm, name: &[u8], mode: &[u8]) -> Result<Gc<Userdata>, LuaError> {
739 match open_file(name, mode) {
740 Ok((f, writable)) => Ok(new_file(vm, FileHandle::File(f), writable)),
741 Err(e) => {
742 let n = String::from_utf8_lossy(c_str(name)).into_owned();
743 let err = strerror(&e);
744 Err(if vm.version() == LuaVersion::Lua51 {
745 arg_error(vm, 1, &format!("{n}: {err}"))
746 } else {
747 raise_str(vm, &format!("cannot open file '{n}' ({err})"))
748 })
749 }
750 }
751}
752
753fn io_input(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
754 g_iofile(vm, fs, nargs, Io::Input)
755}
756
757fn io_output(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
758 g_iofile(vm, fs, nargs, Io::Output)
759}
760
761fn fill(u: Gc<Userdata>) -> std::io::Result<bool> {
765 let m = unsafe { u.as_mut() };
767 let mut chunk = vec![0u8; READ_CHUNK];
768 let n = match m.file_mut() {
769 FileHandle::File(f) => f.read(&mut chunk)?,
770 FileHandle::Stdin => std::io::stdin().read(&mut chunk)?,
771 FileHandle::Stdout | FileHandle::Stderr => {
773 return Err(posix_error(EBADF));
774 }
775 FileHandle::Closed => unreachable!("reads check the stream is open"),
776 };
777 chunk.truncate(n);
778 m.read_buf = chunk;
779 m.read_pos = 0;
780 Ok(n > 0)
781}
782
783fn getc(u: Gc<Userdata>) -> std::io::Result<Option<u8>> {
785 if u.read_pos >= u.read_buf.len() && !fill(u)? {
786 return Ok(None);
787 }
788 let m = unsafe { u.as_mut() };
790 let b = m.read_buf[m.read_pos];
791 m.read_pos += 1;
792 Ok(Some(b))
793}
794
795fn unget(u: Gc<Userdata>, bytes: &[u8]) {
797 let m = unsafe { u.as_mut() };
799 if m.read_pos >= bytes.len() && m.read_buf[m.read_pos - bytes.len()..m.read_pos] == *bytes {
800 m.read_pos -= bytes.len();
801 return;
802 }
803 let mut buf = bytes.to_vec();
804 buf.extend_from_slice(&m.read_buf[m.read_pos..]);
805 m.read_buf = buf;
806 m.read_pos = 0;
807}
808
809fn read_ahead(u: Gc<Userdata>) -> i64 {
811 (u.read_buf.len() - u.read_pos) as i64
812}
813
814fn unread_ahead(u: Gc<Userdata>) -> std::io::Result<()> {
817 let ahead = read_ahead(u);
818 let m = unsafe { u.as_mut() };
820 if ahead > 0
821 && let FileHandle::File(f) = m.file_mut()
822 {
823 f.seek(SeekFrom::Current(-ahead))?;
824 }
825 m.read_buf = Vec::new();
826 m.read_pos = 0;
827 Ok(())
828}
829
830fn write_to(u: Gc<Userdata>, bytes: &[u8]) -> std::io::Result<()> {
832 match unsafe { u.as_mut() }.file_mut() {
834 FileHandle::File(f) => f.write_all(bytes),
835 FileHandle::Stdout => std::io::stdout().write_all(bytes),
836 FileHandle::Stderr => std::io::stderr().write_all(bytes),
837 FileHandle::Stdin => Err(posix_error(EBADF)),
838 FileHandle::Closed => unreachable!("writes check the stream is open"),
839 }
840}
841
842fn drain_write_buf(u: Gc<Userdata>) -> std::io::Result<()> {
845 let buf = std::mem::take(&mut unsafe { u.as_mut() }.write_buf);
847 if buf.is_empty() {
848 return Ok(());
849 }
850 write_to(u, &buf)
851}
852
853fn put_bytes(u: Gc<Userdata>, bytes: &[u8]) -> std::io::Result<()> {
855 if !matches!(u.file(), FileHandle::File(_)) || !u.writable {
856 return write_to(u, bytes);
858 }
859 unread_ahead(u)?;
860 let m = unsafe { u.as_mut() };
862 match m.buf_mode {
863 BUF_NO => write_to(u, bytes),
864 BUF_LINE => {
865 m.write_buf.extend_from_slice(bytes);
866 match m.write_buf.iter().rposition(|&b| b == b'\n') {
867 Some(nl) => {
868 let out: Vec<u8> = m.write_buf.drain(..=nl).collect();
869 write_to(u, &out)
870 }
871 None => Ok(()),
872 }
873 }
874 _ => {
875 m.write_buf.extend_from_slice(bytes);
876 Ok(())
877 }
878 }
879}
880
881const BUF_FULL: u8 = 0;
883const BUF_LINE: u8 = 1;
884const BUF_NO: u8 = 2;
885
886fn flush_stream(u: Gc<Userdata>) -> std::io::Result<()> {
887 drain_write_buf(u)?;
888 match unsafe { u.as_mut() }.file_mut() {
890 FileHandle::File(f) => f.flush(),
891 FileHandle::Stdout => std::io::stdout().flush(),
892 FileHandle::Stderr => std::io::stderr().flush(),
893 FileHandle::Stdin | FileHandle::Closed => Ok(()),
894 }
895}
896
897fn number_text(vm: &Vm, n: Num) -> Vec<u8> {
902 let fmt = match vm.version() {
903 LuaVersion::Lua55 => vm.float_fmt(),
904 _ => FloatFmt::Legacy14,
905 };
906 numeric::num_to_string_for(n, fmt).into_bytes()
907}
908
909fn g_write(vm: &mut Vm, fs: u32, u: Gc<Userdata>, args: Args, first: u32) -> Result<u32, LuaError> {
911 let v = vm.version();
912 let mut total: i64 = 0;
913 let mut failure: Option<std::io::Error> = None;
914 for i in first..args.n {
915 let bytes = match args.get(vm, i) {
916 Value::Int(x) => number_text(vm, Num::Int(x)),
917 Value::Float(f) => number_text(vm, Num::Float(f)),
918 _ => argcheck::check_string(vm, args, i)?.as_bytes().to_vec(),
919 };
920 if failure.is_some() {
923 continue;
924 }
925 match put_bytes(u, &bytes) {
926 Ok(()) => total += bytes.len() as i64,
927 Err(e) if v >= LuaVersion::Lua55 => {
928 let mut vals = file_fail_values(vm, None, &e).to_vec();
929 vals.push(Value::Int(total));
930 return Ok(vm.nat_return(fs, &vals));
931 }
932 Err(e) => failure = Some(e),
933 }
934 }
935 Ok(match failure {
936 Some(e) => file_fail(vm, fs, None, &e),
937 None if v == LuaVersion::Lua51 => file_ok(vm, fs),
939 None => vm.nat_return(fs, &[Value::Userdata(u)]),
940 })
941}
942
943fn io_write(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
944 let u = get_io_file(vm, Io::Output)?;
945 g_write(vm, fs, u, Args::new(fs, nargs), 0)
946}
947
948fn f_write(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
949 let a = Args::new(fs, nargs);
950 let u = check_open(vm, a, 0)?;
951 g_write(vm, fs, u, a, 1)
952}
953
954fn io_flush(vm: &mut Vm, fs: u32, _nargs: u32) -> Result<u32, LuaError> {
955 let u = get_io_file(vm, Io::Output)?;
956 Ok(match flush_stream(u) {
957 Ok(()) => file_ok(vm, fs),
958 Err(e) => file_fail(vm, fs, None, &e),
959 })
960}
961
962fn f_flush(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
963 let u = check_open(vm, Args::new(fs, nargs), 0)?;
964 Ok(match flush_stream(u) {
965 Ok(()) => file_ok(vm, fs),
966 Err(e) => file_fail(vm, fs, None, &e),
967 })
968}
969
970fn f_seek(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
973 let a = Args::new(fs, nargs);
974 let u = check_open(vm, a, 0)?;
975 let op = argcheck::check_option(vm, a, 1, Some("cur"), &["set", "cur", "end"])?;
976 let offset = if vm.version() == LuaVersion::Lua52 {
977 let p3 = argcheck::opt_number(vm, a, 2, 0.0)?;
979 let off = p3 as i64;
980 if off as f64 != p3 {
981 return Err(arg_error(vm, 3, "not an integer in proper range"));
982 }
983 off
984 } else {
985 argcheck::opt_integer(vm, a, 2, 0)?
986 };
987 match seek_stream(u, op, offset) {
988 Ok(pos) if vm.version() <= LuaVersion::Lua52 => {
990 Ok(vm.nat_return(fs, &[Value::Float(pos as f64)]))
991 }
992 Ok(pos) => Ok(vm.nat_return(fs, &[Value::Int(pos as i64)])),
993 Err(e) => Ok(file_fail(vm, fs, None, &e)),
994 }
995}
996
997fn seek_stream(u: Gc<Userdata>, op: usize, offset: i64) -> std::io::Result<u64> {
999 drain_write_buf(u)?;
1000 let ahead = read_ahead(u);
1001 let m = unsafe { u.as_mut() };
1003 let from = match op {
1004 0 if offset < 0 => return Err(posix_error(EINVAL)),
1005 0 => SeekFrom::Start(offset as u64),
1006 1 => match offset.checked_sub(ahead) {
1007 Some(off) => SeekFrom::Current(off),
1008 None => return Err(posix_error(EINVAL)),
1009 },
1010 _ => SeekFrom::End(offset),
1011 };
1012 let pos = match m.file_mut() {
1013 FileHandle::File(f) => f.seek(from)?,
1014 std_stream => seek_std(std_stream, from)?,
1015 };
1016 m.read_buf = Vec::new();
1017 m.read_pos = 0;
1018 Ok(pos)
1019}
1020
1021#[cfg(unix)]
1024fn seek_std(fh: &FileHandle, from: SeekFrom) -> std::io::Result<u64> {
1025 use std::os::fd::AsFd;
1026 let fd = match fh {
1027 FileHandle::Stdin => std::io::stdin().as_fd().try_clone_to_owned()?,
1028 FileHandle::Stdout => std::io::stdout().as_fd().try_clone_to_owned()?,
1029 FileHandle::Stderr => std::io::stderr().as_fd().try_clone_to_owned()?,
1030 FileHandle::File(_) | FileHandle::Closed => unreachable!("only standard streams"),
1031 };
1032 std::fs::File::from(fd).seek(from)
1033}
1034
1035#[cfg(not(unix))]
1036fn seek_std(_fh: &FileHandle, _from: SeekFrom) -> std::io::Result<u64> {
1037 Err(posix_error(ESPIPE))
1038}
1039
1040fn f_setvbuf(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
1041 let a = Args::new(fs, nargs);
1042 let u = check_open(vm, a, 0)?;
1043 let op = argcheck::check_option(vm, a, 1, None, &["no", "full", "line"])?;
1044 argcheck::opt_integer(vm, a, 2, LUAL_BUFFERSIZE)?;
1045 let mode = [BUF_NO, BUF_FULL, BUF_LINE][op];
1046 unsafe { u.as_mut() }.buf_mode = mode;
1048 if mode == BUF_NO
1049 && let Err(e) = drain_write_buf(u)
1050 {
1051 return Ok(file_fail(vm, fs, None, &e));
1052 }
1053 Ok(file_ok(vm, fs))
1054}
1055
1056enum ReadOut {
1061 Values(Vec<Value>),
1062 Error(std::io::Error),
1063}
1064
1065enum Fmt {
1067 Count(i64),
1068 Number,
1069 Line { keep_nl: bool },
1070 All,
1071}
1072
1073fn parse_format(vm: &mut Vm, fmt: Value, argno: u32) -> Result<Fmt, LuaError> {
1075 let v = vm.version();
1076 match fmt {
1077 Value::Int(n) => return Ok(Fmt::Count(n)),
1078 Value::Float(f) if v <= LuaVersion::Lua52 => return Ok(Fmt::Count(f as i64)),
1079 Value::Float(f) => {
1080 return f2i_exact(f)
1081 .map(Fmt::Count)
1082 .ok_or_else(|| arg_error(vm, argno, "number has no integer representation"));
1083 }
1084 _ => {}
1085 }
1086 let spec = match fmt {
1087 Value::Str(s) => s.as_bytes().to_vec(),
1088 _ if v <= LuaVersion::Lua52 => return Err(arg_error(vm, argno, "invalid option")),
1089 _ => {
1090 let tn = argcheck::typename_of(vm, fmt);
1091 return Err(arg_error(vm, argno, &format!("string expected, got {tn}")));
1092 }
1093 };
1094 let body = match spec.strip_prefix(b"*") {
1096 Some(b) => b,
1097 None if v <= LuaVersion::Lua52 => return Err(arg_error(vm, argno, "invalid option")),
1098 None => &spec,
1099 };
1100 Ok(match body.first() {
1101 Some(b'n') => Fmt::Number,
1102 Some(b'l') => Fmt::Line { keep_nl: false },
1103 Some(b'L') if v >= LuaVersion::Lua52 => Fmt::Line { keep_nl: true },
1104 Some(b'a') => Fmt::All,
1105 _ => return Err(arg_error(vm, argno, "invalid format")),
1106 })
1107}
1108
1109fn g_read(vm: &mut Vm, u: Gc<Userdata>, fmts: &[Value], argno0: u32) -> Result<ReadOut, LuaError> {
1112 if let Err(e) = drain_write_buf(u) {
1114 return Ok(ReadOut::Error(e));
1115 }
1116 if fmts.is_empty() {
1117 return Ok(match read_line(vm, u, false) {
1118 Ok(v) => ReadOut::Values(vec![v]),
1119 Err(e) => ReadOut::Error(e),
1120 });
1121 }
1122 let mut out = Vec::with_capacity(fmts.len());
1123 for (i, &f) in fmts.iter().enumerate() {
1124 let fmt = parse_format(vm, f, argno0 + i as u32)?;
1125 let r = match fmt {
1126 Fmt::Count(n) => read_count(vm, u, n)?,
1127 Fmt::Number => read_number(vm, u),
1128 Fmt::Line { keep_nl } => read_line(vm, u, keep_nl),
1129 Fmt::All => read_all(vm, u),
1130 };
1131 match r {
1132 Ok(v) => {
1133 let stop = v.is_nil();
1134 out.push(v);
1135 if stop {
1136 break;
1137 }
1138 }
1139 Err(e) => return Ok(ReadOut::Error(e)),
1140 }
1141 }
1142 Ok(ReadOut::Values(out))
1143}
1144
1145fn push_read(vm: &mut Vm, fs: u32, r: ReadOut) -> u32 {
1146 match r {
1147 ReadOut::Values(vals) => vm.nat_return(fs, &vals),
1148 ReadOut::Error(e) => file_fail(vm, fs, None, &e),
1149 }
1150}
1151
1152fn io_read(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
1153 let u = get_io_file(vm, Io::Input)?;
1154 let fmts: Vec<Value> = (0..nargs).map(|i| vm.nat_arg(fs, nargs, i)).collect();
1155 let r = g_read(vm, u, &fmts, 1)?;
1156 Ok(push_read(vm, fs, r))
1157}
1158
1159fn f_read(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
1160 let u = check_open(vm, Args::new(fs, nargs), 0)?;
1161 let fmts: Vec<Value> = (1..nargs).map(|i| vm.nat_arg(fs, nargs, i)).collect();
1162 let r = g_read(vm, u, &fmts, 2)?;
1163 Ok(push_read(vm, fs, r))
1164}
1165
1166#[cfg(any(
1168 target_os = "macos",
1169 target_os = "ios",
1170 target_os = "freebsd",
1171 target_os = "netbsd",
1172 target_os = "openbsd",
1173 target_os = "dragonfly"
1174))]
1175const BUFSIZ: usize = 1024;
1176#[cfg(not(any(
1177 target_os = "macos",
1178 target_os = "ios",
1179 target_os = "freebsd",
1180 target_os = "netbsd",
1181 target_os = "openbsd",
1182 target_os = "dragonfly"
1183)))]
1184const BUFSIZ: usize = 8192;
1185
1186fn read_line(vm: &mut Vm, u: Gc<Userdata>, keep_nl: bool) -> std::io::Result<Value> {
1189 if vm.version() <= LuaVersion::Lua52 {
1190 return read_line_fgets(vm, u, keep_nl);
1191 }
1192 let mut buf = Vec::new();
1193 let mut got_nl = false;
1194 while let Some(c) = getc(u)? {
1195 if c == b'\n' {
1196 got_nl = true;
1197 if keep_nl {
1198 buf.push(c);
1199 }
1200 break;
1201 }
1202 buf.push(c);
1203 }
1204 if got_nl || !buf.is_empty() {
1205 read_str(vm, &buf)
1206 } else {
1207 Ok(Value::Nil)
1208 }
1209}
1210
1211fn read_line_fgets(vm: &mut Vm, u: Gc<Userdata>, keep_nl: bool) -> std::io::Result<Value> {
1215 let mut out = Vec::new();
1216 loop {
1217 let mut chunk = Vec::new();
1218 while chunk.len() < BUFSIZ - 1 {
1219 match getc(u)? {
1220 Some(c) => {
1221 chunk.push(c);
1222 if c == b'\n' {
1223 break;
1224 }
1225 }
1226 None => break,
1227 }
1228 }
1229 if chunk.is_empty() {
1230 return if out.is_empty() {
1231 Ok(Value::Nil)
1232 } else {
1233 read_str(vm, &out)
1234 };
1235 }
1236 let len = chunk.iter().position(|&b| b == 0).unwrap_or(chunk.len());
1238 if len == 0 || chunk[len - 1] != b'\n' {
1239 out.extend_from_slice(&chunk[..len]);
1240 } else {
1241 let end = if keep_nl { len } else { len - 1 };
1242 out.extend_from_slice(&chunk[..end]);
1243 return read_str(vm, &out);
1244 }
1245 }
1246}
1247
1248fn read_str(vm: &mut Vm, bytes: &[u8]) -> std::io::Result<Value> {
1251 if bytes.len() > crate::runtime::string::MAX_LEN {
1252 return Err(posix_error(ENOMEM));
1253 }
1254 Ok(Value::Str(vm.heap.intern(bytes)))
1255}
1256
1257fn read_all(vm: &mut Vm, u: Gc<Userdata>) -> std::io::Result<Value> {
1259 let mut buf = Vec::new();
1260 loop {
1261 buf.extend_from_slice(&u.read_buf[u.read_pos..]);
1262 unsafe { u.as_mut() }.read_pos = u.read_buf.len();
1264 if !fill(u)? {
1265 break;
1266 }
1267 }
1268 read_str(vm, &buf)
1269}
1270
1271const UNALLOCATABLE: u64 = 1 << 47;
1273
1274fn read_count(vm: &mut Vm, u: Gc<Userdata>, n: i64) -> Result<std::io::Result<Value>, LuaError> {
1277 let size = n as u64;
1278 if size == 0 {
1279 return Ok(test_eof(vm, u));
1280 }
1281 if size >= UNALLOCATABLE && vm.version() >= LuaVersion::Lua52 {
1284 return Err(match vm.version() {
1285 LuaVersion::Lua52 if size > u64::MAX - 64 => {
1286 vm.plain_err("memory allocation error: block too big")
1287 }
1288 LuaVersion::Lua53 => raise_str(vm, "not enough memory for buffer allocation"),
1289 LuaVersion::Lua55 if size >= i64::MAX as u64 => {
1290 raise_str(vm, "resulting string too large")
1291 }
1292 _ => vm.plain_err("not enough memory"),
1293 });
1294 }
1295 let mut buf = Vec::new();
1296 while (buf.len() as u64) < size {
1297 let want = (size - buf.len() as u64) as usize;
1298 if u.read_pos >= u.read_buf.len() {
1299 match fill(u) {
1300 Ok(true) => {}
1301 Ok(false) => break,
1302 Err(e) => return Ok(Err(e)),
1303 }
1304 }
1305 let take = want.min(u.read_buf.len() - u.read_pos);
1306 buf.extend_from_slice(&u.read_buf[u.read_pos..u.read_pos + take]);
1307 unsafe { u.as_mut() }.read_pos += take;
1309 }
1310 Ok(if buf.is_empty() {
1311 Ok(Value::Nil)
1312 } else {
1313 read_str(vm, &buf)
1314 })
1315}
1316
1317fn test_eof(vm: &mut Vm, u: Gc<Userdata>) -> std::io::Result<Value> {
1319 Ok(match getc(u)? {
1320 Some(c) => {
1321 unget(u, &[c]);
1322 Value::Str(vm.heap.intern(b""))
1323 }
1324 None => Value::Nil,
1325 })
1326}
1327
1328fn read_number(vm: &mut Vm, u: Gc<Userdata>) -> std::io::Result<Value> {
1329 if vm.version() <= LuaVersion::Lua52 {
1330 return scan_double(u);
1331 }
1332 let buf = read_numeral(u)?;
1333 Ok(match numeric::str2num(&buf, true, true) {
1334 Some(Num::Int(i)) => Value::Int(i),
1335 Some(Num::Float(f)) => Value::Float(f),
1336 None => Value::Nil,
1337 })
1338}
1339
1340fn scan_double(u: Gc<Userdata>) -> std::io::Result<Value> {
1344 let mut c = getc(u)?;
1345 while matches!(c, Some(b) if is_c_space(b)) {
1346 c = getc(u)?;
1347 }
1348 let mut buf: Vec<u8> = Vec::new();
1349 let mut commit = 0; let mut state = Scan::Start;
1351 while let Some(b) = c {
1352 let next = scan_step(state, b, &buf);
1353 let Some((st, complete)) = next else { break };
1354 buf.push(b);
1355 if complete {
1356 commit = buf.len();
1357 }
1358 state = st;
1359 c = getc(u)?;
1360 }
1361 if let Some(b) = c {
1362 buf.push(b);
1363 }
1364 unget(u, &buf[commit..]);
1365 if commit == 0 {
1366 return Ok(Value::Nil);
1367 }
1368 Ok(Value::Float(parse_c_double(&buf[..commit])))
1369}
1370
1371fn is_c_space(b: u8) -> bool {
1372 matches!(b, b' ' | b'\t' | b'\n' | b'\x0b' | b'\x0c' | b'\r')
1373}
1374
1375#[derive(Clone, Copy, PartialEq)]
1376enum Scan {
1377 Start,
1378 Sign,
1379 Zero,
1380 Int,
1381 Dot,
1382 Frac,
1383 ExpMark,
1384 ExpSign,
1385 ExpDigits,
1386 HexX,
1387 HexInt,
1388 HexDot,
1389 HexFrac,
1390 Word,
1391 NanOpen,
1392 Done,
1393}
1394
1395fn scan_step(s: Scan, b: u8, buf: &[u8]) -> Option<(Scan, bool)> {
1398 let digits = |st| Some((st, true));
1399 match s {
1400 Scan::Start | Scan::Sign => match b {
1401 b'+' | b'-' if s == Scan::Start => Some((Scan::Sign, false)),
1402 b'0' => digits(Scan::Zero),
1403 b'1'..=b'9' => digits(Scan::Int),
1404 b'.' => Some((Scan::Dot, false)),
1405 b'i' | b'I' | b'n' | b'N' => Some((Scan::Word, false)),
1406 _ => None,
1407 },
1408 Scan::Zero if matches!(b, b'x' | b'X') => Some((Scan::HexX, false)),
1409 Scan::Zero | Scan::Int => match b {
1410 b'0'..=b'9' => digits(Scan::Int),
1411 b'.' => digits(Scan::Frac),
1412 b'e' | b'E' => Some((Scan::ExpMark, false)),
1413 _ => None,
1414 },
1415 Scan::Dot => match b {
1416 b'0'..=b'9' => digits(Scan::Frac),
1417 _ => None,
1418 },
1419 Scan::Frac => match b {
1420 b'0'..=b'9' => digits(Scan::Frac),
1421 b'e' | b'E' => Some((Scan::ExpMark, false)),
1422 _ => None,
1423 },
1424 Scan::ExpMark => match b {
1425 b'+' | b'-' => Some((Scan::ExpSign, false)),
1426 b'0'..=b'9' => digits(Scan::ExpDigits),
1427 _ => None,
1428 },
1429 Scan::ExpSign | Scan::ExpDigits => match b {
1430 b'0'..=b'9' => digits(Scan::ExpDigits),
1431 _ => None,
1432 },
1433 Scan::HexX => match b {
1434 b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F' => digits(Scan::HexInt),
1435 b'.' => Some((Scan::HexDot, false)),
1436 _ => None,
1437 },
1438 Scan::HexInt => match b {
1439 b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F' => digits(Scan::HexInt),
1440 b'.' => digits(Scan::HexFrac),
1441 b'p' | b'P' => Some((Scan::ExpMark, false)),
1442 _ => None,
1443 },
1444 Scan::HexDot | Scan::HexFrac => match b {
1445 b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F' => digits(Scan::HexFrac),
1446 b'p' | b'P' if s == Scan::HexFrac => Some((Scan::ExpMark, false)),
1447 _ => None,
1448 },
1449 Scan::Word => {
1450 let word: Vec<u8> = buf
1452 .iter()
1453 .skip_while(|&&c| c == b'+' || c == b'-')
1454 .map(u8::to_ascii_lowercase)
1455 .chain(std::iter::once(b.to_ascii_lowercase()))
1456 .collect();
1457 if b"infinity".starts_with(&word) {
1458 Some((Scan::Word, word == b"inf" || word == b"infinity"))
1459 } else if b"nan".starts_with(&word) {
1460 Some((Scan::Word, word == b"nan"))
1461 } else if word == b"nan(" {
1462 Some((Scan::NanOpen, false))
1463 } else {
1464 None
1465 }
1466 }
1467 Scan::NanOpen => match b {
1468 b')' => Some((Scan::Done, true)),
1469 b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'_' => Some((Scan::NanOpen, false)),
1470 _ => None,
1471 },
1472 Scan::Done => None,
1473 }
1474}
1475
1476fn parse_c_double(s: &[u8]) -> f64 {
1478 let (neg, body) = match s.split_first() {
1479 Some((b'-', rest)) => (true, rest),
1480 Some((b'+', rest)) => (false, rest),
1481 _ => (false, s),
1482 };
1483 let lower = body.to_ascii_lowercase();
1484 let mag = if lower.starts_with(b"inf") {
1485 f64::INFINITY
1486 } else if lower.starts_with(b"nan") {
1487 f64::NAN
1488 } else if lower.starts_with(b"0x") {
1489 match numeric::str2num(body, false, true) {
1490 Some(n) => n.as_f64(),
1491 None => unreachable!("the scanner only commits valid hex numerals"),
1492 }
1493 } else {
1494 std::str::from_utf8(body)
1495 .expect("decimal numerals are ASCII")
1496 .parse::<f64>()
1497 .expect("the scanner only commits valid decimal numerals")
1498 };
1499 if neg { -mag } else { mag }
1500}
1501
1502const L_MAXLENNUM: usize = 200;
1504
1505struct Rn {
1507 buf: Vec<u8>,
1508 c: Option<u8>,
1509}
1510
1511fn rn_next(rn: &mut Rn, u: Gc<Userdata>) -> std::io::Result<bool> {
1514 if rn.buf.len() >= L_MAXLENNUM {
1515 rn.buf.clear();
1516 return Ok(false);
1517 }
1518 if let Some(b) = rn.c {
1519 rn.buf.push(b);
1520 }
1521 rn.c = getc(u)?;
1522 Ok(true)
1523}
1524
1525fn rn_test(rn: &mut Rn, u: Gc<Userdata>, set: &[u8]) -> std::io::Result<bool> {
1527 if matches!(rn.c, Some(c) if set.contains(&c)) {
1528 return rn_next(rn, u);
1529 }
1530 Ok(false)
1531}
1532
1533fn rn_digits(rn: &mut Rn, u: Gc<Userdata>, hex: bool) -> std::io::Result<u32> {
1535 let mut count = 0;
1536 while matches!(rn.c, Some(c) if if hex { c.is_ascii_hexdigit() } else { c.is_ascii_digit() })
1537 && rn_next(rn, u)?
1538 {
1539 count += 1;
1540 }
1541 Ok(count)
1542}
1543
1544fn read_numeral(u: Gc<Userdata>) -> std::io::Result<Vec<u8>> {
1547 let mut c = getc(u)?;
1548 while matches!(c, Some(b) if is_c_space(b)) {
1549 c = getc(u)?;
1550 }
1551 let mut rn = Rn { buf: Vec::new(), c };
1552 let mut count = 0;
1553 let mut hex = false;
1554 rn_test(&mut rn, u, b"-+")?;
1555 if rn_test(&mut rn, u, b"0")? {
1556 if rn_test(&mut rn, u, b"xX")? {
1557 hex = true;
1558 } else {
1559 count = 1;
1560 }
1561 }
1562 count += rn_digits(&mut rn, u, hex)?;
1563 if rn_test(&mut rn, u, b".")? {
1564 count += rn_digits(&mut rn, u, hex)?;
1565 }
1566 if count > 0 && rn_test(&mut rn, u, if hex { b"pP" } else { b"eE" })? {
1567 rn_test(&mut rn, u, b"-+")?;
1568 rn_digits(&mut rn, u, false)?;
1569 }
1570 if let Some(b) = rn.c {
1571 unget(u, &[b]);
1572 }
1573 Ok(rn.buf)
1574}
1575
1576fn check_line_formats(vm: &mut Vm, n: u32) -> Result<(), LuaError> {
1582 let (max, argno, msg) = match vm.version() {
1583 LuaVersion::Lua51 => return Ok(()),
1584 LuaVersion::Lua52 => (17, 17, "too many options"),
1585 _ => (250, 252, "too many arguments"),
1586 };
1587 if n > max {
1588 return Err(arg_error(vm, argno, msg));
1589 }
1590 Ok(())
1591}
1592
1593fn make_lines(vm: &mut Vm, u: Gc<Userdata>, toclose: bool, fmts: &[Value]) -> Value {
1596 let mut up = vec![Value::Userdata(u), Value::Bool(toclose)];
1597 if vm.version() >= LuaVersion::Lua52 {
1598 up.extend_from_slice(fmts);
1599 }
1600 vm.native_with(io_readline, up.into_boxed_slice())
1601}
1602
1603fn f_lines(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
1604 let u = check_open(vm, Args::new(fs, nargs), 0)?;
1605 check_line_formats(vm, nargs.saturating_sub(1))?;
1606 let fmts: Vec<Value> = (1..nargs).map(|i| vm.nat_arg(fs, nargs, i)).collect();
1607 let it = make_lines(vm, u, false, &fmts);
1608 Ok(vm.nat_return(fs, &[it]))
1609}
1610
1611fn io_lines(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
1612 let a = Args::new(fs, nargs);
1613 let (u, toclose) = if a.is_none_or_nil(vm, 0) {
1617 let d = default_file(vm, Io::Input);
1618 if d.file().is_closed() {
1619 return Err(raise_str(vm, "attempt to use a closed file"));
1620 }
1621 (d, false)
1622 } else {
1623 let name = argcheck::check_string(vm, a, 0)?.as_bytes().to_vec();
1624 (open_checked(vm, &name, b"r")?, true)
1625 };
1626 let nfmt = nargs.saturating_sub(1);
1627 check_line_formats(vm, nfmt)?;
1628 let fmts: Vec<Value> = (1..nargs).map(|i| vm.nat_arg(fs, nargs, i)).collect();
1629 let it = make_lines(vm, u, toclose, &fmts);
1630 if toclose && vm.version() >= LuaVersion::Lua54 {
1632 return Ok(vm.nat_return(fs, &[it, Value::Nil, Value::Nil, Value::Userdata(u)]));
1633 }
1634 Ok(vm.nat_return(fs, &[it]))
1635}
1636
1637fn io_readline(vm: &mut Vm, fs: u32, _nargs: u32) -> Result<u32, LuaError> {
1639 let Value::Userdata(u) = vm.nat_upval(fs, 0) else {
1640 unreachable!("line iterator upvalue 0 is its file");
1641 };
1642 if u.file().is_closed() {
1643 return Err(raise_str(vm, "file is already closed"));
1644 }
1645 let fmts: Vec<Value> = (2..vm.nat_upcount(fs))
1646 .map(|i| vm.nat_upval(fs, i))
1647 .collect();
1648 let vals = match g_read(vm, u, &fmts, 2)? {
1649 ReadOut::Values(v) => v,
1650 ReadOut::Error(e) => return Err(raise_str(vm, &strerror(&e))),
1652 };
1653 if !vals[0].is_nil() {
1656 return Ok(vm.nat_return(fs, &vals));
1657 }
1658 if let Value::Bool(true) = vm.nat_upval(fs, 1) {
1659 let _ = close_stream(u); }
1661 Ok(vm.nat_return(fs, &[]))
1662}