Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ add_subdirectory(lute/vm)
add_subdirectory(lute/process)
add_subdirectory(lute/system)
add_subdirectory(lute/time)
add_subdirectory(lute/io)

# executables
add_subdirectory(lute/cli)
Expand Down
7 changes: 7 additions & 0 deletions definitions/io.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
local io = {}

function io.input(): string
error("unimplemented")
end

return io
10 changes: 10 additions & 0 deletions examples/user_input_no_prompt.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- User input can be provided in multiple ways:
-- 1. lute user_input.luau
-- 2. echo "Hello!" | lute user_input_no_prompt.luau
-- 3. lute user_input_no_prompt.luau < input_text.txt

local io = require("@std/io")

-- Get user input
local input = io.input()
print(input)
10 changes: 10 additions & 0 deletions examples/user_input_with_prompt.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- User input can be provided in multiple ways:
-- 1. lute user_input.luau
-- 2. echo "Hello!" | lute user_input_with_prompt.luau
-- 3. lute user_input_with_prompt.luau < input_text.txt

local io = require("@std/io")

-- Get user input with prompt
local name = io.input("Please enter your name: ")
print(name)
2 changes: 1 addition & 1 deletion lute/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ target_sources(Lute.CLI.lib PRIVATE

target_compile_features(Lute.CLI.lib PUBLIC cxx_std_17)
target_include_directories(Lute.CLI.lib PUBLIC include ${CLI_GENERATED_INCLUDE_DIR})
target_link_libraries(Lute.CLI.lib PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.Analysis Luau.VM Lute.CLI.Commands Lute.Crypto Lute.Fs Lute.Luau Lute.Net Lute.Task Lute.VM Lute.Process Lute.System Lute.Time Lute.Require Lute.Runtime Luau.CLI.lib)
target_link_libraries(Lute.CLI.lib PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.Analysis Luau.VM Lute.CLI.Commands Lute.Crypto Lute.Fs Lute.IO Lute.Luau Lute.Net Lute.Task Lute.VM Lute.Process Lute.System Lute.Time Lute.Require Lute.Runtime Luau.CLI.lib)
target_compile_options(Lute.CLI.lib PRIVATE ${LUTE_OPTIONS})

add_executable(Lute.CLI)
Expand Down
2 changes: 2 additions & 0 deletions lute/cli/src/climain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "lute/compile.h"
#include "lute/crypto.h"
#include "lute/fs.h"
#include "lute/io.h"
#include "lute/luau.h"
#include "lute/net.h"
#include "lute/options.h"
Expand Down Expand Up @@ -73,6 +74,7 @@ static void luteopen_libs(lua_State* L)
{"@lute/vm", luteopen_vm},
{"@lute/system", luteopen_system},
{"@lute/time", luteopen_time},
{"@lute/io", luteopen_io},
}};

for (const auto& [name, func] : libs)
Expand Down
12 changes: 12 additions & 0 deletions lute/io/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
add_library(Lute.IO STATIC)

target_sources(Lute.IO PRIVATE
include/lute/io.h

src/io.cpp
)

target_compile_features(Lute.IO PUBLIC cxx_std_17)
target_include_directories(Lute.IO PUBLIC "include" ${LIBUV_INCLUDE_DIR})
target_link_libraries(Lute.IO PRIVATE Lute.Runtime Luau.VM uv_a)
target_compile_options(Lute.IO PRIVATE ${LUTE_OPTIONS})
22 changes: 22 additions & 0 deletions lute/io/include/lute/io.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include "lua.h"
#include "lualib.h"

// open the library as a standard global luau library
int luaopen_io(lua_State* L);
// open the library as a table on top of the stack
int luteopen_io(lua_State* L);

namespace io
{

int read(lua_State* L);

static const luaL_Reg lib[] = {
{"read", read},

{nullptr, nullptr}
};

} // namespace io
136 changes: 136 additions & 0 deletions lute/io/src/io.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#include "lute/io.h"
#include "Luau/Variant.h"
#include "lute/runtime.h"

#include <uv.h>
#include <memory>

