// 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 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(context); const int32_t read = reader->file->read(reader->buffer, sizeof(reader->buffer)); *size = read > 0 ? static_cast(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