piccolo icon indicating copy to clipboard operation
piccolo copied to clipboard

`Feature`: implemented (almost) all `io.*` and `file:*` functions in stdlib

Open reloginn opened this issue 7 months ago • 4 comments

All functions are based on and fully comply with the Lua 5.3 manual (https://www.lua.org/manual/5.3/).

Features

  • Implemented functions io.close, io.flush, io.input, io.lines, io.open, io.output, io.read, io.tmpfile, io.type, io.write, file:flush, file:lines, file:read, file:seek, file:write.
  • Added tests in io.lua for all new functions.
  • In Cargo.toml, the tempfile and either crates have been added. tempfile is used for io.tmpfile, and either is used as a helper in the IoFile type.

reloginn avatar May 08 '25 18:05 reloginn

@Aeledfyr since you are reviewing all PRs, could you please review this one as well?

notxvilka avatar May 21 '25 04:05 notxvilka

I can review this, but I'm only merging relatively small changes. Larger changes like this will have to wait until kyren is available to review as well, which will likely be a while from now.

I'll do a first-pass review of the larger PRs (and see if there are sub-parts that can be split out to merge sooner), but it may be a while before they can be fully merged.

Aeledfyr avatar May 21 '25 21:05 Aeledfyr

Another high-level thing that needs to be considered is how to handle sandboxing this, and how to expose that to the user. The main things we'd need to achieve for reasonable sandboxing:

  • Allowing the host to limit access to stdin/stdout/stderr, and/or replace them with custom files/streams
  • Allowing the host to limit what files can be opened, and what operations can be done on them

One approach that might make this possible would be to have the user provide the system interface through a trait implementation -- something like this:

type FileRef = Rc<RefCell<dyn IoFile>>;
trait IoContext {
    fn open(&self, path: &[u8], mode: Mode) -> Result<FileRef, Error>;
    fn tempfile(&self) -> Result<FileRef, Error>;
    fn stdin(&self) -> Option<FileRef>;
    fn stdout(&self) -> Option<FileRef>;
    fn stderr(&self) -> Option<FileRef>;
}
// For files that only support some of the read/write/seek
// operations, make a placeholder impl that always errors.
trait IoFile: Read + Write + Seek {
    fn close(&mut self) -> Result<(), Error>;
    fn flush(&mut self) -> Result<(), Error>;
}

struct LuaStdin(std::io::Stdin);
impl Read for LuaStdin { /* pass through */ }
impl Write for LuaStdin { /* always error */ }
impl Seek for LuaStdin { /* always error */ }
impl IoFile for LuaStdin {
    fn close(&mut self) -> Result<(), Error> { Ok(()) }
    fn flush(&mut self) -> Result<(), Error> { Ok(()) }
}

With that, load_io could just take in an implementation of IoContext and rely on the user to provide the implementation / sandboxing.

Aeledfyr avatar May 22 '25 21:05 Aeledfyr

Another high-level thing that needs to be considered is how to handle sandboxing this, and how to expose that to the user. The main things we'd need to achieve for reasonable sandboxing:

  • Allowing the host to limit access to stdin/stdout/stderr, and/or replace them with custom files/streams
  • Allowing the host to limit what files can be opened, and what operations can be done on them

One approach that might make this possible would be to have the user provide the system interface through a trait implementation -- something like this:

Yeah, this suggestion to add some kind of trait abstraction around I/O or OS details sounds good, to avoid assuming it's safe/desirable to expose the host OS / filesystem to Lua code.

I've been playing with using piccolo recently and at least for my use case it's a feature that piccolo doesn't support the io APIs which allow code to interact with the host platform.

rib avatar Jul 05 '25 01:07 rib