116 lines
3.6 KiB
C++
116 lines
3.6 KiB
C++
// Loading Lua off the SD card. Lua's stock loaders go through stdio, which cannot see the
|
|
// mount, so every path into the filesystem goes through FsProvider instead.
|
|
|
|
#include <lua/runtime.h>
|
|
|
|
extern "C" {
|
|
#include "lauxlib.h"
|
|
#include "lua.h"
|
|
}
|
|
|
|
namespace esp32lua {
|
|
namespace {
|
|
|
|
struct ChunkReader {
|
|
FileReader* file;
|
|
char buffer[512];
|
|
};
|
|
|
|
const char* readChunk(lua_State*, void* context, size_t* size) {
|
|
ChunkReader* reader = static_cast<ChunkReader*>(context);
|
|
const int32_t read = reader->file->read(reader->buffer, sizeof(reader->buffer));
|
|
*size = read > 0 ? static_cast<size_t>(read) : 0;
|
|
return read > 0 ? reader->buffer : nullptr;
|
|
}
|
|
|
|
// Loads a path onto the stack as a chunk, or pushes nothing and returns a Lua status.
|
|
int load(lua_State* state, FsProvider& fs, const std::string& path) {
|
|
ChunkReader reader;
|
|
reader.file = fs.openRead(path);
|
|
if (!reader.file) {
|
|
lua_pushfstring(state, "cannot open %s", path.c_str());
|
|
return LUA_ERRFILE;
|
|
}
|
|
const std::string chunkname = "@" + path;
|
|
const int status = lua_load(state, readChunk, &reader, chunkname.c_str(), "t");
|
|
delete reader.file;
|
|
return status;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool Runtime::loadScript(const std::string& path) {
|
|
return load(state_, *providers_.fs, path) == LUA_OK;
|
|
}
|
|
|
|
// Resolves a module name against package.path, reporting every path tried the way the stock
|
|
// searcher does.
|
|
int Runtime::searchModule(lua_State* state) {
|
|
Runtime* runtime = Runtime::from(state);
|
|
std::string name = luaL_checkstring(state, 1);
|
|
for (size_t at = 0; at < name.size(); at++) {
|
|
if (name[at] == '.') name[at] = '/';
|
|
}
|
|
|
|
lua_getglobal(state, "package");
|
|
lua_getfield(state, -1, "path");
|
|
const std::string templates = luaL_optstring(state, -1, "");
|
|
lua_pop(state, 2);
|
|
|
|
std::string tried;
|
|
size_t start = 0;
|
|
while (start <= templates.size()) {
|
|
const size_t end = templates.find(';', start);
|
|
std::string candidate = templates.substr(start, end == std::string::npos ? std::string::npos : end - start);
|
|
start = end == std::string::npos ? templates.size() + 1 : end + 1;
|
|
if (candidate.empty()) continue;
|
|
|
|
const size_t mark = candidate.find('?');
|
|
if (mark != std::string::npos) candidate.replace(mark, 1, name);
|
|
if (!runtime->providers_.fs->exists(candidate)) {
|
|
tried += "\n\tno file '" + candidate + "'";
|
|
continue;
|
|
}
|
|
if (load(state, *runtime->providers_.fs, candidate) != LUA_OK) {
|
|
return luaL_error(state, "error loading module '%s' from '%s':\n\t%s", luaL_checkstring(state, 1),
|
|
candidate.c_str(), lua_tostring(state, -1));
|
|
}
|
|
lua_pushstring(state, candidate.c_str());
|
|
return 2;
|
|
}
|
|
lua_pushstring(state, tried.c_str());
|
|
return 1;
|
|
}
|
|
|
|
int Runtime::loadFile(lua_State* state) {
|
|
Runtime* runtime = Runtime::from(state);
|
|
if (load(state, *runtime->providers_.fs, luaL_checkstring(state, 1)) == LUA_OK) return 1;
|
|
lua_pushnil(state);
|
|
lua_insert(state, -2);
|
|
return 2;
|
|
}
|
|
|
|
void Runtime::installLoader(const std::string& appDir) {
|
|
lua_getglobal(state_, "package");
|
|
|
|
const std::string path = appDir + "/?.lua;" + paths_.lib + "/?.lua";
|
|
lua_pushlstring(state_, path.data(), path.size());
|
|
lua_setfield(state_, -2, "path");
|
|
|
|
// Keep the preload searcher, drop the C loaders: they can only report misleading errors
|
|
// about shared objects that were never there.
|
|
lua_getfield(state_, -1, "searchers");
|
|
lua_pushcfunction(state_, searchModule);
|
|
lua_rawseti(state_, -2, 2);
|
|
for (int at = 3; at <= 4; at++) {
|
|
lua_pushnil(state_);
|
|
lua_rawseti(state_, -2, at);
|
|
}
|
|
lua_pop(state_, 2);
|
|
|
|
lua_pushcfunction(state_, loadFile);
|
|
lua_setglobal(state_, "loadfile");
|
|
}
|
|
|
|
} // namespace esp32lua
|