#include "lua.h"
#include "lualib.h"


namespace io
{

struct IOHandle
{
Luau::Variant<uv_pipe_t, uv_tty_t> streamVariant;
uv_loop_t* loop = nullptr;
ResumeToken resumeToken;
std::shared_ptr<IOHandle> self;
std::vector<char> buffer;

~IOHandle() {}

void closeHandles()
{
auto closeCb = [](uv_handle_t* handle)
{
IOHandle* ioh = static_cast<IOHandle*>(handle->data);
ioh->self.reset();
};

uv_stream_t *stream = getStream();
uv_read_stop(stream);
uv_close((uv_handle_t*)stream, closeCb);
}

uv_stream_t* getStream() {
if (auto* tty = streamVariant.get_if<uv_tty_t>())
return (uv_stream_t*)tty;
if (auto* pipe = streamVariant.get_if<uv_pipe_t>())
return (uv_stream_t*)pipe;
return nullptr;
}
};

static void allocBuffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf)
{
IOHandle* ioh = static_cast<IOHandle*>(handle->data);
ioh->buffer.resize(suggestedSize);
buf->base = ioh->buffer.data();
buf->len = ioh->buffer.size();
}

// IOHandle is closed immediately after one read since we don't need a long running stream for this API.
static void onTtyRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf)
{
IOHandle* handle = static_cast<IOHandle*>(stream->data);

if (nread > 0)
{
handle->resumeToken->complete(
[data = std::string(buf->base, nread)](lua_State* L) -> int
{
lua_pushlstring(L, data.c_str(), data.size());
return 1;
}
);
}
else if (nread < 0)
{
handle->resumeToken->fail(uv_strerror(nread));
}

handle->closeHandles();
}

int read(lua_State* L)
{
auto handle = std::make_shared<IOHandle>();
handle->loop = uv_default_loop();
handle->resumeToken = getResumeToken(L);
handle->self = handle;

uv_handle_type ht = uv_guess_handle(fileno(stdin));
if (ht == UV_TTY)
{
uv_tty_t& tty = handle->streamVariant.emplace<uv_tty_t>();
int status = uv_tty_init(handle->loop, &tty, fileno(stdin), 0);
if (status < 0)
luaL_error(L, "Failed to initialize TTY: %s", uv_strerror(status));
}
else if (ht == UV_NAMED_PIPE || ht == UV_FILE)
{
uv_pipe_t& pipe = handle->streamVariant.emplace<uv_pipe_t>();
int status = uv_pipe_init(handle->loop, static_cast<uv_pipe_t*>(&pipe), 0);
if (status < 0)
luaL_error(L, "Failed to initialize pipe: %s", uv_strerror(status));
uv_pipe_open(static_cast<uv_pipe_t*>(&pipe), fileno(stdin));
}
else
{
luaL_error(L, "Unsupported stdin type");
}

uv_stream_t *stream = handle->getStream();
stream->data = handle.get();
uv_read_start(stream, allocBuffer, onTtyRead);
return lua_yield(L, 0);
}

} // namespace io

int luaopen_io(lua_State* L)
{
luaL_register(L, "io", io::lib);
return 1;
}

int luteopen_io(lua_State* L)
{
lua_createtable(L, 0, std::size(io::lib));

for (auto& [name, func] : io::lib)
{
if (!name || !func)
break;

lua_pushcfunction(L, func, name);
lua_setfield(L, -2, name);
}

lua_setreadonly(L, -1, 1);

return 1;
}
19 changes: 19 additions & 0 deletions lute/std/libs/io.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--!strict
-- @std/io
-- stdlib for `@lute/io`
-- Provides standard input, output, and other I/O utility functions

local io = require("@lute/io")

-- User input can be provided via command line stdin, piped input, or redirection from a file. See `examples/user_input.luau`.
function input(prompt: string?): string
if prompt then
-- We temporarily use `print` for prompt until we have proper stdout support, so there is a `\n` between prompt and input.
print(prompt)
end
return io.read()
end

return table.freeze({
input = input,
})